Merge ~ubuntu-release/britney/+git/britney2-ubuntu:sil2100/private-runs into ~ubuntu-release/britney/+git/britney2-ubuntu:master

Proposed by Łukasz Zemczak
Status: Needs review
Proposed branch: ~ubuntu-release/britney/+git/britney2-ubuntu:sil2100/private-runs
Merge into: ~ubuntu-release/britney/+git/britney2-ubuntu:master
Diff against target: 716 lines (+476/-43) (has conflicts)
8 files modified
britney.conf (+15/-0)
britney.conf.template (+15/-0)
britney2/policies/autopkgtest.py (+161/-41)
tests/__init__.py (+12/-0)
tests/mock_swift.py (+20/-1)
tests/mock_swiftclient.py (+70/-0)
tests/test_autopkgtest.py (+177/-1)
tests/test_policy.py (+6/-0)
Conflict in tests/__init__.py
Reviewer Review Type Date Requested Status
Steve Langasek Needs Fixing
Review via email: mp+399727@code.launchpad.net

Commit message

Add support for running private tests and using private PPAs.

Description of the change

Add support for running private tests and using private PPAs.

The autopkgtest-cloud counterpart is here: https://code.launchpad.net/~ubuntu-release/autopkgtest-cloud/+git/autopkgtest-cloud/+merge/399668

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

Looks good, minor comments.

review: Needs Fixing
Revision history for this message
Łukasz Zemczak (sil2100) wrote :

Thank you for the review Steve! On it!

3b1699d... by Łukasz Zemczak

Fix some comments.

Revision history for this message
Łukasz Zemczak (sil2100) wrote :

Ok, pushed some comment fixes and also commented on the exception handling part. Could you take a look again? Oh, and in previous inline comments I also addressed the question about private PPAs that are not embargoed.

Revision history for this message
Łukasz Zemczak (sil2100) wrote :

Actually I almost forgot about one thing and I'm working on it right now: since the test results are private, the britney ADT report will have SWIFT urls to the results which will not works (as the container is private) - but we can have a small SSO protected wrapper to fetch the results for us in webcontrol.

1de71db... by Łukasz Zemczak

Add ability to share results with selected people.

a21c904... by Łukasz Zemczak

Merge branch 'master' of git+ssh://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu into sil2100/private-runs

d1549b9... by Łukasz Zemczak

Commit the work regarding private-results handling.

7ff150c... by Łukasz Zemczak

Switch from swiftclient for private PPAs to using HTTP with X-Auth-Token instead.

This way there's less secrets that need to be shared and less new code to introduce. We also modified the test tooling to be able to check for authentication tokens in the queries.

f79ec3b... by Łukasz Zemczak

Revert "Switch from swiftclient for private PPAs to using HTTP with X-Auth-Token instead."

This reverts commit 7ff150ced70d0db565378683909e22dfac5383e1.

Sadly we can't use the X-Auth-Token approach due to implementational details (the token is valid only for an hour). So we need to switch back to using swiftclient.

41b606c... by Łukasz Zemczak

Commit fixes from staging testing: add required region name parameter.

090ccdf... by Łukasz Zemczak

Add an option to configure whether all ADT test results should be displayed or not.

738cac8... by Łukasz Zemczak

Add additional support for additional private test retry capabilities.

1848597... by Łukasz Zemczak

Support PPAs which have fingerprint enabled in them.

Unmerged commits

1848597... by Łukasz Zemczak

Support PPAs which have fingerprint enabled in them.

738cac8... by Łukasz Zemczak

Add additional support for additional private test retry capabilities.

090ccdf... by Łukasz Zemczak

Add an option to configure whether all ADT test results should be displayed or not.

41b606c... by Łukasz Zemczak

Commit fixes from staging testing: add required region name parameter.

f79ec3b... by Łukasz Zemczak

Revert "Switch from swiftclient for private PPAs to using HTTP with X-Auth-Token instead."

This reverts commit 7ff150ced70d0db565378683909e22dfac5383e1.

Sadly we can't use the X-Auth-Token approach due to implementational details (the token is valid only for an hour). So we need to switch back to using swiftclient.

7ff150c... by Łukasz Zemczak

Switch from swiftclient for private PPAs to using HTTP with X-Auth-Token instead.

This way there's less secrets that need to be shared and less new code to introduce. We also modified the test tooling to be able to check for authentication tokens in the queries.

d1549b9... by Łukasz Zemczak

Commit the work regarding private-results handling.

a21c904... by Łukasz Zemczak

Merge branch 'master' of git+ssh://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu into sil2100/private-runs

1de71db... by Łukasz Zemczak

Add ability to share results with selected people.

3b1699d... by Łukasz Zemczak

