Merge lp:~robru/bileto/finalize into lp:~ci-train-staging-area/bileto/trunk-copy-please-ignore
- finalize
- Merge into trunk-copy-please-ignore
Proposed by
Robert Bruce Park
Status: | Merged |
---|---|
Merged at revision: | 535 |
Proposed branch: | lp:~robru/bileto/finalize |
Merge into: | lp:~ci-train-staging-area/bileto/trunk-copy-please-ignore |
Diff against target: |
886 lines (+310/-195) 16 files modified
bileto/actions.py (+9/-3) bileto/models.py (+3/-22) bileto/static/index.html (+3/-3) bileto/streams.py (+1/-1) bileto/worker/manager.py (+89/-19) bileto/worker/merge.py (+11/-0) bileto/worker/package.py (+9/-34) bileto/worker/vcs.py (+1/-1) debian/control (+1/-0) scripts/vcs.sh (+20/-4) tests/test_actions.py (+10/-3) tests/test_login.py (+1/-1) tests/test_models.py (+0/-20) tests/test_worker_manager.py (+113/-47) tests/test_worker_merge.py (+38/-0) tests/test_worker_package.py (+1/-37) |
To merge this branch: | bzr merge lp:~robru/bileto/finalize |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
CI Train Staging PPAs | Pending | ||
Review via email: mp+297594@code.launchpad.net |
Commit message
Testing merges to trunk.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bileto/actions.py' |
2 | --- bileto/actions.py 2016-06-04 00:33:30 +0000 |
3 | +++ bileto/actions.py 2016-06-16 06:25:31 +0000 |
4 | @@ -7,6 +7,7 @@ |
5 | class Actions: |
6 | """Define what actions can be taken on a ticket.""" |
7 | statuses = dict( |
8 | + assign='Assigning PPA...', |
9 | status='Updating status.', |
10 | build='Preparing packages.', |
11 | diff='Generating diffs.', |
12 | @@ -16,6 +17,11 @@ |
13 | ) |
14 | |
15 | @staticmethod |
16 | + def assign(manager): |
17 | + """Assign a PPA to this Ticket""" |
18 | + manager.assign() |
19 | + |
20 | + @staticmethod |
21 | def status(manager): |
22 | """Update Package States""" |
23 | manager.status() |
24 | @@ -39,9 +45,9 @@ |
25 | @staticmethod |
26 | def finalize(manager): |
27 | """Merge to Trunk""" |
28 | - manager.merge().push().nuke() |
29 | + manager.merge().push().nuke('Landed') |
30 | |
31 | @staticmethod |
32 | def abandon(manager): |
33 | - """Delete PPA""" |
34 | - manager.abandon().nuke() |
35 | + """Give Up PPA, Close Ticket""" |
36 | + manager.nuke('Abandoned') |
37 | |
38 | === modified file 'bileto/models.py' |
39 | --- bileto/models.py 2016-06-10 21:49:43 +0000 |
40 | +++ bileto/models.py 2016-06-16 06:25:31 +0000 |
41 | @@ -114,13 +114,6 @@ |
42 | |
43 | Link to a document that explains the steps to verify this silo.""" |
44 | |
45 | -SUMMARY = """Packages: |
46 | -{packages} |
47 | - |
48 | -More info: |
49 | -{REQUEST_URL_ROOT}{permalink} |
50 | -""" |
51 | - |
52 | COLORS = dict( |
53 | blue={'granted'}, |
54 | green={'landed'}, |
55 | @@ -264,17 +257,6 @@ |
56 | if self.series == 'xenial+vivid': |
57 | raise BiletoError(MIGRATE_TO_YAKKETY) |
58 | |
59 | - def summarize(self): |
60 | - """Return a text summary of this ticket.""" |
61 | - return SUMMARY.format( |
62 | - packages=N.join( |
63 | - 'https://launchpad.net/ubuntu/+source/' + source |
64 | - for source in self.sources_sorted), |
65 | - requestid=self.request_id, |
66 | - permalink=self.path, |
67 | - **environ # Trailing comma is syntax error in py3.4 |
68 | - ) |
69 | - |
70 | def publishable(self): |
71 | """Identify if this request is appropriate to publish or not.""" |
72 | if self.status in HIDDEN: |
73 | @@ -378,13 +360,14 @@ |
74 | self.artifacts = EMPTY |
75 | self.autopkgtest = EMPTY |
76 | |
77 | - def set_status(self, message): |
78 | + def set_status(self, message, **kwargs): |
79 | """Update status immediately.""" |
80 | self.update( |
81 | status=message, |
82 | job_log=environ.get('LOG_PATH') or |
83 | environ.get('REQUEST_PATH') or |
84 | - request.path) |
85 | + request.path, |
86 | + **kwargs) |
87 | self.commit() |
88 | return message |
89 | |
90 | @@ -398,12 +381,10 @@ |
91 | @lru_cache() |
92 | def lock_fd(self): |
93 | """Return the file descriptor that we lock on.""" |
94 | - print(self.lockfile) |
95 | return open(self.lockfile, 'w') |
96 | |
97 | def lock(self): |
98 | """Prevent multiple jobs from running simultaneously.""" |
99 | - print(self.lock_fd) |
100 | flock(self.lock_fd, LOCK_EX | LOCK_NB) |
101 | |
102 | # TODO: not needed yet |
103 | |
104 | === modified file 'bileto/static/index.html' |
105 | --- bileto/static/index.html 2016-06-14 09:25:57 +0000 |
106 | +++ bileto/static/index.html 2016-06-16 06:25:31 +0000 |
107 | @@ -224,12 +224,12 @@ |
108 | |
109 | <div class="actionbox" ng-if="req.request_id && expand"> |
110 | <table class="actions"><tr> |
111 | -<td ng-if="!editing"><a href="{{meta.jenkins}}/securityRealm/commenceLogin?from=/job/prepare-silo/parambuild%3FREQUEST_ID%3D{{req.request_id}}" target="_blank">Assign</a></td> |
112 | +<td ng-if="!editing"><a href="/log/{{req.request_id}}/assign" target="_blank">Assign</a></td> |
113 | <td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/build" target="_blank">Build</a></td> |
114 | <td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/diff" target="_blank">Diff</a></td> |
115 | <td ng-if="req.siloname && !editing"><a href="{{meta.jenkins}}/job/{{req.siloname.replace('/', '-')}}-2-publish/build" target="_blank">Publish</a></td> |
116 | -<td ng-if="req.siloname && !editing"><a href="{{meta.jenkins}}/job/{{req.siloname.replace('/', '-')}}-3-merge-clean/build" target="_blank">Merge</a></td> |
117 | -<td><a href="{{meta.jenkins}}/securityRealm/commenceLogin?from=/job/abandon/parambuild%3FREQUEST_ID%3D{{req.request_id}}" target="_blank">Abandon</a></td> |
118 | +<td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/finalize" target="_blank">Finalize</a></td> |
119 | +<td><a href="/log/{{req.request_id}}/abandon" target="_blank">Abandon</a></td> |
120 | <td ng-if="editing"><a ng-click="cancel()">Cancel Edit</a></td> |
121 | <td ng-if="!editing"><a ng-click="edit(req)">Edit</a></td> |
122 | </tr></table> |
123 | |
124 | === modified file 'bileto/streams.py' |
125 | --- bileto/streams.py 2016-06-14 09:21:38 +0000 |
126 | +++ bileto/streams.py 2016-06-16 06:25:31 +0000 |
127 | @@ -78,7 +78,7 @@ |
128 | |
129 | def assert_action(action): |
130 | """Abort if action is invalid.""" |
131 | - if action not in ('build', 'diff'): |
132 | + if action not in ('assign', 'build', 'diff', 'finalize', 'abandon'): |
133 | abort(404) |
134 | |
135 | |
136 | |
137 | === modified file 'bileto/worker/manager.py' |
138 | --- bileto/worker/manager.py 2016-06-14 11:51:01 +0000 |
139 | +++ bileto/worker/manager.py 2016-06-16 06:25:31 +0000 |
140 | @@ -11,11 +11,15 @@ |
141 | """ |
142 | |
143 | from os import environ |
144 | +from os.path import join |
145 | + |
146 | from contextlib import suppress |
147 | from functools import lru_cache |
148 | +from fcntl import flock, LOCK_EX |
149 | from collections import defaultdict |
150 | |
151 | -from lazr.restfulclient.errors import BadRequest, NotFound |
152 | +from requests import get |
153 | +from lazr.restfulclient.errors import NotFound |
154 | |
155 | from bileto.lplib import lp |
156 | from bileto.settings import Config |
157 | @@ -25,10 +29,15 @@ |
158 | from bileto.worker.gles import GLES |
159 | from bileto.worker.merge import Merge |
160 | from bileto.worker.manual import Manual |
161 | -from bileto.worker.vcs import parallelize |
162 | +from bileto.worker.vcs import VCS, parallelize |
163 | from bileto.worker.package import Package |
164 | from bileto.worker.secondary import Secondary |
165 | |
166 | +API_PREFIX = 'http://127.0.0.1:8080/v1/tickets?' |
167 | +READ_API = API_PREFIX + 'active_only&siloname=land' |
168 | + |
169 | +UNAVAILABLE = """No silos available! Please ask your friendly neighborhood \ |
170 | +trainguard to free some.""" |
171 | MERGE_ORDER = """Merges have been sorted by prerequisite \ |
172 | and will be merged in this order: |
173 | %s |
174 | @@ -59,6 +68,37 @@ |
175 | log.info('Packages targetting %s:\n%s\n', dest, N.join(names)) |
176 | |
177 | |
178 | +def discover_ppas(): |
179 | + """Discover what PPAs we have.""" |
180 | + for ppa in lp.ppa_team.ppas: |
181 | + dist = ppa.distribution_link.split('/')[-1] |
182 | + siloname = '{}/{}'.format(dist, ppa.name) |
183 | + if '/landing-' in siloname and '-deleted' not in siloname: |
184 | + yield siloname |
185 | + |
186 | + |
187 | +def choose_available_ppa(): |
188 | + """Randomly choose one available PPA for assignment.""" |
189 | + silonames = set(discover_ppas()) |
190 | + resp = get(READ_API).json() |
191 | + assigned = {ticket['siloname'] for ticket in resp.get('requests', [])} |
192 | + available = silonames - assigned |
193 | + if not available: |
194 | + raise BiletoError(UNAVAILABLE) |
195 | + return available.pop() |
196 | + |
197 | + |
198 | +def set_swift_creds(): |
199 | + """Ensure swift creds are set in environment variables.""" |
200 | + environ.update( |
201 | + OS_REGION_NAME=Config.os_region_name, |
202 | + OS_TENANT_NAME=Config.os_tenant_name, |
203 | + OS_AUTH_URL=Config.os_auth_url, |
204 | + OS_USERNAME=Config.os_username, |
205 | + OS_PASSWORD=Config.os_password, |
206 | + ) |
207 | + |
208 | + |
209 | class Manager: |
210 | """Manage the instantiation of Package objects.""" |
211 | def __init__(self, ticket): |
212 | @@ -134,13 +174,28 @@ |
213 | """Pass arguments down to methods.""" |
214 | log.abort_if_cancelled() |
215 | log.debug('Beginning step: {}'.format(attr.upper())) |
216 | - getattr(Package, 'pre_' + attr, NOP)(self.ticket) |
217 | parallelize([getattr(package, attr)(*args, **kwargs) |
218 | for name, package in self.packages().items()]) |
219 | - getattr(Package, 'post_' + attr, NOP)(self.ticket) |
220 | return self |
221 | return passthrough |
222 | |
223 | + def assign(self): |
224 | + """Assign a PPA to this ticket, if possible.""" |
225 | + with open(join(Config.HOME, 'bileto-assign-lock'), 'w') as lock: |
226 | + flock(lock, LOCK_EX) |
227 | + if not self.ticket.siloname: |
228 | + siloname = choose_available_ppa() |
229 | + log.info('This ticket has been assigned %s.', siloname) |
230 | + self.ticket.set_status('Ready to build', siloname=siloname) |
231 | + self.ticket.commit() |
232 | + else: |
233 | + log.info('This ticket is already assigned! Just build it!') |
234 | + |
235 | + def validate(self): |
236 | + """Halt job if ticket has invalid parameters.""" |
237 | + self.ticket.validate() |
238 | + return self |
239 | + |
240 | def upload(self): |
241 | """Prevent cancellation once uploading begins.""" |
242 | log.cancellable = False |
243 | @@ -152,12 +207,26 @@ |
244 | environ.update(INCLUDES=self.ticket.sources) |
245 | for func in (Manager.names, Manager.types, Manager.packages): |
246 | func.cache_clear() |
247 | - return self.__getattr__('diff')() |
248 | + self.__getattr__('diff')() |
249 | + set_swift_creds() |
250 | + stdout = parallelize([VCS.swift_upload()])[0] |
251 | + log.info('Uploaded diffs:\n%s', stdout) |
252 | + self.ticket.artifacts = stdout |
253 | + self.ticket.commit() |
254 | |
255 | - def nuke(self): |
256 | - """Delete PPA.""" |
257 | - self.delete_ppa(self.ticket.request_id) |
258 | - # TODO: Delete swift container |
259 | + def nuke(self, status): |
260 | + """Delete PPA contents.""" |
261 | + # TODO: Eventually delete ephemeral PPAs here |
262 | + # self.delete_ppa(self.ticket.request_id) |
263 | + with suppress(BiletoError): |
264 | + for source in self.ppa.getPublishedSources(): |
265 | + if source.status != 'Deleted': |
266 | + log.info('Deleting %s ppa.', source.display_name) |
267 | + source.requestDeletion() |
268 | + set_swift_creds() |
269 | + parallelize([VCS.delete_swift()]) |
270 | + self.ticket.set_status(status, siloname=EMPTY, artifacts=EMPTY) |
271 | + self.ticket.commit() |
272 | |
273 | @property |
274 | @lru_cache() |
275 | @@ -231,13 +300,14 @@ |
276 | # TODO: Needs to be devirted and all arches enabled. |
277 | return self.get_ppa(number) |
278 | |
279 | - def delete_ppa(self, number): |
280 | - """Unconditionally delete an ephemeral PPA.""" |
281 | - log.info('Deleting PPA %s.', number) |
282 | - try: |
283 | - self.get_ppa(name_ppa(number)).lp_delete() |
284 | - except BadRequest as err: |
285 | - # Will log 'Archive already deleted.' if deleted but not gone yet |
286 | - log.warning(err.content.decode('utf-8')) |
287 | - except AttributeError: |
288 | - pass # already gone for real |
289 | + # TODO: use this for ephemeral PPAs eventually |
290 | + # def delete_ppa(self, number): |
291 | + # """Unconditionally delete an ephemeral PPA.""" |
292 | + # log.info('Deleting PPA %s.', number) |
293 | + # try: |
294 | + # self.get_ppa(name_ppa(number)).lp_delete() |
295 | + # except BadRequest as err: |
296 | + # # Will log 'Archive already deleted.' if deleted but not gone yet |
297 | + # log.warning(err.content.decode('utf-8')) |
298 | + # except AttributeError: |
299 | + # pass # already gone for real |
300 | |
301 | === modified file 'bileto/worker/merge.py' |
302 | --- bileto/worker/merge.py 2016-06-15 06:14:03 +0000 |
303 | +++ bileto/worker/merge.py 2016-06-16 06:25:31 +0000 |
304 | @@ -175,3 +175,14 @@ |
305 | def expose(self): |
306 | """Push our locally built branch to launchpad.""" |
307 | yield from VCS.expose(self.lp_branch, **self.env) |
308 | + |
309 | + @coroutine |
310 | + def merge(self): |
311 | + """Merge target trunk back into our work branch.""" |
312 | + yield from VCS.fetch_work_branch(self.lp_branch, **self.env) |
313 | + yield from VCS.merge_trunk(self.target, **self.env) |
314 | + |
315 | + @coroutine |
316 | + def push(self): |
317 | + """Push our final release to target trunk.""" |
318 | + yield from VCS.push_to_trunk(self.target, **self.env) |
319 | |
320 | === modified file 'bileto/worker/package.py' |
321 | --- bileto/worker/package.py 2016-06-13 11:58:39 +0000 |
322 | +++ bileto/worker/package.py 2016-06-16 06:25:31 +0000 |
323 | @@ -3,16 +3,16 @@ |
324 | Defines steps common to all types of packages that we handle. |
325 | """ |
326 | |
327 | -from os import environ |
328 | from os.path import join |
329 | +from asyncio import coroutine |
330 | from contextlib import suppress |
331 | -from asyncio import coroutine, get_event_loop |
332 | |
333 | from bileto.settings import Config |
334 | |
335 | -from bileto.worker.log import log |
336 | from bileto.worker.vcs import VCS |
337 | |
338 | +EMPTY = '' |
339 | + |
340 | |
341 | class Package: |
342 | """Represent the steps necessary to build & publish a package.""" |
343 | @@ -31,16 +31,6 @@ |
344 | self.instances[join(series.name, name)] = self |
345 | |
346 | @coroutine |
347 | - def validate(self): |
348 | - """NOP; Validation handled globally.""" |
349 | - |
350 | - @staticmethod |
351 | - def post_validate(ticket): |
352 | - """Report ticket summary.""" |
353 | - ticket.validate() |
354 | - log.info(ticket.summarize()) |
355 | - |
356 | - @coroutine |
357 | def build(self): |
358 | """Build source package.""" |
359 | yield from VCS.build(**self.env) |
360 | @@ -91,25 +81,10 @@ |
361 | self.dest.distribution.main_archive)) |
362 | yield from VCS.diff_dest(DSC_URL=dest_dsc, **self.env) |
363 | |
364 | - @staticmethod |
365 | - def post_diff(ticket): |
366 | - """Upload all diffs to swift after they are all generated.""" |
367 | - environ.update( |
368 | - OS_REGION_NAME=Config.os_region_name, |
369 | - OS_TENANT_NAME=Config.os_tenant_name, |
370 | - OS_AUTH_URL=Config.os_auth_url, |
371 | - OS_USERNAME=Config.os_username, |
372 | - OS_PASSWORD=Config.os_password, |
373 | - ) |
374 | - stdout = get_event_loop().run_until_complete(VCS.swift_upload( |
375 | - # Don't put creds here as they'll leak into debug logs. |
376 | - REQUESTID=str(ticket.request_id), |
377 | - )) |
378 | - log.info('Uploaded diffs:\n%s', stdout) |
379 | - ticket.artifacts = stdout |
380 | - ticket.commit() |
381 | + @coroutine |
382 | + def merge(self): |
383 | + """NOP; implemented by subclasses.""" |
384 | |
385 | - @staticmethod |
386 | - def post_abandon(ticket): |
387 | - """Deactivate ticket.""" |
388 | - ticket.set_status('Abandoned') |
389 | + @coroutine |
390 | + def push(self): |
391 | + """NOP; implemented by subclasses.""" |
392 | |
393 | === modified file 'bileto/worker/vcs.py' |
394 | --- bileto/worker/vcs.py 2016-06-14 09:23:00 +0000 |
395 | +++ bileto/worker/vcs.py 2016-06-16 06:25:31 +0000 |
396 | @@ -17,7 +17,7 @@ |
397 | |
398 | def parallelize(tasks): |
399 | """Do each task in parallel with asyncio.""" |
400 | - get_event_loop().run_until_complete(gather(*tasks)) |
401 | + return get_event_loop().run_until_complete(gather(*tasks)) |
402 | |
403 | |
404 | class VCSError(BiletoError): |
405 | |
406 | === modified file 'debian/control' |
407 | --- debian/control 2015-12-13 01:11:10 +0000 |
408 | +++ debian/control 2016-06-16 06:25:31 +0000 |
409 | @@ -15,6 +15,7 @@ |
410 | python3-nose2, |
411 | python3-pep8, |
412 | python3-pyflakes | pyflakes, |
413 | + python3-requests, |
414 | python3-sqlalchemy, |
415 | Standards-Version: 3.9.6 |
416 | Homepage: https://launchpad.net/bileto |
417 | |
418 | === modified file 'scripts/vcs.sh' |
419 | --- scripts/vcs.sh 2016-06-13 11:39:56 +0000 |
420 | +++ scripts/vcs.sh 2016-06-16 06:25:31 +0000 |
421 | @@ -20,6 +20,7 @@ |
422 | WORKSPACE="$(pwd)" |
423 | WORKPARENT="$WORKSPACE/$SERIES/$SOURCE" |
424 | WORKDIR="$WORKPARENT/$SOURCE" |
425 | +SWIFT_CONTAINER="bileto-$REQUESTID" |
426 | [ -d "$WORKDIR" ] && cd "$WORKDIR" || true |
427 | IFS=' |
428 | ' |
429 | @@ -210,22 +211,24 @@ |
430 | root="https://objectstorage.prodstack4-5.canonical.com/v1" |
431 | auth="$(swift stat -v | sed -n 's/^\s\+Account:\s\+//p')" |
432 | timestamp="$(date --utc '+%Y-%m-%d_%H:%M:%S')" |
433 | - container="bileto-$REQUESTID" |
434 | [ -n "$auth" ] || die "Failed to determine swift account. $retry" |
435 | |
436 | diffs="" |
437 | for filename in */*/*.diff; do |
438 | base="$(basename "$filename")" |
439 | - confirmed="$(swift upload "$container" "$filename" --object-name="$timestamp/$base")" || die "Failed to upload diffs. $retry" |
440 | + confirmed="$(swift upload "$SWIFT_CONTAINER" "$filename" --object-name="$timestamp/$base")" || die "Failed to upload diffs. $retry" |
441 | size="$(wc --lines $filename | awk '{ print $1 }')" |
442 | - diffs="$diffs\n$root/$auth/$container/$confirmed ($size lines)" |
443 | + diffs="$diffs\n$root/$auth/$SWIFT_CONTAINER/$confirmed ($size lines)" |
444 | done |
445 | |
446 | - swift post "$container" --read-acl '.r:*,.rlistings' || die "Failed to publish diffs publicly. $retry" |
447 | + swift post "$SWIFT_CONTAINER" --read-acl '.r:*,.rlistings' || die "Failed to publish diffs publicly. $retry" |
448 | # shellcheck disable=SC2059 |
449 | printf "$diffs" |
450 | } |
451 | |
452 | +do_delete_swift() { |
453 | + loudly swift delete "$SWIFT_CONTAINER" || true |
454 | +} |
455 | |
456 | ################ |
457 | # BZR Commands # |
458 | @@ -289,6 +292,19 @@ |
459 | loudly bzr push "$BRANCH" --overwrite || die "Failed to push source tree to launchpad." |
460 | } |
461 | |
462 | +bzr_do_fetch_work_branch() { |
463 | + loudly bzr branch "$BRANCH" "$WORKDIR" |
464 | +} |
465 | + |
466 | +bzr_do_merge_trunk() { |
467 | + loudly bzr merge "$CACHE" || die "Failed to merge $BRANCH." |
468 | + loudly bzr commit -m 'Resync trunk.' || true |
469 | +} |
470 | + |
471 | +bzr_do_push_to_trunk() { |
472 | + loudly bzr push "$BRANCH" || die "Failed to push to $BRANCH. Check bot team membership and branch ownership." |
473 | +} |
474 | + |
475 | |
476 | ################ |
477 | # GIT Commands # |
478 | |
479 | === modified file 'tests/test_actions.py' |
480 | --- tests/test_actions.py 2016-04-14 21:39:20 +0000 |
481 | +++ tests/test_actions.py 2016-06-16 06:25:31 +0000 |
482 | @@ -13,6 +13,14 @@ |
483 | class MainTestCase(WorkerTestCase): |
484 | """Test Main Subprocess Script.""" |
485 | |
486 | + def test_actions_assign(self): |
487 | + """Ensure assign calls correct methods.""" |
488 | + manager = Mock() |
489 | + Actions.assign(manager) |
490 | + self.assertSequenceEqual(manager.mock_calls, [ |
491 | + call.assign(), |
492 | + ]) |
493 | + |
494 | def test_actions_status(self): |
495 | """Ensure status calls correct methods.""" |
496 | manager = Mock() |
497 | @@ -51,7 +59,7 @@ |
498 | self.assertSequenceEqual(manager.mock_calls, [ |
499 | call.merge(), |
500 | call.merge().push(), |
501 | - call.merge().push().nuke(), |
502 | + call.merge().push().nuke('Landed'), |
503 | ]) |
504 | |
505 | def test_actions_abandon(self): |
506 | @@ -59,6 +67,5 @@ |
507 | manager = Mock() |
508 | Actions.abandon(manager) |
509 | self.assertSequenceEqual(manager.mock_calls, [ |
510 | - call.abandon(), |
511 | - call.abandon().nuke(), |
512 | + call.nuke('Abandoned'), |
513 | ]) |
514 | |
515 | === modified file 'tests/test_login.py' |
516 | --- tests/test_login.py 2016-02-16 21:33:16 +0000 |
517 | +++ tests/test_login.py 2016-06-16 06:25:31 +0000 |
518 | @@ -21,7 +21,7 @@ |
519 | openid='test', |
520 | next='/', |
521 | ), follow_redirects=False) |
522 | - self.assertIn(b'https://login.ubuntu.com/+openid', ret.data) |
523 | + self.assertIn(b'You should be redirected', ret.data) |
524 | self.assertEqual(ret.status_code, 302) |
525 | |
526 | def test_login_get(self): |
527 | |
528 | === modified file 'tests/test_models.py' |
529 | --- tests/test_models.py 2016-06-03 21:37:27 +0000 |
530 | +++ tests/test_models.py 2016-06-16 06:25:31 +0000 |
531 | @@ -42,26 +42,6 @@ |
532 | db.session.add.assert_called_once_with(comment.return_value) |
533 | db.session.commit.assert_called_once_with() |
534 | |
535 | - @patch('bileto.models.environ', ENVIRON) |
536 | - def test_request_summarize(self): |
537 | - """Display a handy summary.""" |
538 | - req = Request( |
539 | - dest=EMPTY, |
540 | - landers='jimbo', |
541 | - merge_proposals='http://foo/merge', |
542 | - request_id=5, |
543 | - series='yakkety+xenial+vivid', |
544 | - sources='qtmir qtubuntu', |
545 | - ) |
546 | - self.assertEqual( |
547 | - req.summarize(), |
548 | - 'Packages:\n' |
549 | - 'https://launchpad.net/ubuntu/+source/qtmir\n' |
550 | - 'https://launchpad.net/ubuntu/+source/qtubuntu\n\n' |
551 | - 'More info:\n' |
552 | - 'http://example.com/#/ticket/5\n') |
553 | - self.assertEqual(req.merge_proposals_list, ['http://foo/merge']) |
554 | - |
555 | def test_request_validate(self): |
556 | """Prevent invalid requests from being built.""" |
557 | req = Request( |
558 | |
559 | === modified file 'tests/test_worker_manager.py' |
560 | --- tests/test_worker_manager.py 2016-06-14 06:59:01 +0000 |
561 | +++ tests/test_worker_manager.py 2016-06-16 06:25:31 +0000 |
562 | @@ -3,15 +3,18 @@ |
563 | Test the package manager class. |
564 | """ |
565 | |
566 | +from os.path import join |
567 | +from subprocess import PIPE |
568 | from asyncio import coroutine |
569 | from collections import defaultdict |
570 | from unittest.mock import Mock, patch, call |
571 | |
572 | -from lazr.restfulclient.errors import BadRequest |
573 | +# from lazr.restfulclient.errors import BadRequest |
574 | |
575 | from tests.tests import WorkerTestCase, fake_subproc |
576 | |
577 | from bileto.lplib import lp |
578 | +from bileto.settings import Config |
579 | from bileto.models import BiletoError |
580 | from bileto.worker.manager import Manager, Merge, Manual, Package |
581 | from bileto.worker.log import log |
582 | @@ -20,8 +23,11 @@ |
583 | DIFF_ENV = dict(INCLUDES='') |
584 | MAKE_PPA_ENV = dict(REQUEST_URL_ROOT='https://ppa/thing') |
585 | MERGES = fake_subproc(0, [(b'ofono', b'')] * 5) |
586 | +POST_DIFF = fake_subproc(0, [(b'swift/diff (9000 lines)', b'')]) |
587 | +POST_NUKE = fake_subproc(0, [(b'', b'')]) |
588 | MOCKS = defaultdict(Mock) |
589 | SLASH = '/' |
590 | +EMPTY = '' |
591 | N = '\n' |
592 | |
593 | |
594 | @@ -211,10 +217,46 @@ |
595 | ticket = Mock() |
596 | manager = Manager(ticket) |
597 | manager.do_the_stuff() |
598 | - package.pre_do_the_stuff.assert_called_once_with(ticket) |
599 | - package.post_do_the_stuff.assert_called_once_with(ticket) |
600 | self.assertEqual(called, [True, True]) |
601 | |
602 | + @patch('bileto.worker.manager.get') |
603 | + @patch('bileto.worker.manager.lp') |
604 | + def test_manager_assign(self, lp_mock, get): |
605 | + """Assign silos.""" |
606 | + lp_mock.ppa_team.ppas = [Mock(distribution_link='/ubuntu')] |
607 | + lp_mock.ppa_team.ppas[0].name = 'landing-987' |
608 | + get.return_value.json.return_value = dict( |
609 | + requests=[dict(siloname='ubuntu/landing-123')]) |
610 | + ticket = Mock(siloname=None) |
611 | + manager = Manager(ticket) |
612 | + manager.assign() |
613 | + ticket.siloname = 'yes' |
614 | + manager.assign() |
615 | + ticket.set_status.assert_called_once_with( |
616 | + 'Ready to build', siloname='ubuntu/landing-987') |
617 | + |
618 | + @patch('bileto.worker.manager.get') |
619 | + @patch('bileto.worker.manager.lp') |
620 | + def test_manager_assign_fail(self, lp_mock, get): |
621 | + """Fail correctly when no silos available.""" |
622 | + lp_mock.ppa_team.ppas = [Mock(distribution_link='/ubuntu')] |
623 | + lp_mock.ppa_team.ppas[0].name = 'landing-987' |
624 | + get.return_value.json.return_value = dict( |
625 | + requests=[dict(siloname='ubuntu/landing-987')]) |
626 | + ticket = Mock(siloname=None) |
627 | + manager = Manager(ticket) |
628 | + with self.assertRaisesRegex(BiletoError, 'No silos'): |
629 | + manager.assign() |
630 | + |
631 | + def test_manager_validate(self): |
632 | + """Do global validation.""" |
633 | + ticket = Mock() |
634 | + manager = Manager(ticket) |
635 | + manager.validate() |
636 | + self.assertEqual(ticket.mock_calls, [ |
637 | + call.validate(), |
638 | + ]) |
639 | + |
640 | @patch('bileto.worker.manager.Manager.__getattr__') |
641 | def test_manager_upload(self, getattr_mock): |
642 | """Prevent cancellation after uploading.""" |
643 | @@ -228,6 +270,7 @@ |
644 | @patch('bileto.worker.manager.Package') |
645 | @patch('bileto.worker.manager.Manager.packages') |
646 | @patch('bileto.worker.manager.environ', DIFF_ENV) |
647 | + @patch('bileto.worker.vcs.create_subprocess_exec', POST_DIFF) |
648 | def test_manager_diff(self, packages, package): |
649 | """Force diffing against all packages even if not all were built.""" |
650 | called = [] |
651 | @@ -239,54 +282,77 @@ |
652 | instance = Mock() |
653 | instance.diff = appender |
654 | packages.return_value = dict(blip=instance, glorp=instance) |
655 | - ticket = Mock(sources='blip glorp') |
656 | + ticket = Mock(sources='blip glorp', request_id=5) |
657 | manager = Manager(ticket) |
658 | manager.diff() |
659 | - package.pre_diff.assert_called_once_with(ticket) |
660 | - package.post_diff.assert_called_once_with(ticket) |
661 | self.assertEqual(called, [True, True]) |
662 | - self.assertEqual(DIFF_ENV, dict(INCLUDES='blip glorp')) |
663 | + self.assertEqual(DIFF_ENV['INCLUDES'], 'blip glorp') |
664 | packages.cache_clear.assert_called_once_with() |
665 | - |
666 | - @patch('bileto.worker.manager.lp') |
667 | - def test_manager_nuke(self, lp_mock): |
668 | - """Delete ephemeral PPA during nuke phase.""" |
669 | - ticket = Mock(request_id='5') |
670 | - manager = Manager(ticket) |
671 | - manager.nuke() |
672 | - self.assertEqual( |
673 | - str(lp_mock.mock_calls), |
674 | - "[call.ppa_team.getPPAByName(name='05'),\n" |
675 | - " call.ppa_team.getPPAByName().web_link.__str__(),\n" |
676 | - " call.ppa_team.getPPAByName().lp_delete()]") |
677 | - |
678 | - @patch('bileto.worker.manager.lp') |
679 | - def test_manager_nuke_again(self, lp_mock): |
680 | - """Don't explode if the same PPA is nuked twice in a row.""" |
681 | - lp_mock.ppa_team.getPPAByName.return_value.lp_delete.side_effect = ( |
682 | - BadRequest(b'', b'Archive already deleted')) |
683 | - ticket = Mock(request_id='5') |
684 | - manager = Manager(ticket) |
685 | - manager.nuke() |
686 | - self.assertEqual( |
687 | - str(lp_mock.mock_calls), |
688 | - "[call.ppa_team.getPPAByName(name='05'),\n" |
689 | - " call.ppa_team.getPPAByName().web_link.__str__(),\n" |
690 | - " call.ppa_team.getPPAByName().lp_delete()]") |
691 | - |
692 | - @patch('bileto.worker.manager.lp') |
693 | - def test_manager_nuke_invalid(self, lp_mock): |
694 | - """Don't explode if a non-existing PPA is nuked.""" |
695 | - lp_mock.ppa_team.getPPAByName.return_value.lp_delete.side_effect = ( |
696 | - AttributeError) |
697 | - ticket = Mock(request_id='5') |
698 | - manager = Manager(ticket) |
699 | - manager.nuke() |
700 | - self.assertEqual( |
701 | - str(lp_mock.mock_calls), |
702 | - "[call.ppa_team.getPPAByName(name='05'),\n" |
703 | - " call.ppa_team.getPPAByName().web_link.__str__(),\n" |
704 | - " call.ppa_team.getPPAByName().lp_delete()]") |
705 | + env = POST_DIFF.mock_calls[0][2]['env'] |
706 | + self.assertEqual(env['ACTION'], 'swift_upload') |
707 | + self.assertSequenceEqual(POST_DIFF.mock_calls, [ |
708 | + call(join(Config.ROOT, 'scripts', 'vcs.sh'), |
709 | + env=env, stdout=PIPE, stderr=PIPE), |
710 | + ]) |
711 | + self.assertEqual(ticket.mock_calls, [ |
712 | + call.commit(), |
713 | + ]) |
714 | + self.assertEqual(ticket.artifacts, 'swift/diff (9000 lines)') |
715 | + |
716 | + @patch('bileto.worker.manager.Manager.ppa') |
717 | + @patch('bileto.worker.vcs.create_subprocess_exec', POST_NUKE) |
718 | + def test_manager_nuke(self, ppa): |
719 | + """Empty PPA when freeing.""" |
720 | + source = Mock(status='Published') |
721 | + ppa.getPublishedSources.return_value = [source] |
722 | + ticket = Mock(request_id='5') |
723 | + manager = Manager(ticket) |
724 | + manager.nuke('From Orbit') |
725 | + source.requestDeletion.assert_called_once_with() |
726 | + self.assertEqual(ticket.mock_calls, [ |
727 | + call.set_status('From Orbit', siloname=EMPTY, artifacts=EMPTY), |
728 | + call.commit(), |
729 | + ]) |
730 | + |
731 | + # @patch('bileto.worker.manager.lp') |
732 | + # def test_manager_nuke(self, lp_mock): |
733 | + # """Delete ephemeral PPA during nuke phase.""" |
734 | + # ticket = Mock(request_id='5') |
735 | + # manager = Manager(ticket) |
736 | + # manager.nuke() |
737 | + # self.assertEqual( |
738 | + # str(lp_mock.mock_calls), |
739 | + # "[call.ppa_team.getPPAByName(name='05'),\n" |
740 | + # " call.ppa_team.getPPAByName().web_link.__str__(),\n" |
741 | + # " call.ppa_team.getPPAByName().lp_delete()]") |
742 | + # |
743 | + # @patch('bileto.worker.manager.lp') |
744 | + # def test_manager_nuke_again(self, lp_mock): |
745 | + # """Don't explode if the same PPA is nuked twice in a row.""" |
746 | + # lp_mock.ppa_team.getPPAByName.return_value.lp_delete.side_effect = ( |
747 | + # BadRequest(b'', b'Archive already deleted')) |
748 | + # ticket = Mock(request_id='5') |
749 | + # manager = Manager(ticket) |
750 | + # manager.nuke() |
751 | + # self.assertEqual( |
752 | + # str(lp_mock.mock_calls), |
753 | + # "[call.ppa_team.getPPAByName(name='05'),\n" |
754 | + # " call.ppa_team.getPPAByName().web_link.__str__(),\n" |
755 | + # " call.ppa_team.getPPAByName().lp_delete()]") |
756 | + |
757 | + # @patch('bileto.worker.manager.lp') |
758 | + # def test_manager_nuke_invalid(self, lp_mock): |
759 | + # """Don't explode if a non-existing PPA is nuked.""" |
760 | + # lp_mock.ppa_team.getPPAByName.return_value.lp_delete.side_effect = ( |
761 | + # AttributeError) |
762 | + # ticket = Mock(request_id='5') |
763 | + # manager = Manager(ticket) |
764 | + # manager.nuke() |
765 | + # self.assertEqual( |
766 | + # str(lp_mock.mock_calls), |
767 | + # "[call.ppa_team.getPPAByName(name='05'),\n" |
768 | + # " call.ppa_team.getPPAByName().web_link.__str__(),\n" |
769 | + # " call.ppa_team.getPPAByName().lp_delete()]") |
770 | |
771 | @patch('bileto.worker.manager.lp') |
772 | def test_manager_ppa_ephemeral(self, lp_mock): |
773 | |
774 | === modified file 'tests/test_worker_merge.py' |
775 | --- tests/test_worker_merge.py 2016-06-15 06:14:03 +0000 |
776 | +++ tests/test_worker_merge.py 2016-06-16 06:25:31 +0000 |
777 | @@ -31,6 +31,8 @@ |
778 | (b'Jimmy <j@bob.com>, Sally <s@lly.com>, Suzy <su@zy.com>, ', b''), STD, |
779 | ]) |
780 | EXPOSE = fake_subproc(0, [STD]) |
781 | +MERGE = fake_subproc(0, [STD, STD]) |
782 | +PUSH = fake_subproc(0, [STD]) |
783 | |
784 | |
785 | class MergeTestCase(WorkerTestCase): |
786 | @@ -151,3 +153,39 @@ |
787 | 'lp:~/ofono/ofono-ubuntu-xenial-12321', |
788 | env=env, stdout=PIPE, stderr=PIPE), |
789 | ]) |
790 | + |
791 | + @patch('bileto.worker.vcs.create_subprocess_exec', MERGE) |
792 | + def test_merge_merge(self): |
793 | + """Merge trunks back into workdir.""" |
794 | + Merge.all_mps = dict(ofono=[self.mp]) |
795 | + merge = Merge('ofono', self.distro, self.series, self.dest, self.ppa) |
796 | + self.run_coro(merge.merge()) |
797 | + env1 = MERGE.mock_calls[0][2]['env'] |
798 | + self.assertEqual(env1['SOURCE'], 'ofono') |
799 | + self.assertEqual(env1['SERIES'], 'xenial') |
800 | + self.assertEqual(env1['ACTION'], 'fetch_work_branch') |
801 | + env2 = MERGE.mock_calls[1][2]['env'] |
802 | + self.assertEqual(env2['ACTION'], 'merge_trunk') |
803 | + self.assertSequenceEqual(MERGE.mock_calls, [ |
804 | + call(join(Config.ROOT, 'scripts', 'vcs.sh'), |
805 | + 'lp:~/ofono/ofono-ubuntu-xenial-12321', |
806 | + env=env1, stdout=PIPE, stderr=PIPE), |
807 | + call(join(Config.ROOT, 'scripts', 'vcs.sh'), |
808 | + 'lp:ofono', |
809 | + env=env2, stdout=PIPE, stderr=PIPE), |
810 | + ]) |
811 | + |
812 | + @patch('bileto.worker.vcs.create_subprocess_exec', PUSH) |
813 | + def test_merge_push(self): |
814 | + """Push to trunks.""" |
815 | + Merge.all_mps = dict(ofono=[self.mp]) |
816 | + merge = Merge('ofono', self.distro, self.series, self.dest, self.ppa) |
817 | + self.run_coro(merge.push()) |
818 | + env = PUSH.mock_calls[0][2]['env'] |
819 | + self.assertEqual(env['SOURCE'], 'ofono') |
820 | + self.assertEqual(env['SERIES'], 'xenial') |
821 | + self.assertEqual(env['ACTION'], 'push_to_trunk') |
822 | + self.assertSequenceEqual(PUSH.mock_calls, [ |
823 | + call(join(Config.ROOT, 'scripts', 'vcs.sh'), |
824 | + 'lp:ofono', env=env, stdout=PIPE, stderr=PIPE), |
825 | + ]) |
826 | |
827 | === modified file 'tests/test_worker_package.py' |
828 | --- tests/test_worker_package.py 2016-06-04 00:33:30 +0000 |
829 | +++ tests/test_worker_package.py 2016-06-16 06:25:31 +0000 |
830 | @@ -12,26 +12,15 @@ |
831 | from bileto.settings import Config |
832 | from bileto.worker.package import Package |
833 | |
834 | - |
835 | +EMPTY = '' |
836 | BUILD = fake_subproc(0, [(b'', b'')]) |
837 | UPLOAD = fake_subproc(0, [(b'', b'')]) |
838 | DIFF = fake_subproc(0, [(b'', b''), (b'', b'')]) |
839 | -POST_DIFF = fake_subproc(0, [(b'swift/diff (9000 lines)', b'')]) |
840 | |
841 | |
842 | class PackageTestCase(WorkerTestCase): |
843 | """Test Packages.""" |
844 | |
845 | - def test_package_post_validate(self): |
846 | - """Do global validation steps.""" |
847 | - ticket = Mock() |
848 | - package = Package('foo', self.distro, self.series, self.dest, self.ppa) |
849 | - package.post_validate(ticket) |
850 | - self.assertEqual(ticket.mock_calls, [ |
851 | - call.validate(), |
852 | - call.summarize(), |
853 | - ]) |
854 | - |
855 | @patch('bileto.worker.vcs.create_subprocess_exec', BUILD) |
856 | def test_package_build(self): |
857 | """Invoke package building.""" |
858 | @@ -101,28 +90,3 @@ |
859 | call(source_name='dif', distro_series=self.series, |
860 | exact_match=True, order_by_date=True), |
861 | ]) |
862 | - |
863 | - @patch('bileto.worker.vcs.create_subprocess_exec', POST_DIFF) |
864 | - def test_package_post_diff(self): |
865 | - """Invoke swift diff uploading.""" |
866 | - ticket = Mock(request_id='5') |
867 | - Package.post_diff(ticket) |
868 | - env = POST_DIFF.mock_calls[0][2]['env'] |
869 | - self.assertEqual(env['REQUESTID'], '5') |
870 | - self.assertEqual(env['ACTION'], 'swift_upload') |
871 | - self.assertSequenceEqual(POST_DIFF.mock_calls, [ |
872 | - call(join(Config.ROOT, 'scripts', 'vcs.sh'), |
873 | - env=env, stdout=PIPE, stderr=PIPE), |
874 | - ]) |
875 | - self.assertEqual(ticket.mock_calls, [ |
876 | - call.commit(), |
877 | - ]) |
878 | - self.assertEqual(ticket.artifacts, 'swift/diff (9000 lines)') |
879 | - |
880 | - def test_package_post_abandon(self): |
881 | - """Set abandonment status.""" |
882 | - ticket = Mock(request_id='5') |
883 | - Package.post_abandon(ticket) |
884 | - self.assertEqual(ticket.mock_calls, [ |
885 | - call.set_status('Abandoned'), |
886 | - ]) |