Merge ~techalchemy/ubuntu-qa-tools:feature/in-memory-cookiedb into ubuntu-qa-tools:master

Proposed by Dan Ryan
Status: Needs review
Proposed branch: ~techalchemy/ubuntu-qa-tools:feature/in-memory-cookiedb
Merge into: ubuntu-qa-tools:master
Diff against target: 121 lines (+49/-35)
1 file modified
common/lpl_common.py (+49/-35)
Reviewer Review Type Date Requested Status
Mike Salvatore Pending
Review via email: mp+378348@code.launchpad.net

Commit message

Use in-memory sqlite database for cookie storage and cookiejar generation

- Also addresses an issue converting launchpadlib.__version__ to and from integers due to byte/unicode encoding changes which prevented importing on python 3

To post a comment you must log in.
Revision history for this message
Steve Beattie (sbeattie) wrote :

Did you actually try to use this? The sql portion of the change fails for me like so:

  $UCT/scripts/sis-changes --ppa ubuntu --pocket Proposed -r bionic --download ${HOME}/tmp/prepare-kernel-usn-6r8qbh5h/usn-bionic-linux-azure-5.3 linux-azure-5.3
  Traceback (most recent call last):
    File "/home/steve/git/ubuntu-cve-tracker/scripts/sis-changes", line 158, in <module>
      opener = lpl_common.opener_with_cookie(cve_lib.config["plb_authentication"])
    File "/home/steve/git/ubuntu-cve-tracker/scripts/lpl_common.py", line 148, in opener_with_cookie
      for line in cookie_db.iterdump():
    File "/usr/lib/python3.7/sqlite3/dump.py", line 30, in _iterdump
      schema_res = cu.execute(q)
  sqlite3.OperationalError: database is locked

which is why the shutil copy is performed in the original code.

Also, would prefer two distinct fixes as two distinct commits, please.

Thanks.

Revision history for this message
Steve Beattie (sbeattie) wrote :

That said, the changes around the launchpadlib version check looked fine; I pulled that change out and have merged that into master.

Thanks!

Revision history for this message
Steve Beattie (sbeattie) wrote :

The real solution, by the by, is to kill the remaining users of opener_with_cookie(), and convert them to using the proper auth mechanism from python-launchpadlib, either via lpl_common.py:connect() or directly.

(The opener_with_cookie() function is a hysterical/historical artifact dating back before python-launchpadlib existed and launchpad supported application auth tokens, and the only way you could interact was via web-scraping.)

Revision history for this message
Dan Ryan (techalchemy) wrote :

First of all thanks for taking the time to try this out, I have no idea how the code actually gets invoked so I wasn't able to verify that there wouldn't be any issues with competitive locks on the db in question. I included the encoding change because while attempting to import the function in question, I found I couldn't. I'll be sure to make more granular commits so that they can be cherry picked as needed.

I anticipated that, if necessary, I'd iterate on this change (e.g. by loading the db in memory instead of accessing it directly). This was a change we discussed (and there is a 'todo' in the code about it), but I am happy to either 'close' this merge request if I can figure out how to do that, or to make the changes you are suggesting to the remaining callers of this function.

Unmerged commits

1d490ec... by Dan Ryan

Use in-memory sqlite database for cookie storage and cookiejar generation