Fix some comments.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/britney.conf b/britney.conf
2index 52f5b4c..00ffe30 100644
3--- a/britney.conf
4+++ b/britney.conf
5@@ -101,11 +101,26 @@ ADT_SHARED_RESULTS_CACHE =
6 # Swift base URL with the results (must be publicly readable and browsable)
7 # or file location if results are pre-fetched
8 ADT_SWIFT_URL = https://autopkgtest.ubuntu.com/results/
9+# Swift identity with read access to the private result container
10+# (this is required whenever a private PPA is used for testing)
11+ADT_SWIFT_USER =
12+ADT_SWIFT_PASS =
13+ADT_SWIFT_AUTH_URL =
14+ADT_SWIFT_TENANT =
15+ADT_SWIFT_REGION =
16+# List of launchpad users/teams that should have read access to any private
17+# result logs
18+ADT_PRIVATE_SHARED =
19+ADT_PRIVATE_URL = https://autopkgtest.ubuntu.com/private-results/
20+ADT_PRIVATE_RETRY =
21 # Base URL for autopkgtest site, used for links in the excuses
22 ADT_CI_URL = https://autopkgtest.ubuntu.com/
23 # URL for the autopkgtest database, if used
24 ADT_DB_URL = https://autopkgtest.ubuntu.com/static/autopkgtest.db
25 ADT_HUGE = 20
26+# Change to 'yes' for excuses to include all ADT results, even those requiring
27+# no action (like passing or always-failed)
28+ADT_SHOW_IRRELEVANT = no
29
30 # Autopkgtest results can be used to influence the aging
31 ADT_REGRESSION_PENALTY =
32diff --git a/britney.conf.template b/britney.conf.template
33index b1df068..f662aa4 100644
34--- a/britney.conf.template
35+++ b/britney.conf.template
36@@ -124,11 +124,26 @@ ADT_SHARED_RESULTS_CACHE =
37 # or file location if results are pre-fetched
38 #ADT_SWIFT_URL = https://example.com/some/url
39 ADT_SWIFT_URL = file:///path/to/britney/state/debci.json
40+# Swift identity with read access to the private result container
41+# (this is required whenever a private PPA is used for testing)
42+ADT_SWIFT_USER =
43+ADT_SWIFT_PASS =
44+ADT_SWIFT_AUTH_URL =
45+ADT_SWIFT_TENANT =
46+ADT_SWIFT_REGION =
47+# List of launchpad users/teams that should have read access to any private
48+# result logs
49+ADT_PRIVATE_SHARED =
50+ADT_PRIVATE_URL =
51+ADT_PRIVATE_RETRY =
52 # Base URL for autopkgtest site, used for links in the excuses
53 ADT_CI_URL = https://example.com/
54 # Enable the huge queue for packages that trigger vast amounts of tests to not
55 # starve the regular queue
56 #ADT_HUGE = 20
57+# Change to 'yes' for excuses to include all ADT results, even those requiring
58+# no action (like passing or always-failed)
59+ADT_SHOW_IRRELEVANT = no
60
61 # Autopkgtest results can be used to influence the aging, leave
62 # ADT_REGRESSION_PENALTY empty to have regressions block migration
63diff --git a/britney2/policies/autopkgtest.py b/britney2/policies/autopkgtest.py
64index 7ff8c1f..d007ba0 100644
65--- a/britney2/policies/autopkgtest.py
66+++ b/britney2/policies/autopkgtest.py
67@@ -150,12 +150,26 @@ class AutopkgtestPolicy(BasePolicy):
68
69 try:
70 self.options.adt_ppas = self.options.adt_ppas.strip().split()
71+ # We also allow, for certain other use-cases, passing the PPA
72+ # fingerprint to for each of the PPAs. This however is not
73+ # currently used by the ADT policy, so get rid of it.
74+ for i, ppa in enumerate(self.options.adt_ppas):
75+ if '@' not in ppa and ':' in ppa:
76+ self.options.adt_ppas[i] = ppa.split(':')[0]
77 except AttributeError:
78 self.options.adt_ppas = []
79
80+ try:
81+ self.options.adt_private_shared = self.options.adt_private_shared.strip().split()
82+ except AttributeError:
83+ self.options.adt_private_shared = []
84+
85 self.swift_container = 'autopkgtest-' + options.series
86 if self.options.adt_ppas:
87- self.swift_container += '-' + options.adt_ppas[-1].replace('/', '-')
88+ # private PPAs require the auth credentials given + we allow the
89+ # PPA fingerprint attached at the end
90+ # those need to be removed before the ppa-based name can be used
91+ self.swift_container += '-' + options.adt_ppas[-1].rpartition('@')[2].replace('/', '-').partition(':')[0]
92
93 # restrict adt_arches to architectures we actually run for
94 self.adt_arches = []
95@@ -233,6 +247,48 @@ class AutopkgtestPolicy(BasePolicy):
96 else:
97 self.logger.info('%s does not exist, re-downloading all results from swift', self.results_cache_file)
98
99+ # log into swift in case we need to fetch some private results
100+ # this is optional - if there are no credentials present, results will
101+ # be fetched without authentication
102+ if self.options.adt_swift_user:
103+ if (not self.options.adt_swift_pass or
104+ not self.options.adt_swift_auth_url or
105+ not self.options.adt_swift_tenant or
106+ not self.options.adt_swift_region):
107+ raise RuntimeError('Incomplete swift credentials given')
108+
109+ # once swift credentials are given, the results will be published
110+ # to a private container
111+ self.swift_container = 'private-' + self.swift_container
112+
113+ # check if all private PPAs have a fingerprint provided
114+ # private PPAs need to follow the following pattern:
115+ # user:token@team/name:fingerprint
116+ for ppa in self.options.adt_ppas:
117+ # TODO: write a test for this
118+ if '@' in ppa and not re.match(r'^.+:.+@.+:.+$', ppa):
119+ raise RuntimeError('Private PPA %s not following required format (user:token@team/name:fingerprint)', ppa)
120+
121+ import swiftclient
122+
123+ if '/v2.0' not in self.options.adt_swift_auth_url:
124+ raise RuntimeError('Unsupported swift auth version')
125+
126+ self.logger.info('Creating an authenticated swift connection for user %s', self.options.adt_swift_user)
127+ self.swift_conn = swiftclient.Connection(
128+ authurl=self.options.adt_swift_auth_url,
129+ user=self.options.adt_swift_user,
130+ key=self.options.adt_swift_pass,
131+ tenant_name=self.options.adt_swift_tenant,
132+ auth_version='2.0',
133+ os_options={'region_name': self.options.adt_swift_region}
134+ )
135+ else:
136+ if any('@' in ppa for ppa in self.options.adt_ppas):
137+ raise RuntimeError('Private PPA configured but no swift credentials given')
138+
139+ self.swift_conn = None
140+
141 # read in the new results
142 if self.options.adt_swift_url.startswith('file://'):
143 debci_file = self.options.adt_swift_url[7:]
144@@ -478,7 +534,22 @@ class AutopkgtestPolicy(BasePolicy):
145 if status in ['REGRESSION', 'RUNNING-REFERENCE']:
146 if self.options.adt_retry_url_mech == 'run_id':
147 retry_url = self.options.adt_ci_url + 'api/v1/retry/' + run_id
148- else:
149+ elif self.options.adt_private_retry:
150+ # if a custom retry url mechanism has been given,
151+ # use that - but instead of passing PPAs with
152+ # sensitive credentials, we pass additional context
153+ # that will help the backend identify what env
154+ # needs to be used
155+ retry_url = self.options.adt_private_retry + \
156+ urllib.parse.urlencode([('release', self.options.series),
157+ ('arch', arch),
158+ ('package', testsrc),
159+ ('trigger', trigger),
160+ ('context', self.swift_container)])
161+ elif not any('@' in ppa for ppa in self.options.adt_ppas):
162+ # otherwise private PPAs currently should not
163+ # display a retry button as we can not guarantee
164+ # that the secrets will not leak
165 retry_url = self.options.adt_ci_url + 'request.cgi?' + \
166 urllib.parse.urlencode([('release', self.options.series),
167 ('arch', arch),
168@@ -502,8 +573,10 @@ class AutopkgtestPolicy(BasePolicy):
169 html_archmsg.append(message)
170
171 # render HTML line for testsrc entry, but only when action is
172- # or may be required
173- if r - {'PASS', 'NEUTRAL', 'RUNNING-ALWAYSFAIL', 'ALWAYSFAIL', 'IGNORE-FAIL'}:
174+ # or may be required or when britney is configured to print
175+ # everything
176+ if (self.options.adt_show_irrelevant or
177+ r - {'PASS', 'NEUTRAL', 'RUNNING-ALWAYSFAIL', 'ALWAYSFAIL', 'IGNORE-FAIL'}):
178 results_info.append("autopkgtest for %s: %s" % (testname, ', '.join(html_archmsg)))
179
180 if verdict != PolicyVerdict.PASS:
181@@ -891,55 +964,93 @@ class AutopkgtestPolicy(BasePolicy):
182 query['marker'] = query['prefix'] + latest_run_id
183
184 # request new results from swift
185- url = os.path.join(swift_url, self.swift_container)
186- url += '?' + urllib.parse.urlencode(query)
187- f = None
188- try:
189- f = self.download_retry(url)
190- if f.getcode() == 200:
191- result_paths = f.read().decode().strip().splitlines()
192- elif f.getcode() == 204: # No content
193- result_paths = []
194- else:
195- # we should not ever end up here as we expect a HTTPError in
196- # other cases; e. g. 3XX is something that tells us to adjust
197- # our URLS, so fail hard on those
198- raise NotImplementedError('fetch_swift_results(%s): cannot handle HTTP code %i' %
199- (url, f.getcode()))
200- except IOError as e:
201- # 401 "Unauthorized" is swift's way of saying "container does not exist"
202- if hasattr(e, 'code') and e.code == 401:
203- self.logger.info('fetch_swift_results: %s does not exist yet or is inaccessible', url)
204- return
205- # Other status codes are usually a transient
206- # network/infrastructure failure. Ignoring this can lead to
207- # re-requesting tests which we already have results for, so
208- # fail hard on this and let the next run retry.
209- self.logger.error('Failure to fetch swift results from %s: %s', url, str(e))
210- sys.exit(1)
211- finally:
212- if f is not None:
213- f.close()
214+ if self.swift_conn:
215+ # when we have an authenticated swift connection, use that to
216+ # fetch the result_path list as we might be fetching from an
217+ # otherwise unaccessible container
218+ from swiftclient.exceptions import ClientException
219+
220+ try:
221+ _, returned_paths = self.swift_conn.get_container(
222+ self.swift_container,
223+ query_string=urllib.parse.urlencode(query))
224+ except ClientException as e:
225+ # 401 "Unauthorized" is swift's way of saying "container does not exist"
226+ if e.http_status == 401 or e.http_status == 404:
227+ self.logger.info('fetch_swift_results: %s does not exist yet or is inaccessible', self.swift_container)
228+ return
229+ # Other status codes are usually a transient
230+ # network/infrastructure failure. Ignoring this can lead to
231+ # re-requesting tests which we already have results for, so
232+ # fail hard on this and let the next run retry.
233+ self.logger.error('Failure to fetch swift results from %s: %s', self.swift_container, str(e))
234+ sys.exit(1)
235+ result_paths = [p['subdir'] for p in returned_paths]
236+ else:
237+ url = os.path.join(swift_url, self.swift_container)
238+ url += '?' + urllib.parse.urlencode(query)
239+ f = None
240+ try:
241+ f = self.download_retry(url)
242+ if f.getcode() == 200:
243+ result_paths = f.read().decode().strip().splitlines()
244+ elif f.getcode() == 204: # No content
245+ result_paths = []
246+ else:
247+ # we should not ever end up here as we expect a HTTPError in
248+ # other cases; e. g. 3XX is something that tells us to adjust
249+ # our URLS, so fail hard on those
250+ raise NotImplementedError('fetch_swift_results(%s): cannot handle HTTP code %i' %
251+ (url, f.getcode()))
252+ except IOError as e:
253+ # 401 "Unauthorized" is swift's way of saying "container does not exist"
254+ if hasattr(e, 'code') and e.code == 401:
255+ self.logger.info('fetch_swift_results: %s does not exist yet or is inaccessible', url)
256+ return
257+ # same as above in the swift authenticated case
258+ self.logger.error('Failure to fetch swift results from %s: %s', url, str(e))
259+ sys.exit(1)
260+ finally:
261+ if f is not None:
262+ f.close()
263
264 for p in result_paths:
265 self.fetch_one_result(
266- os.path.join(swift_url, self.swift_container, p, 'result.tar'), src, arch)
267+ swift_url, self.swift_container, p, 'result.tar', src, arch)
268
269 fetch_swift_results._done = set()
270
271- def fetch_one_result(self, url, src, arch):
272+ def fetch_one_result(self, swift_url, container, path, name, src, arch):
273 '''Download one result URL for source/arch
274
275 Remove matching pending_tests entries.
276 '''
277+
278 f = None
279 try:
280- f = self.download_retry(url)
281- if f.getcode() == 200:
282- tar_bytes = io.BytesIO(f.read())
283+ if self.swift_conn:
284+ from swiftclient.exceptions import ClientException
285+
286+ # We don't need any additional retry logic as swiftclient
287+ # already performs retries (5 by default).
288+ url = os.path.join(path, name)
289+ try:
290+ _, contents = self.swift_conn.get_object(container, url)
291+ except ClientException as e:
292+ self.logger.error('Failure to fetch %s from container %s: %s',
293+ url, container, str(e))
294+ if e.http_status == 404:
295+ return
296+ sys.exit(1)
297+ tar_bytes = io.BytesIO(contents)
298 else:
299- raise NotImplementedError('fetch_one_result(%s): cannot handle HTTP code %i' %
300- (url, f.getcode()))
301+ url = os.path.join(swift_url, container, path, name)
302+ f = self.download_retry(url)
303+ if f.getcode() == 200:
304+ tar_bytes = io.BytesIO(f.read())
305+ else:
306+ raise NotImplementedError('fetch_one_result(%s): cannot handle HTTP code %i' %
307+ (url, f.getcode()))
308 except IOError as e:
309 self.logger.error('Failure to fetch %s: %s', url, str(e))
310 # we tolerate "not found" (something went wrong on uploading the
311@@ -1122,6 +1233,8 @@ class AutopkgtestPolicy(BasePolicy):
312
313 params = {'triggers': triggers}
314 if self.options.adt_ppas:
315+ # Note: the PPA might be a private PPA, and then the PPA parameter
316+ # includes the authorization token.
317 params['ppas'] = self.options.adt_ppas
318 qname = 'debci-ppa-%s-%s' % (self.options.series, arch)
319 elif huge:
320@@ -1130,6 +1243,11 @@ class AutopkgtestPolicy(BasePolicy):
321 qname = 'debci-%s-%s' % (self.options.series, arch)
322 params['submit-time'] = datetime.strftime(datetime.utcnow(), '%Y-%m-%d %H:%M:%S%z')
323
324+ if self.swift_conn:
325+ params['swiftuser'] = self.options.adt_swift_user
326+ if self.options.adt_private_shared:
327+ params['readable-by'] = self.options.adt_private_shared
328+
329 if self.amqp_channel:
330 import amqplib.client_0_8 as amqp
331 params = json.dumps(params)
332@@ -1358,7 +1476,9 @@ class AutopkgtestPolicy(BasePolicy):
333 run_id,
334 'log.gz')
335 else:
336- url = os.path.join(self.options.adt_swift_url,
337+ # Private runs have a different base url
338+ results_url = self.options.adt_private_url if self.swift_conn else self.options.adt_swift_url
339+ url = os.path.join(results_url,
340 self.swift_container,
341 self.options.series,
342 arch,
343diff --git a/tests/__init__.py b/tests/__init__.py
344index eaa9436..a277747 100644
345--- a/tests/__init__.py
346+++ b/tests/__init__.py
347@@ -395,9 +395,21 @@ ADT_PPAS =
348 ADT_SHARED_RESULTS_CACHE =
349
350 ADT_SWIFT_URL = http://localhost:18085
351+ADT_SWIFT_USER =
352+ADT_SWIFT_PASS =
353+ADT_SWIFT_AUTH_URL =
354+ADT_SWIFT_TENANT =
355+ADT_SWIFT_REGION =
356+ADT_PRIVATE_SHARED =
357+ADT_PRIVATE_URL =
358+ADT_PRIVATE_RETRY =
359 ADT_CI_URL = https://autopkgtest.ubuntu.com/
360 ADT_HUGE = 20
361+<<<<<<< tests/__init__.py
362 ADT_DB_URL =
363+=======
364+ADT_SHOW_IRRELEVANT = no
365+>>>>>>> tests/__init__.py
366
367 ADT_SUCCESS_BOUNTY =
368 ADT_REGRESSION_PENALTY =
369diff --git a/tests/mock_swift.py b/tests/mock_swift.py
370index b33c65a..0fc1a4c 100644
371--- a/tests/mock_swift.py
372+++ b/tests/mock_swift.py
373@@ -19,6 +19,10 @@ except ImportError:
374 from urlparse import urlparse, parse_qs
375
376
377+TESTS_DIR = os.path.dirname(os.path.abspath(__file__))
378+PROJECT_DIR = os.path.dirname(TESTS_DIR)
379+
380+
381 class SwiftHTTPRequestHandler(BaseHTTPRequestHandler):
382 '''Mock swift container with autopkgtest results
383
384@@ -124,7 +128,15 @@ class AutoPkgTestSwiftServer:
385 '''
386 SwiftHTTPRequestHandler.results = results
387
388- def start(self):
389+ def start(self, swiftclient=False):
390+ if swiftclient:
391+ # since we're running britney directly, the only way to reliably
392+ # mock out the swiftclient module is to override it in the local
393+ # path with the dummy version we created
394+ src = os.path.join(TESTS_DIR, 'mock_swiftclient.py')
395+ dst = os.path.join(PROJECT_DIR, 'swiftclient.py')
396+ os.symlink(src, dst)
397+
398 assert self.server_pid is None, 'already started'
399 if self.log:
400 self.log.close()
401@@ -148,12 +160,19 @@ class AutoPkgTestSwiftServer:
402 sys.exit(0)
403
404 def stop(self):
405+ # in case we were 'mocking out' swiftclient, remove the symlink we
406+ # created earlier during start()
407+ swiftclient_mod = os.path.join(PROJECT_DIR, 'swiftclient.py')
408+ if os.path.islink(swiftclient_mod):
409+ os.unlink(swiftclient_mod)
410+
411 assert self.server_pid, 'not running'
412 os.kill(self.server_pid, 15)
413 os.waitpid(self.server_pid, 0)
414 self.server_pid = None
415 self.log.close()
416
417+
418 if __name__ == '__main__':
419 srv = AutoPkgTestSwiftServer()
420 srv.set_results({'autopkgtest-testing': {
421diff --git a/tests/mock_swiftclient.py b/tests/mock_swiftclient.py
422new file mode 100644
423index 0000000..5f419ac
424--- /dev/null
425+++ b/tests/mock_swiftclient.py
426@@ -0,0 +1,70 @@
427+# Mock the swiftclient Python library, the bare minimum for ADT purposes
428+# Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@ubuntu.com>
429+
430+import os
431+import sys
432+
433+from urllib.request import urlopen
434+
435+
436+# We want to use this single Python module file to mock out the exception
437+# module as well.
438+sys.modules["swiftclient.exceptions"] = sys.modules[__name__]
439+
440+
441+class ClientException(Exception):
442+ def __init__(self, msg, http_status=''):
443+ super(ClientException, self).__init__(msg)
444+ self.msg = msg
445+ self.http_status = http_status
446+
447+
448+class Connection:
449+ def __init__(self, authurl, user, key, tenant_name, auth_version):
450+ self._mocked_swift = 'http://localhost:18085'
451+
452+ def get_container(self, container, marker=None, limit=None, prefix=None,
453+ delimiter=None, end_marker=None, path=None,
454+ full_listing=False, headers=None, query_string=None):
455+ url = os.path.join(self._mocked_swift, container) + '?' + query_string
456+ req = None
457+ try:
458+ req = urlopen(url, timeout=30)
459+ code = req.getcode()
460+ if code == 200:
461+ result_paths = req.read().decode().strip().splitlines()
462+ elif code == 204: # No content
463+ result_paths = []
464+ else:
465+ raise ClientException('MockedError', http_status=str(code))
466+ except IOError as e:
467+ # 401 "Unauthorized" is swift's way of saying "container does not exist"
468+ # But here we just assume swiftclient handles this via the usual
469+ # ClientException.
470+ raise ClientException('MockedError', http_status=str(e.code) if hasattr(e, 'code') else '')
471+ finally:
472+ if req is not None:
473+ req.close()
474+
475+ return (None, result_paths)
476+
477+ def get_object(self, container, obj):
478+ url = os.path.join(self._mocked_swift, container, obj)
479+ req = None
480+ try:
481+ req = urlopen(url, timeout=30)
482+ code = req.getcode()
483+ if code == 200:
484+ contents = req.read()
485+ else:
486+ raise ClientException('MockedError', http_status=str(code))
487+ except IOError as e:
488+ # 401 "Unauthorized" is swift's way of saying "container does not exist"
489+ # But here we just assume swiftclient handles this via the usual
490+ # ClientException.
491+ raise ClientException('MockedError', http_status=str(e.code) if hasattr(e, 'code') else '')
492+ finally:
493+ if req is not None:
494+ req.close()
495+
496+ return (None, contents)
497diff --git a/tests/test_autopkgtest.py b/tests/test_autopkgtest.py
498index 5c7bb72..906f729 100644
499--- a/tests/test_autopkgtest.py
500+++ b/tests/test_autopkgtest.py
501@@ -17,6 +17,7 @@ import urllib.parse
502
503 import apt_pkg
504 import yaml
505+from unittest.mock import patch, Mock
506
507 PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
508 sys.path.insert(0, PROJECT_DIR)
509@@ -214,7 +215,7 @@ class TestAutopkgtestBase(TestBase):
510 with open(email_path, 'w', encoding='utf-8') as email:
511 email.write(json.dumps(self.email_cache))
512
513- self.swift.start()
514+ self.swift.start(swiftclient=True)
515 (excuses_yaml, excuses_html, out) = self.run_britney()
516 self.swift.stop()
517
518@@ -2666,6 +2667,181 @@ class AT(TestAutopkgtestBase):
519 self.assertEqual(self.amqp_requests, set())
520 self.assertEqual(self.pending_requests, {})
521
522+ def test_ppas_fingerprint(self):
523+ '''Run test requests with PPAs where fingerprint is provided'''
524+
525+ self.data.add_default_packages(lightgreen=False)
526+
527+ for line in fileinput.input(self.britney_conf, inplace=True):
528+ if line.startswith('ADT_PPAS'):
529+ print('ADT_PPAS = joe/foo:fingerprint awesome-developers/staging')
530+ else:
531+ sys.stdout.write(line)
532+
533+ exc = self.run_it(
534+ [('lightgreen', {'Version': '2'}, 'autopkgtest')],
535+ {'lightgreen': (True, {'lightgreen': {'amd64': 'RUNNING-ALWAYSFAIL'}})},
536+ {'lightgreen': [('old-version', '1'), ('new-version', '2')]}
537+ )[1]
538+
539+ for arch in ['i386', 'amd64']:
540+ self.assertTrue(
541+ ('debci-ppa-testing-%s:lightgreen {"triggers": ["lightgreen/2"], '
542+ '"ppas": ["joe/foo", "awesome-developers/staging"]}') % arch in self.amqp_requests or
543+ ('debci-ppa-testing-%s:lightgreen {"ppas": ["joe/foo", '
544+ '"awesome-developers/staging"], "triggers": ["lightgreen/2"]}') % arch in self.amqp_requests,
545+ self.amqp_requests)
546+ self.assertEqual(len(self.amqp_requests), 2)
547+
548+ def test_private_ppas(self):
549+ '''Run test requests with an additional private PPA'''
550+
551+ self.data.add_default_packages(lightgreen=False)
552+
553+ for line in fileinput.input(self.britney_conf, inplace=True):
554+ if line.startswith('ADT_PPAS'):
555+ print('ADT_PPAS = first/ppa user:password@joe/foo:DEADBEEF')
556+ elif line.startswith('ADT_SWIFT_USER'):
557+ print('ADT_SWIFT_USER = user')
558+ elif line.startswith('ADT_SWIFT_PASS'):
559+ print('ADT_SWIFT_PASS = pass')
560+ elif line.startswith('ADT_SWIFT_TENANT'):
561+ print('ADT_SWIFT_TENANT = tenant')
562+ elif line.startswith('ADT_SWIFT_REGION'):
563+ print('ADT_SWIFT_REGION = region')
564+ elif line.startswith('ADT_SWIFT_AUTH_URL'):
565+ print('ADT_SWIFT_AUTH_URL = http://127.0.0.1:5000/v2.0/')
566+ elif line.startswith('ADT_PRIVATE_SHARED'):
567+ print('ADT_PRIVATE_SHARED = user1 team2')
568+ elif line.startswith('ADT_PRIVATE_URL'):
569+ print('ADT_PRIVATE_URL = http://localhost:18085/private-results/')
570+ else:
571+ sys.stdout.write(line)
572+
573+ exc = self.run_it(
574+ [('lightgreen', {'Version': '2'}, 'autopkgtest')],
575+ {'lightgreen': (True, {'lightgreen': {'amd64': 'RUNNING-ALWAYSFAIL'}})},
576+ {'lightgreen': [('old-version', '1'), ('new-version', '2')]}
577+ )[1]
578+
579+ # check if the private PPA info is propagated to the AMQP queue
580+ self.assertEqual(len(self.amqp_requests), 2)
581+ for request in self.amqp_requests:
582+ self.assertIn('"triggers": ["lightgreen/2"]', request)
583+ self.assertIn('"ppas": ["first/ppa", "user:password@joe/foo:DEADBEEF"]', request)
584+ self.assertIn('"swiftuser": "user"', request)
585+ self.assertIn('"readable-by": ["user1", "team2"]', request)
586+
587+ # add results to PPA specific swift container
588+ self.swift.set_results({'private-autopkgtest-testing-joe-foo': {
589+ 'testing/i386/l/lightgreen/20150101_100000@': (0, 'lightgreen 1', tr('passedbefore/1')),
590+ 'testing/i386/l/lightgreen/20150101_100100@': (4, 'lightgreen 2', tr('lightgreen/2')),
591+ 'testing/amd64/l/lightgreen/20150101_100101@': (0, 'lightgreen 2', tr('lightgreen/2')),
592+ }})
593+
594+ exc = self.run_it(
595+ [],
596+ {'lightgreen': (False, {'lightgreen/2': {'i386': 'REGRESSION', 'amd64': 'PASS'}})},
597+ {'lightgreen': [('old-version', '1'), ('new-version', '2')]}
598+ )[1]
599+ # check if the right container name is used and that no secrets are
600+ # leaked in the retry url (as it should be None now)
601+ self.assertEqual(
602+ exc['lightgreen']['policy_info']['autopkgtest'],
603+ {'lightgreen/2': {
604+ 'amd64': [
605+ 'PASS',
606+ 'http://localhost:18085/private-results/private-autopkgtest-testing-joe-foo/'
607+ 'testing/amd64/l/lightgreen/20150101_100101@/log.gz',
608+ None,
609+ 'http://localhost:18085/private-results/private-autopkgtest-testing-joe-foo/'
610+ 'testing/amd64/l/lightgreen/20150101_100101@/artifacts.tar.gz',
611+ None],
612+ 'i386': [
613+ 'REGRESSION',
614+ 'http://localhost:18085/private-results/private-autopkgtest-testing-joe-foo/'
615+ 'testing/i386/l/lightgreen/20150101_100100@/log.gz',
616+ None,
617+ 'http://localhost:18085/private-results/private-autopkgtest-testing-joe-foo/'
618+ 'testing/i386/l/lightgreen/20150101_100100@/artifacts.tar.gz',
619+ None]},
620+ 'verdict': 'REJECTED_PERMANENTLY'})
621+ self.assertEqual(self.amqp_requests, set())
622+ self.assertEqual(self.pending_requests, {})
623+
624+ def test_private_without_ppa(self):
625+ '''Run a private test (without using a private PPA)'''
626+
627+ self.data.add_default_packages(lightgreen=False)
628+
629+ for line in fileinput.input(self.britney_conf, inplace=True):
630+ if line.startswith('ADT_SWIFT_USER'):
631+ print('ADT_SWIFT_USER = user')
632+ elif line.startswith('ADT_SWIFT_PASS'):
633+ print('ADT_SWIFT_PASS = pass')
634+ elif line.startswith('ADT_SWIFT_TENANT'):
635+ print('ADT_SWIFT_TENANT = tenant')
636+ elif line.startswith('ADT_SWIFT_REGION'):
637+ print('ADT_SWIFT_REGION = region')
638+ elif line.startswith('ADT_SWIFT_AUTH_URL'):
639+ print('ADT_SWIFT_AUTH_URL = http://127.0.0.1:5000/v2.0/')
640+ elif line.startswith('ADT_PRIVATE_URL'):
641+ print('ADT_PRIVATE_URL = http://localhost:18085/private-results/')
642+ else:
643+ sys.stdout.write(line)
644+
645+ exc = self.run_it(
646+ [('lightgreen', {'Version': '2'}, 'autopkgtest')],
647+ {'lightgreen': (True, {'lightgreen': {'amd64': 'RUNNING-ALWAYSFAIL'}})},
648+ {'lightgreen': [('old-version', '1'), ('new-version', '2')]}
649+ )[1]
650+
651+ # check if the user info is propagated to the AMQP queue
652+ self.assertEqual(len(self.amqp_requests), 2)
653+ for request in self.amqp_requests:
654+ self.assertIn('"triggers": ["lightgreen/2"]', request)
655+ self.assertIn('"swiftuser": "user"', request)
656+ # we did not give a list of users to give read-access, so make sure
657+ # there are none
658+ self.assertNotIn('"readable-by"', request)
659+
660+ # add results to PPA specific swift container
661+ self.swift.set_results({'private-autopkgtest-testing': {
662+ 'testing/i386/l/lightgreen/20150101_100000@': (0, 'lightgreen 1', tr('passedbefore/1')),
663+ 'testing/i386/l/lightgreen/20150101_100100@': (4, 'lightgreen 2', tr('lightgreen/2')),
664+ 'testing/amd64/l/lightgreen/20150101_100101@': (0, 'lightgreen 2', tr('lightgreen/2')),
665+ }})
666+
667+ exc = self.run_it(
668+ [],
669+ {'lightgreen': (False, {'lightgreen/2': {'i386': 'REGRESSION', 'amd64': 'PASS'}})},
670+ {'lightgreen': [('old-version', '1'), ('new-version', '2')]}
671+ )[1]
672+
673+ # check if the right container name is used and that we still have the
674+ # retry url (it should only be hidden for private PPAs)
675+ self.assertEqual(
676+ exc['lightgreen']['policy_info']['autopkgtest'],
677+ {'lightgreen/2': {
678+ 'amd64': [
679+ 'PASS',
680+ 'http://localhost:18085/private-results/private-autopkgtest-testing/'
681+ 'testing/amd64/l/lightgreen/20150101_100101@/log.gz',
682+ 'https://autopkgtest.ubuntu.com/packages/l/lightgreen/testing/amd64',
683+ None,
684+ None],
685+ 'i386': [
686+ 'REGRESSION',
687+ 'http://localhost:18085/private-results/private-autopkgtest-testing/'
688+ 'testing/i386/l/lightgreen/20150101_100100@/log.gz',
689+ 'https://autopkgtest.ubuntu.com/packages/l/lightgreen/testing/i386',
690+ None,
691+ 'https://autopkgtest.ubuntu.com/request.cgi?release=testing&arch=i386&package=lightgreen&'
692+ 'trigger=lightgreen%2F2']},
693+ 'verdict': 'REJECTED_PERMANENTLY'})
694+ self.assertEqual(self.amqp_requests, set())
695+ self.assertEqual(self.pending_requests, {})
696+
697 def test_disable_upgrade_tester(self):
698 '''Run without second stage upgrade tester'''
699
700diff --git a/tests/test_policy.py b/tests/test_policy.py
701index 1952831..c6600c0 100644
702--- a/tests/test_policy.py
703+++ b/tests/test_policy.py
704@@ -43,6 +43,12 @@ def initialize_policy(test_name, policy_class, *args, **kwargs):
705 architectures=ARCH,
706 adt_swift_url='file://' + debci_data,
707 adt_ci_url='',
708+ adt_swift_user='',
709+ adt_swift_pass='',
710+ adt_swift_tenant='',
711+ adt_swift_auth_url='',
712+ adt_private_shared=[],
713+ adt_private_url='',
714 adt_success_bounty=3,
715 adt_regression_penalty=False,
716 adt_retry_url_mech='run_id',

Subscribers

People subscribed via source and target branches