Merge lp:~robru/bileto/finalize into lp:~ci-train-staging-area/bileto/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
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.

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- ])

Subscribers

People subscribed via source and target branches