Signed-off-by: Dan Ryan <email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/common/lpl_common.py b/common/lpl_common.py
index de9281f..6b57854 100644
--- a/common/lpl_common.py
+++ b/common/lpl_common.py
@@ -7,7 +7,7 @@
77
8from __future__ import print_function8from __future__ import print_function
99
10import os, sys, tempfile, time, shutil, launchpadlib10import os, sys, tempfile, time, shutil, launchpadlib, io
11from launchpadlib.launchpad import Launchpad11from launchpadlib.launchpad import Launchpad
12from launchpadlib.credentials import Credentials12from launchpadlib.credentials import Credentials
13import launchpadlib.errors13import launchpadlib.errors
@@ -20,23 +20,26 @@ except ImportError:
20# as of 16.04, launchpadlib supports python320# as of 16.04, launchpadlib supports python3
21# so make code support both python2 and python321# so make code support both python2 and python3
22if sys.version_info > (3, 0):22if sys.version_info > (3, 0):
23 string_types = (str,)
23 from http.cookiejar import LoadError, MozillaCookieJar24 from http.cookiejar import LoadError, MozillaCookieJar
25 from io import StringIO
24 from urllib.request import HTTPCookieProcessor, HTTPError, build_opener26 from urllib.request import HTTPCookieProcessor, HTTPError, build_opener
25 from urllib.parse import unquote27 from urllib.parse import unquote
26else:28else:
29 string_types = (basestring,)
27 from cookielib import LoadError, MozillaCookieJar30 from cookielib import LoadError, MozillaCookieJar
31 from cStringIO import StringIO
28 from urllib2 import HTTPCookieProcessor, HTTPError, build_opener32 from urllib2 import HTTPCookieProcessor, HTTPError, build_opener
29 from urllib import unquote33 from urllib import unquote
3034
31version_min = [1,5,7]35version_min = [1,5,7]
32if sys.version_info > (3, 0):36lp_version = launchpadlib.__version__
33 if [int(x) for x in launchpadlib.__version__.split('.')] < version_min:37if not isinstance(lp_version, string_types):
34 raise ValueError("Requires launchpadlib version %s or later (%s in use)" \38 lp_version = lp_version.decode()
35 % (".".join(version_min), launchpadlib.__version__))39
36else:40if version_min > list(map(int, lp_version.split('.'))):
37 if [int(x) for x in launchpadlib.__version__.decode().split('.')] < version_min:41 raise ValueError("Requires launchpadlib version %s or later (%s in use)" \
38 raise ValueError("Requires launchpadlib version %s or later (%s in use)" \42 % (".".join(map(str, version_min)), lp_version))
39 % (".".join(version_min), launchpadlib.__version__))
4043
41def connect(use_edge=False, beta=False, version=None, uri=None, bot=None):44def connect(use_edge=False, beta=False, version=None, uri=None, bot=None):
4245
@@ -136,36 +139,47 @@ def opener_with_cookie(cookie_file):
136 old_umask = os.umask(0o077)139 old_umask = os.umask(0o077)
137140
138 # Work around Firefox 3.5's dumb sqlite locking problems by copying cookies out:141 # Work around Firefox 3.5's dumb sqlite locking problems by copying cookies out:
139 tmp = None142 opener = None
143 cj = MozillaCookieJar()
140 if cookie_file.endswith('.sqlite'):144 if cookie_file.endswith('.sqlite'):
141 (cookie_path, cookie_name) = os.path.split(cookie_file)
142 with tempfile.NamedTemporaryFile(prefix='cookies-XXXXXX', suffix='.sqlite') as sql_handle:
143 sql = sql_handle.name
144 shutil.copyfile(cookie_file, sql)
145145
146 match = '%launchpad.net'146 sql_script = StringIO()
147 con = sqlite.connect(sql)147 with sqlite.connect(cookie_file) as cookie_db:
148 cur = con.cursor()148 for line in cookie_db.iterdump():
149 cur.execute("select host, path, isSecure, expiry, name, value from moz_cookies where host like ?", [match])149 sql_script.write("{0}\n".format(line))
150 ftstr = ["FALSE","TRUE"]150 sql_script.seek(0)
151 tmp = tempfile.NamedTemporaryFile(prefix='cookies-XXXXXX', suffix='.mozilla', mode='w+')
152 cookie_file = tmp.name
153 tmp.write("# HTTP Cookie File\n")
154 for item in cur.fetchall():
155 str = "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % ( item[0], \
156 ftstr[item[0].startswith('.')], item[1], \
157 ftstr[item[2]], item[3], item[4], item[5])
158 tmp.write(str)
159 sql = None
160 tmp.flush()
161151
162 cj = MozillaCookieJar()152 match = '%launchpad.net'
163 try:153 query = "select host, path, isSecure, expiry, name, value from moz_cookies where host like ?"
164 cj.load(cookie_file)154 with tempfile.NamedTemporaryFile(prefix='cookies-XXXXXX', suffix='.mozilla', mode='w+') as cookie_jar:
165 except LoadError as e:155 with sqlite.connect(":memory:") as con:
166 print("Failed to load cookie from file (%s): %s - continuing anyway..." % (cookie_file, e.strerror))156 cur = con.cursor()
157 try:
158 cur.executescript(sql_script.read())
159 cur.commit()
160 except sqlite.Error:
161 print("Failed loading cookied DB...")
162 raise
163
164 try:
165 cur.execute(query, [match])
166 except sqlite.Error:
167 print("Failed reading database...")
168 raise
169 ftstr = ["FALSE","TRUE"]
170 cookie_jar.write("# HTTP Cookie File\n")
171 for item in cur.fetchall():
172 str_ = "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % ( item[0], \
173 ftstr[item[0].startswith('.')], item[1], \
174 ftstr[item[2]], item[3], item[4], item[5])
175 cookie_jar.write(str_)
176
177 cookie_jar.flush()
178 try:
179 cj.load(cookie_jar.name)
180 except LoadError as e:
181 print("Failed to load cookie from file ({0}): {1!s} - continuing anyway...".format(cookie_jar.name, e))
167 opener = build_opener(HTTPCookieProcessor(cj))182 opener = build_opener(HTTPCookieProcessor(cj))
168 tmp = None
169 os.umask(old_umask)183 os.umask(old_umask)
170 return opener184 return opener
171185

Subscribers

People subscribed via source and target branches