Merge lp:~cjwatson/launchpad/cveimport-requests into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18682
Proposed branch: lp:~cjwatson/launchpad/cveimport-requests
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/bugs-remote-finders-requests
Diff against target: 234 lines (+83/-19)
6 files modified
constraints.txt (+1/-0)
lib/lp/bugs/scripts/cveimport.py (+17/-5)
lib/lp/services/config/schema-lazr.conf (+3/-0)
lib/lp/services/tests/test_timeout.py (+33/-2)
lib/lp/services/timeout.py (+28/-12)
setup.py (+1/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/cveimport-requests
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+347201@code.launchpad.net

Commit message

Convert update-cve to urlfetch with explicit proxy configuration.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)
Revision history for this message
Colin Watson (cjwatson) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'constraints.txt'
2--- constraints.txt 2018-05-31 10:23:03 +0000
3+++ constraints.txt 2018-06-05 01:51:40 +0000
4@@ -336,6 +336,7 @@
5 PyYAML==3.10
6 rabbitfixture==0.3.6
7 requests==2.7.0
8+requests-file==1.4.3
9 requests-toolbelt==0.6.2
10 responses==0.9.0
11 scandir==1.7
12
13=== modified file 'lib/lp/bugs/scripts/cveimport.py'
14--- lib/lp/bugs/scripts/cveimport.py 2016-08-16 15:33:02 +0000
15+++ lib/lp/bugs/scripts/cveimport.py 2018-06-05 01:51:40 +0000
16@@ -1,4 +1,4 @@
17-# Copyright 2009 Canonical Ltd. This software is licensed under the
18+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
19 # GNU Affero General Public License version 3 (see the file LICENSE).
20
21 """A set of functions related to the ability to parse the XML CVE database,
22@@ -10,9 +10,9 @@
23 import gzip
24 import StringIO
25 import time
26-import urllib2
27 import xml.etree.cElementTree as cElementTree
28
29+import requests
30 from zope.component import getUtility
31 from zope.event import notify
32 from zope.interface import implementer
33@@ -31,6 +31,10 @@
34 LaunchpadCronScript,
35 LaunchpadScriptFailure,
36 )
37+from lp.services.timeout import (
38+ override_timeout,
39+ urlfetch,
40+ )
41
42
43 CVEDB_NS = '{http://cve.mitre.org/cve/downloads/1.0}'
44@@ -209,14 +213,22 @@
45 elif self.options.cveurl is not None:
46 self.logger.info("Downloading CVE database from %s..." %
47 self.options.cveurl)
48+ proxies = {}
49+ if config.launchpad.http_proxy:
50+ proxies['http'] = config.launchpad.http_proxy
51+ proxies['https'] = config.launchpad.http_proxy
52 try:
53- url = urllib2.urlopen(self.options.cveurl)
54- except (urllib2.HTTPError, urllib2.URLError):
55+ with override_timeout(config.cveupdater.timeout):
56+ # Command-line options are trusted, so allow file://
57+ # URLs to ease testing.
58+ response = urlfetch(
59+ self.options.cveurl, proxies=proxies, allow_file=True)
60+ except requests.RequestException:
61 raise LaunchpadScriptFailure(
62 'Unable to connect for CVE database %s'
63 % self.options.cveurl)
64
65- cve_db_gz = url.read()
66+ cve_db_gz = response.content
67 self.logger.info("%d bytes downloaded." % len(cve_db_gz))
68 cve_db = gzip.GzipFile(
69 fileobj=StringIO.StringIO(cve_db_gz)).read()
70
71=== modified file 'lib/lp/services/config/schema-lazr.conf'
72--- lib/lp/services/config/schema-lazr.conf 2018-05-31 12:47:31 +0000
73+++ lib/lp/services/config/schema-lazr.conf 2018-06-05 01:51:40 +0000
74@@ -490,6 +490,9 @@
75 # datatype: string
76 cve_db_url: https://cve.mitre.org/data/downloads/allitems.xml.gz
77
78+# datatype: integer
79+timeout: 30
80+
81
82 [database]
83 # Connection strings, format as per the PQconnectdb connection string as
84
85=== modified file 'lib/lp/services/tests/test_timeout.py'
86--- lib/lp/services/tests/test_timeout.py 2018-06-05 01:31:47 +0000
87+++ lib/lp/services/tests/test_timeout.py 2018-06-05 01:51:40 +0000
88@@ -15,14 +15,22 @@
89 import threading
90 import xmlrpclib
91
92-from fixtures import MonkeyPatch
93+from fixtures import (
94+ MonkeyPatch,
95+ TempDir,
96+ )
97 from requests import Response
98 from requests.exceptions import (
99 ConnectionError,
100 InvalidSchema,
101 )
102-from testtools.matchers import MatchesStructure
103+from testtools.matchers import (
104+ ContainsDict,
105+ Equals,
106+ MatchesStructure,
107+ )
108
109+from lp.services.osutils import write_file
110 from lp.services.timeout import (
111 default_timeout,
112 get_default_timeout_function,
113@@ -368,6 +376,17 @@
114 self.assertEqual(
115 "No connection adapters were found for '%s'" % url, str(e))
116
117+ def test_urlfetch_does_not_support_file_urls_by_default(self):
118+ """urlfetch() does not support file urls by default."""
119+ set_default_timeout_function(lambda: 1)
120+ self.addCleanup(set_default_timeout_function, None)
121+ test_path = self.useFixture(TempDir()).join('file')
122+ write_file(test_path, '')
123+ url = 'file://' + test_path
124+ e = self.assertRaises(InvalidSchema, urlfetch, url)
125+ self.assertEqual(
126+ "No connection adapters were found for '%s'" % url, str(e))
127+
128 def test_urlfetch_no_proxy_by_default(self):
129 """urlfetch does not use a proxy by default."""
130 self.pushConfig('launchpad', http_proxy='http://proxy.example:3128/')
131@@ -399,6 +418,18 @@
132 {scheme: proxy for scheme in ('http', 'https')},
133 fake_send.calls[0][1]['proxies'])
134
135+ def test_urlfetch_supports_file_urls_if_allow_file(self):
136+ """urlfetch() supports file urls if explicitly asked to do so."""
137+ set_default_timeout_function(lambda: 1)
138+ self.addCleanup(set_default_timeout_function, None)
139+ test_path = self.useFixture(TempDir()).join('file')
140+ write_file(test_path, 'Success.')
141+ url = 'file://' + test_path
142+ self.assertThat(urlfetch(url, allow_file=True), MatchesStructure(
143+ status_code=Equals(200),
144+ headers=ContainsDict({'Content-Length': Equals(8)}),
145+ content=Equals('Success.')))
146+
147 def test_xmlrpc_transport(self):
148 """ Another use case for timeouts is communicating with external
149 systems using XMLRPC. In order to allow timeouts using XMLRPC we
150
151=== modified file 'lib/lp/services/timeout.py'
152--- lib/lp/services/timeout.py 2018-06-05 01:31:47 +0000
153+++ lib/lp/services/timeout.py 2018-06-05 01:51:40 +0000
154@@ -40,6 +40,7 @@
155 )
156 from requests.packages.urllib3.exceptions import ClosedPoolError
157 from requests.packages.urllib3.poolmanager import PoolManager
158+from requests_file import FileAdapter
159 from six import reraise
160
161 from lp.services.config import config
162@@ -318,25 +319,38 @@
163 class URLFetcher:
164 """Object fetching remote URLs with a time out."""
165
166- @staticmethod
167- def _makeSession(trust_env=None):
168- session = Session()
169+ def __init__(self):
170+ self.session = None
171+
172+ @with_timeout(cleanup='cleanup')
173+ def fetch(self, url, trust_env=None, use_proxy=False, allow_file=False,
174+ **request_kwargs):
175+ """Fetch the URL using a custom HTTP handler supporting timeout.
176+
177+ :param url: The URL to fetch.
178+ :param trust_env: If not None, set the session's trust_env to this
179+ to determine whether it fetches proxy configuration from the
180+ environment.
181+ :param use_proxy: If True, use Launchpad's configured proxy.
182+ :param allow_file: If True, allow file:// URLs. (Be careful to only
183+ pass this if the URL is trusted.)
184+ :param request_kwargs: Additional keyword arguments passed on to
185+ `Session.request`.
186+ """
187+ self.session = Session()
188 if trust_env is not None:
189- session.trust_env = trust_env
190+ self.session.trust_env = trust_env
191 # Mount our custom adapters.
192- session.mount("https://", CleanableHTTPAdapter())
193- session.mount("http://", CleanableHTTPAdapter())
194- return session
195+ self.session.mount("https://", CleanableHTTPAdapter())
196+ self.session.mount("http://", CleanableHTTPAdapter())
197+ if allow_file:
198+ self.session.mount("file://", FileAdapter())
199
200- @with_timeout(cleanup='cleanup')
201- def fetch(self, url, trust_env=None, use_proxy=False, **request_kwargs):
202- """Fetch the URL using a custom HTTP handler supporting timeout."""
203 request_kwargs.setdefault("method", "GET")
204 if use_proxy and config.launchpad.http_proxy:
205 request_kwargs.setdefault("proxies", {})
206 request_kwargs["proxies"]["http"] = config.launchpad.http_proxy
207 request_kwargs["proxies"]["https"] = config.launchpad.http_proxy
208- self.session = self._makeSession(trust_env=trust_env)
209 response = self.session.request(url=url, **request_kwargs)
210 response.raise_for_status()
211 # Make sure the content has been consumed before returning.
212@@ -345,7 +359,9 @@
213
214 def cleanup(self):
215 """Reset the connection when the operation timed out."""
216- self.session.close()
217+ if self.session is not None:
218+ self.session.close()
219+ self.session = None
220
221
222 def urlfetch(url, trust_env=None, **request_kwargs):
223
224=== modified file 'setup.py'
225--- setup.py 2018-05-31 10:23:03 +0000
226+++ setup.py 2018-06-05 01:51:40 +0000
227@@ -207,6 +207,7 @@
228 'PyYAML',
229 'rabbitfixture',
230 'requests',
231+ 'requests-file',
232 'requests-toolbelt',
233 'responses',
234 'scandir',