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
=== modified file 'bileto/actions.py'
--- bileto/actions.py 2016-06-04 00:33:30 +0000
+++ bileto/actions.py 2016-06-16 06:25:31 +0000
@@ -7,6 +7,7 @@
7class Actions:7class Actions:
8 """Define what actions can be taken on a ticket."""8 """Define what actions can be taken on a ticket."""
9 statuses = dict(9 statuses = dict(
10 assign='Assigning PPA...',
10 status='Updating status.',11 status='Updating status.',
11 build='Preparing packages.',12 build='Preparing packages.',
12 diff='Generating diffs.',13 diff='Generating diffs.',
@@ -16,6 +17,11 @@
16 )17 )
1718
18 @staticmethod19 @staticmethod
20 def assign(manager):
21 """Assign a PPA to this Ticket"""
22 manager.assign()
23
24 @staticmethod
19 def status(manager):25 def status(manager):
20 """Update Package States"""26 """Update Package States"""
21 manager.status()27 manager.status()
@@ -39,9 +45,9 @@
39 @staticmethod45 @staticmethod
40 def finalize(manager):46 def finalize(manager):
41 """Merge to Trunk"""47 """Merge to Trunk"""
42 manager.merge().push().nuke()48 manager.merge().push().nuke('Landed')
4349
44 @staticmethod50 @staticmethod
45 def abandon(manager):51 def abandon(manager):
46 """Delete PPA"""52 """Give Up PPA, Close Ticket"""
47 manager.abandon().nuke()53 manager.nuke('Abandoned')
4854
=== modified file 'bileto/models.py'
--- bileto/models.py 2016-06-10 21:49:43 +0000
+++ bileto/models.py 2016-06-16 06:25:31 +0000
@@ -114,13 +114,6 @@
114114
115Link to a document that explains the steps to verify this silo."""115Link to a document that explains the steps to verify this silo."""
116116
117SUMMARY = """Packages:
118{packages}
119
120More info:
121{REQUEST_URL_ROOT}{permalink}
122"""
123
124COLORS = dict(117COLORS = dict(
125 blue={'granted'},118 blue={'granted'},
126 green={'landed'},119 green={'landed'},
@@ -264,17 +257,6 @@
264 if self.series == 'xenial+vivid':257 if self.series == 'xenial+vivid':
265 raise BiletoError(MIGRATE_TO_YAKKETY)258 raise BiletoError(MIGRATE_TO_YAKKETY)
266259
267 def summarize(self):
268 """Return a text summary of this ticket."""
269 return SUMMARY.format(
270 packages=N.join(
271 'https://launchpad.net/ubuntu/+source/' + source
272 for source in self.sources_sorted),
273 requestid=self.request_id,
274 permalink=self.path,
275 **environ # Trailing comma is syntax error in py3.4
276 )
277
278 def publishable(self):260 def publishable(self):
279 """Identify if this request is appropriate to publish or not."""261 """Identify if this request is appropriate to publish or not."""
280 if self.status in HIDDEN:262 if self.status in HIDDEN:
@@ -378,13 +360,14 @@
378 self.artifacts = EMPTY360 self.artifacts = EMPTY
379 self.autopkgtest = EMPTY361 self.autopkgtest = EMPTY
380362
381 def set_status(self, message):363 def set_status(self, message, **kwargs):
382 """Update status immediately."""364 """Update status immediately."""
383 self.update(365 self.update(
384 status=message,366 status=message,
385 job_log=environ.get('LOG_PATH') or367 job_log=environ.get('LOG_PATH') or
386 environ.get('REQUEST_PATH') or368 environ.get('REQUEST_PATH') or
387 request.path)369 request.path,
370 **kwargs)
388 self.commit()371 self.commit()
389 return message372 return message
390373
@@ -398,12 +381,10 @@
398 @lru_cache()381 @lru_cache()
399 def lock_fd(self):382 def lock_fd(self):
400 """Return the file descriptor that we lock on."""383 """Return the file descriptor that we lock on."""
401 print(self.lockfile)
402 return open(self.lockfile, 'w')384 return open(self.lockfile, 'w')
403385
404 def lock(self):386 def lock(self):
405 """Prevent multiple jobs from running simultaneously."""387 """Prevent multiple jobs from running simultaneously."""
406 print(self.lock_fd)
407 flock(self.lock_fd, LOCK_EX | LOCK_NB)388 flock(self.lock_fd, LOCK_EX | LOCK_NB)
408389
409 # TODO: not needed yet390 # TODO: not needed yet
410391
=== modified file 'bileto/static/index.html'
--- bileto/static/index.html 2016-06-14 09:25:57 +0000
+++ bileto/static/index.html 2016-06-16 06:25:31 +0000
@@ -224,12 +224,12 @@
224224
225<div class="actionbox" ng-if="req.request_id && expand">225<div class="actionbox" ng-if="req.request_id && expand">
226<table class="actions"><tr>226<table class="actions"><tr>
227<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>227<td ng-if="!editing"><a href="/log/{{req.request_id}}/assign" target="_blank">Assign</a></td>
228<td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/build" target="_blank">Build</a></td>228<td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/build" target="_blank">Build</a></td>
229<td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/diff" target="_blank">Diff</a></td>229<td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/diff" target="_blank">Diff</a></td>
230<td ng-if="req.siloname && !editing"><a href="{{meta.jenkins}}/job/{{req.siloname.replace('/', '-')}}-2-publish/build" target="_blank">Publish</a></td>230<td ng-if="req.siloname && !editing"><a href="{{meta.jenkins}}/job/{{req.siloname.replace('/', '-')}}-2-publish/build" target="_blank">Publish</a></td>
231<td ng-if="req.siloname && !editing"><a href="{{meta.jenkins}}/job/{{req.siloname.replace('/', '-')}}-3-merge-clean/build" target="_blank">Merge</a></td>231<td ng-if="req.siloname && !editing"><a href="/log/{{req.request_id}}/finalize" target="_blank">Finalize</a></td>
232<td><a href="{{meta.jenkins}}/securityRealm/commenceLogin?from=/job/abandon/parambuild%3FREQUEST_ID%3D{{req.request_id}}" target="_blank">Abandon</a></td>232<td><a href="/log/{{req.request_id}}/abandon" target="_blank">Abandon</a></td>
233<td ng-if="editing"><a ng-click="cancel()">Cancel Edit</a></td>233<td ng-if="editing"><a ng-click="cancel()">Cancel Edit</a></td>
234<td ng-if="!editing"><a ng-click="edit(req)">Edit</a></td>234<td ng-if="!editing"><a ng-click="edit(req)">Edit</a></td>
235</tr></table>235</tr></table>
236236
=== modified file 'bileto/streams.py'
--- bileto/streams.py 2016-06-14 09:21:38 +0000
+++ bileto/streams.py 2016-06-16 06:25:31 +0000
@@ -78,7 +78,7 @@
7878
79def assert_action(action):79def assert_action(action):
80 """Abort if action is invalid."""80 """Abort if action is invalid."""
81 if action not in ('build', 'diff'):81 if action not in ('assign', 'build', 'diff', 'finalize', 'abandon'):
82 abort(404)82 abort(404)
8383
8484
8585
=== modified file 'bileto/worker/manager.py'
--- bileto/worker/manager.py 2016-06-14 11:51:01 +0000
+++ bileto/worker/manager.py 2016-06-16 06:25:31 +0000
@@ -11,11 +11,15 @@
11"""11"""
1212
13from os import environ13from os import environ
14from os.path import join
15
14from contextlib import suppress16from contextlib import suppress
15from functools import lru_cache17from functools import lru_cache
18from fcntl import flock, LOCK_EX
16from collections import defaultdict19from collections import defaultdict
1720
18from lazr.restfulclient.errors import BadRequest, NotFound21from requests import get
22from lazr.restfulclient.errors import NotFound
1923
20from bileto.lplib import lp24from bileto.lplib import lp
21from bileto.settings import Config25from bileto.settings import Config
@@ -25,10 +29,15 @@
25from bileto.worker.gles import GLES29from bileto.worker.gles import GLES
26from bileto.worker.merge import Merge30from bileto.worker.merge import Merge
27from bileto.worker.manual import Manual31from bileto.worker.manual import Manual
28from bileto.worker.vcs import parallelize32from bileto.worker.vcs import VCS, parallelize
29from bileto.worker.package import Package33from bileto.worker.package import Package
30from bileto.worker.secondary import Secondary34from bileto.worker.secondary import Secondary
3135
36API_PREFIX = 'http://127.0.0.1:8080/v1/tickets?'
37READ_API = API_PREFIX + 'active_only&siloname=land'
38
39UNAVAILABLE = """No silos available! Please ask your friendly neighborhood \
40trainguard to free some."""
32MERGE_ORDER = """Merges have been sorted by prerequisite \41MERGE_ORDER = """Merges have been sorted by prerequisite \
33and will be merged in this order:42and will be merged in this order:
34%s43%s
@@ -59,6 +68,37 @@
59 log.info('Packages targetting %s:\n%s\n', dest, N.join(names))68 log.info('Packages targetting %s:\n%s\n', dest, N.join(names))
6069
6170
71def discover_ppas():
72 """Discover what PPAs we have."""
73 for ppa in lp.ppa_team.ppas:
74 dist = ppa.distribution_link.split('/')[-1]
75 siloname = '{}/{}'.format(dist, ppa.name)
76 if '/landing-' in siloname and '-deleted' not in siloname:
77 yield siloname
78
79
80def choose_available_ppa():
81 """Randomly choose one available PPA for assignment."""
82 silonames = set(discover_ppas())
83 resp = get(READ_API).json()
84 assigned = {ticket['siloname'] for ticket in resp.get('requests', [])}
85 available = silonames - assigned
86 if not available:
87 raise BiletoError(UNAVAILABLE)
88 return available.pop()
89
90
91def set_swift_creds():
92 """Ensure swift creds are set in environment variables."""
93 environ.update(
94 OS_REGION_NAME=Config.os_region_name,
95 OS_TENANT_NAME=Config.os_tenant_name,
96 OS_AUTH_URL=Config.os_auth_url,
97 OS_USERNAME=Config.os_username,
98 OS_PASSWORD=Config.os_password,
99 )
100
101
62class Manager:102class Manager:
63 """Manage the instantiation of Package objects."""103 """Manage the instantiation of Package objects."""
64 def __init__(self, ticket):104 def __init__(self, ticket):
@@ -134,13 +174,28 @@
134 """Pass arguments down to methods."""174 """Pass arguments down to methods."""
135 log.abort_if_cancelled()175 log.abort_if_cancelled()
136 log.debug('Beginning step: {}'.format(attr.upper()))176 log.debug('Beginning step: {}'.format(attr.upper()))
137 getattr(Package, 'pre_' + attr, NOP)(self.ticket)
138 parallelize([getattr(package, attr)(*args, **kwargs)177 parallelize([getattr(package, attr)(*args, **kwargs)
139 for name, package in self.packages().items()])178 for name, package in self.packages().items()])
140 getattr(Package, 'post_' + attr, NOP)(self.ticket)
141 return self179 return self
142 return passthrough180 return passthrough
143181
182 def assign(self):
183 """Assign a PPA to this ticket, if possible."""
184 with open(join(Config.HOME, 'bileto-assign-lock'), 'w') as lock:
185 flock(lock, LOCK_EX)
186 if not self.ticket.siloname:
187 siloname = choose_available_ppa()
188 log.info('This ticket has been assigned %s.', siloname)
189 self.ticket.set_status('Ready to build', siloname=siloname)
190 self.ticket.commit()
191 else:
192 log.info('This ticket is already assigned! Just build it!')
193
194 def validate(self):
195 """Halt job if ticket has invalid parameters."""
196 self.ticket.validate()
197 return self
198
144 def upload(self):199 def upload(self):
145 """Prevent cancellation once uploading begins."""200 """Prevent cancellation once uploading begins."""
146 log.cancellable = False201 log.cancellable = False
@@ -152,12 +207,26 @@
152 environ.update(INCLUDES=self.ticket.sources)207 environ.update(INCLUDES=self.ticket.sources)
153 for func in (Manager.names, Manager.types, Manager.packages):208 for func in (Manager.names, Manager.types, Manager.packages):
154 func.cache_clear()209 func.cache_clear()
155 return self.__getattr__('diff')()210 self.__getattr__('diff')()
211 set_swift_creds()
212 stdout = parallelize([VCS.swift_upload()])[0]
213 log.info('Uploaded diffs:\n%s', stdout)
214 self.ticket.artifacts = stdout
215 self.ticket.commit()
156216
157 def nuke(self):217 def nuke(self, status):
158 """Delete PPA."""218 """Delete PPA contents."""
159 self.delete_ppa(self.ticket.request_id)219 # TODO: Eventually delete ephemeral PPAs here
160 # TODO: Delete swift container220 # self.delete_ppa(self.ticket.request_id)
221 with suppress(BiletoError):
222 for source in self.ppa.getPublishedSources():
223 if source.status != 'Deleted':
224 log.info('Deleting %s ppa.', source.display_name)
225 source.requestDeletion()
226 set_swift_creds()
227 parallelize([VCS.delete_swift()])
228 self.ticket.set_status(status, siloname=EMPTY, artifacts=EMPTY)
229 self.ticket.commit()
161230
162 @property231 @property
163 @lru_cache()232 @lru_cache()
@@ -231,13 +300,14 @@
231 # TODO: Needs to be devirted and all arches enabled.300 # TODO: Needs to be devirted and all arches enabled.
232 return self.get_ppa(number)301 return self.get_ppa(number)
233302
234 def delete_ppa(self, number):303 # TODO: use this for ephemeral PPAs eventually
235 """Unconditionally delete an ephemeral PPA."""304 # def delete_ppa(self, number):
236 log.info('Deleting PPA %s.', number)305 # """Unconditionally delete an ephemeral PPA."""
237 try:306 # log.info('Deleting PPA %s.', number)
238 self.get_ppa(name_ppa(number)).lp_delete()307 # try:
239 except BadRequest as err:308 # self.get_ppa(name_ppa(number)).lp_delete()
240 # Will log 'Archive already deleted.' if deleted but not gone yet309 # except BadRequest as err:
241 log.warning(err.content.decode('utf-8'))310 # # Will log 'Archive already deleted.' if deleted but not gone yet
242 except AttributeError:311 # log.warning(err.content.decode('utf-8'))
243 pass # already gone for real312 # except AttributeError:
313 # pass # already gone for real
244314
=== modified file 'bileto/worker/merge.py'
--- bileto/worker/merge.py 2016-06-15 06:14:03 +0000
+++ bileto/worker/merge.py 2016-06-16 06:25:31 +0000
@@ -175,3 +175,14 @@
175 def expose(self):175 def expose(self):
176 """Push our locally built branch to launchpad."""176 """Push our locally built branch to launchpad."""
177 yield from VCS.expose(self.lp_branch, **self.env)177 yield from VCS.expose(self.lp_branch, **self.env)
178
179 @coroutine
180 def merge(self):
181 """Merge target trunk back into our work branch."""
182 yield from VCS.fetch_work_branch(self.lp_branch, **self.env)
183 yield from VCS.merge_trunk(self.target, **self.env)
184
185 @coroutine
186 def push(self):
187 """Push our final release to target trunk."""
188 yield from VCS.push_to_trunk(self.target, **self.env)
178189
=== modified file 'bileto/worker/package.py'
--- bileto/worker/package.py 2016-06-13 11:58:39 +0000
+++ bileto/worker/package.py 2016-06-16 06:25:31 +0000
@@ -3,16 +3,16 @@
3Defines steps common to all types of packages that we handle.3Defines steps common to all types of packages that we handle.
4"""4"""
55
6from os import environ
7from os.path import join6from os.path import join
7from asyncio import coroutine
8from contextlib import suppress8from contextlib import suppress
9from asyncio import coroutine, get_event_loop
109
11from bileto.settings import Config10from bileto.settings import Config
1211
13from bileto.worker.log import log
14from bileto.worker.vcs import VCS12from bileto.worker.vcs import VCS
1513
14EMPTY = ''
15
1616
17class Package:17class Package:
18 """Represent the steps necessary to build & publish a package."""18 """Represent the steps necessary to build & publish a package."""
@@ -31,16 +31,6 @@
31 self.instances[join(series.name, name)] = self31 self.instances[join(series.name, name)] = self
3232
33 @coroutine33 @coroutine
34 def validate(self):
35 """NOP; Validation handled globally."""
36
37 @staticmethod
38 def post_validate(ticket):
39 """Report ticket summary."""
40 ticket.validate()
41 log.info(ticket.summarize())
42
43 @coroutine
44 def build(self):34 def build(self):
45 """Build source package."""35 """Build source package."""
46 yield from VCS.build(**self.env)36 yield from VCS.build(**self.env)
@@ -91,25 +81,10 @@
91 self.dest.distribution.main_archive))81 self.dest.distribution.main_archive))
92 yield from VCS.diff_dest(DSC_URL=dest_dsc, **self.env)82 yield from VCS.diff_dest(DSC_URL=dest_dsc, **self.env)
9383
94 @staticmethod84 @coroutine
95 def post_diff(ticket):85 def merge(self):
96 """Upload all diffs to swift after they are all generated."""86 """NOP; implemented by subclasses."""
97 environ.update(
98 OS_REGION_NAME=Config.os_region_name,
99 OS_TENANT_NAME=Config.os_tenant_name,
100 OS_AUTH_URL=Config.os_auth_url,
101 OS_USERNAME=Config.os_username,
102 OS_PASSWORD=Config.os_password,
103 )
104 stdout = get_event_loop().run_until_complete(VCS.swift_upload(
105 # Don't put creds here as they'll leak into debug logs.
106 REQUESTID=str(ticket.request_id),
107 ))
108 log.info('Uploaded diffs:\n%s', stdout)
109 ticket.artifacts = stdout
110 ticket.commit()
11187
112 @staticmethod88 @coroutine
113 def post_abandon(ticket):89 def push(self):
114 """Deactivate ticket."""90 """NOP; implemented by subclasses."""
115 ticket.set_status('Abandoned')
11691
=== modified file 'bileto/worker/vcs.py'
--- bileto/worker/vcs.py 2016-06-14 09:23:00 +0000
+++ bileto/worker/vcs.py 2016-06-16 06:25:31 +0000
@@ -17,7 +17,7 @@
1717
18def parallelize(tasks):18def parallelize(tasks):
19 """Do each task in parallel with asyncio."""19 """Do each task in parallel with asyncio."""
20 get_event_loop().run_until_complete(gather(*tasks))20 return get_event_loop().run_until_complete(gather(*tasks))
2121
2222
23class VCSError(BiletoError):23class VCSError(BiletoError):
2424
=== modified file 'debian/control'
--- debian/control 2015-12-13 01:11:10 +0000
+++ debian/control 2016-06-16 06:25:31 +0000
@@ -15,6 +15,7 @@
15 python3-nose2,15 python3-nose2,
16 python3-pep8,16 python3-pep8,
17 python3-pyflakes | pyflakes,17 python3-pyflakes | pyflakes,
18 python3-requests,
18 python3-sqlalchemy,19 python3-sqlalchemy,
19Standards-Version: 3.9.620Standards-Version: 3.9.6
20Homepage: https://launchpad.net/bileto21Homepage: https://launchpad.net/bileto
2122
=== modified file 'scripts/vcs.sh'
--- scripts/vcs.sh 2016-06-13 11:39:56 +0000
+++ scripts/vcs.sh 2016-06-16 06:25:31 +0000
@@ -20,6 +20,7 @@
20WORKSPACE="$(pwd)"20WORKSPACE="$(pwd)"
21WORKPARENT="$WORKSPACE/$SERIES/$SOURCE"21WORKPARENT="$WORKSPACE/$SERIES/$SOURCE"
22WORKDIR="$WORKPARENT/$SOURCE"22WORKDIR="$WORKPARENT/$SOURCE"
23SWIFT_CONTAINER="bileto-$REQUESTID"
23[ -d "$WORKDIR" ] && cd "$WORKDIR" || true24[ -d "$WORKDIR" ] && cd "$WORKDIR" || true
24IFS='25IFS='
25'26'
@@ -210,22 +211,24 @@
210 root="https://objectstorage.prodstack4-5.canonical.com/v1"211 root="https://objectstorage.prodstack4-5.canonical.com/v1"
211 auth="$(swift stat -v | sed -n 's/^\s\+Account:\s\+//p')"212 auth="$(swift stat -v | sed -n 's/^\s\+Account:\s\+//p')"
212 timestamp="$(date --utc '+%Y-%m-%d_%H:%M:%S')"213 timestamp="$(date --utc '+%Y-%m-%d_%H:%M:%S')"
213 container="bileto-$REQUESTID"
214 [ -n "$auth" ] || die "Failed to determine swift account. $retry"214 [ -n "$auth" ] || die "Failed to determine swift account. $retry"
215215
216 diffs=""216 diffs=""
217 for filename in */*/*.diff; do217 for filename in */*/*.diff; do
218 base="$(basename "$filename")"218 base="$(basename "$filename")"
219 confirmed="$(swift upload "$container" "$filename" --object-name="$timestamp/$base")" || die "Failed to upload diffs. $retry"219 confirmed="$(swift upload "$SWIFT_CONTAINER" "$filename" --object-name="$timestamp/$base")" || die "Failed to upload diffs. $retry"
220 size="$(wc --lines $filename | awk '{ print $1 }')"220 size="$(wc --lines $filename | awk '{ print $1 }')"
221 diffs="$diffs\n$root/$auth/$container/$confirmed ($size lines)"221 diffs="$diffs\n$root/$auth/$SWIFT_CONTAINER/$confirmed ($size lines)"
222 done222 done
223223
224 swift post "$container" --read-acl '.r:*,.rlistings' || die "Failed to publish diffs publicly. $retry"224 swift post "$SWIFT_CONTAINER" --read-acl '.r:*,.rlistings' || die "Failed to publish diffs publicly. $retry"
225 # shellcheck disable=SC2059225 # shellcheck disable=SC2059
226 printf "$diffs"226 printf "$diffs"
227}227}
228228
229do_delete_swift() {
230 loudly swift delete "$SWIFT_CONTAINER" || true
231}
229232
230################233################
231# BZR Commands #234# BZR Commands #
@@ -289,6 +292,19 @@
289 loudly bzr push "$BRANCH" --overwrite || die "Failed to push source tree to launchpad."292 loudly bzr push "$BRANCH" --overwrite || die "Failed to push source tree to launchpad."
290}293}
291294
295bzr_do_fetch_work_branch() {
296 loudly bzr branch "$BRANCH" "$WORKDIR"
297}
298
299bzr_do_merge_trunk() {
300 loudly bzr merge "$CACHE" || die "Failed to merge $BRANCH."
301 loudly bzr commit -m 'Resync trunk.' || true
302}
303
304bzr_do_push_to_trunk() {
305 loudly bzr push "$BRANCH" || die "Failed to push to $BRANCH. Check bot team membership and branch ownership."
306}
307
292308
293################309################
294# GIT Commands #310# GIT Commands #
295311
=== modified file 'tests/test_actions.py'
--- tests/test_actions.py 2016-04-14 21:39:20 +0000
+++ tests/test_actions.py 2016-06-16 06:25:31 +0000
@@ -13,6 +13,14 @@
13class MainTestCase(WorkerTestCase):13class MainTestCase(WorkerTestCase):
14 """Test Main Subprocess Script."""14 """Test Main Subprocess Script."""
1515
16 def test_actions_assign(self):
17 """Ensure assign calls correct methods."""
18 manager = Mock()
19 Actions.assign(manager)
20 self.assertSequenceEqual(manager.mock_calls, [
21 call.assign(),
22 ])
23
16 def test_actions_status(self):24 def test_actions_status(self):
17 """Ensure status calls correct methods."""25 """Ensure status calls correct methods."""
18 manager = Mock()26 manager = Mock()
@@ -51,7 +59,7 @@
51 self.assertSequenceEqual(manager.mock_calls, [59 self.assertSequenceEqual(manager.mock_calls, [
52 call.merge(),60 call.merge(),
53 call.merge().push(),61 call.merge().push(),
54 call.merge().push().nuke(),62 call.merge().push().nuke('Landed'),
55 ])63 ])
5664
57 def test_actions_abandon(self):65 def test_actions_abandon(self):
@@ -59,6 +67,5 @@
59 manager = Mock()67 manager = Mock()
60 Actions.abandon(manager)68 Actions.abandon(manager)
61 self.assertSequenceEqual(manager.mock_calls, [69 self.assertSequenceEqual(manager.mock_calls, [
62 call.abandon(),70 call.nuke('Abandoned'),
63 call.abandon().nuke(),
64 ])71 ])
6572
=== modified file 'tests/test_login.py'
--- tests/test_login.py 2016-02-16 21:33:16 +0000
+++ tests/test_login.py 2016-06-16 06:25:31 +0000
@@ -21,7 +21,7 @@
21 openid='test',21 openid='test',
22 next='/',22 next='/',
23 ), follow_redirects=False)23 ), follow_redirects=False)
24 self.assertIn(b'https://login.ubuntu.com/+openid', ret.data)24 self.assertIn(b'You should be redirected', ret.data)
25 self.assertEqual(ret.status_code, 302)25 self.assertEqual(ret.status_code, 302)
2626
27 def test_login_get(self):27 def test_login_get(self):
2828
=== modified file 'tests/test_models.py'
--- tests/test_models.py 2016-06-03 21:37:27 +0000
+++ tests/test_models.py 2016-06-16 06:25:31 +0000
@@ -42,26 +42,6 @@
42 db.session.add.assert_called_once_with(comment.return_value)42 db.session.add.assert_called_once_with(comment.return_value)
43 db.session.commit.assert_called_once_with()43 db.session.commit.assert_called_once_with()
4444
45 @patch('bileto.models.environ', ENVIRON)
46 def test_request_summarize(self):
47 """Display a handy summary."""
48 req = Request(
49 dest=EMPTY,
50 landers='jimbo',
51 merge_proposals='http://foo/merge',
52 request_id=5,
53 series='yakkety+xenial+vivid',
54 sources='qtmir qtubuntu',
55 )
56 self.assertEqual(
57 req.summarize(),
58 'Packages:\n'
59 'https://launchpad.net/ubuntu/+source/qtmir\n'
60 'https://launchpad.net/ubuntu/+source/qtubuntu\n\n'
61 'More info:\n'
62 'http://example.com/#/ticket/5\n')
63 self.assertEqual(req.merge_proposals_list, ['http://foo/merge'])
64
65 def test_request_validate(self):45 def test_request_validate(self):
66 """Prevent invalid requests from being built."""46 """Prevent invalid requests from being built."""
67 req = Request(47 req = Request(
6848
=== modified file 'tests/test_worker_manager.py'
--- tests/test_worker_manager.py 2016-06-14 06:59:01 +0000
+++ tests/test_worker_manager.py 2016-06-16 06:25:31 +0000
@@ -3,15 +3,18 @@
3Test the package manager class.3Test the package manager class.
4"""4"""
55
6from os.path import join
7from subprocess import PIPE
6from asyncio import coroutine8from asyncio import coroutine
7from collections import defaultdict9from collections import defaultdict
8from unittest.mock import Mock, patch, call10from unittest.mock import Mock, patch, call
911
10from lazr.restfulclient.errors import BadRequest12# from lazr.restfulclient.errors import BadRequest
1113
12from tests.tests import WorkerTestCase, fake_subproc14from tests.tests import WorkerTestCase, fake_subproc
1315
14from bileto.lplib import lp16from bileto.lplib import lp
17from bileto.settings import Config
15from bileto.models import BiletoError18from bileto.models import BiletoError
16from bileto.worker.manager import Manager, Merge, Manual, Package19from bileto.worker.manager import Manager, Merge, Manual, Package
17from bileto.worker.log import log20from bileto.worker.log import log
@@ -20,8 +23,11 @@
20DIFF_ENV = dict(INCLUDES='')23DIFF_ENV = dict(INCLUDES='')
21MAKE_PPA_ENV = dict(REQUEST_URL_ROOT='https://ppa/thing')24MAKE_PPA_ENV = dict(REQUEST_URL_ROOT='https://ppa/thing')
22MERGES = fake_subproc(0, [(b'ofono', b'')] * 5)25MERGES = fake_subproc(0, [(b'ofono', b'')] * 5)
26POST_DIFF = fake_subproc(0, [(b'swift/diff (9000 lines)', b'')])
27POST_NUKE = fake_subproc(0, [(b'', b'')])
23MOCKS = defaultdict(Mock)28MOCKS = defaultdict(Mock)
24SLASH = '/'29SLASH = '/'
30EMPTY = ''
25N = '\n'31N = '\n'
2632
2733
@@ -211,10 +217,46 @@
211 ticket = Mock()217 ticket = Mock()
212 manager = Manager(ticket)218 manager = Manager(ticket)
213 manager.do_the_stuff()219 manager.do_the_stuff()
214 package.pre_do_the_stuff.assert_called_once_with(ticket)
215 package.post_do_the_stuff.assert_called_once_with(ticket)
216 self.assertEqual(called, [True, True])220 self.assertEqual(called, [True, True])
217221
222 @patch('bileto.worker.manager.get')
223 @patch('bileto.worker.manager.lp')
224 def test_manager_assign(self, lp_mock, get):
225 """Assign silos."""
226 lp_mock.ppa_team.ppas = [Mock(distribution_link='/ubuntu')]
227 lp_mock.ppa_team.ppas[0].name = 'landing-987'
228 get.return_value.json.return_value = dict(
229 requests=[dict(siloname='ubuntu/landing-123')])
230 ticket = Mock(siloname=None)
231 manager = Manager(ticket)
232 manager.assign()
233 ticket.siloname = 'yes'
234 manager.assign()
235 ticket.set_status.assert_called_once_with(
236 'Ready to build', siloname='ubuntu/landing-987')
237
238 @patch('bileto.worker.manager.get')
239 @patch('bileto.worker.manager.lp')
240 def test_manager_assign_fail(self, lp_mock, get):
241 """Fail correctly when no silos available."""
242 lp_mock.ppa_team.ppas = [Mock(distribution_link='/ubuntu')]
243 lp_mock.ppa_team.ppas[0].name = 'landing-987'
244 get.return_value.json.return_value = dict(
245 requests=[dict(siloname='ubuntu/landing-987')])
246 ticket = Mock(siloname=None)
247 manager = Manager(ticket)
248 with self.assertRaisesRegex(BiletoError, 'No silos'):
249 manager.assign()
250
251 def test_manager_validate(self):
252 """Do global validation."""
253 ticket = Mock()
254 manager = Manager(ticket)
255 manager.validate()
256 self.assertEqual(ticket.mock_calls, [
257 call.validate(),
258 ])
259
218 @patch('bileto.worker.manager.Manager.__getattr__')260 @patch('bileto.worker.manager.Manager.__getattr__')
219 def test_manager_upload(self, getattr_mock):261 def test_manager_upload(self, getattr_mock):
220 """Prevent cancellation after uploading."""262 """Prevent cancellation after uploading."""
@@ -228,6 +270,7 @@
228 @patch('bileto.worker.manager.Package')270 @patch('bileto.worker.manager.Package')
229 @patch('bileto.worker.manager.Manager.packages')271 @patch('bileto.worker.manager.Manager.packages')
230 @patch('bileto.worker.manager.environ', DIFF_ENV)272 @patch('bileto.worker.manager.environ', DIFF_ENV)
273 @patch('bileto.worker.vcs.create_subprocess_exec', POST_DIFF)
231 def test_manager_diff(self, packages, package):274 def test_manager_diff(self, packages, package):
232 """Force diffing against all packages even if not all were built."""275 """Force diffing against all packages even if not all were built."""
233 called = []276 called = []
@@ -239,54 +282,77 @@
239 instance = Mock()282 instance = Mock()
240 instance.diff = appender283 instance.diff = appender
241 packages.return_value = dict(blip=instance, glorp=instance)284 packages.return_value = dict(blip=instance, glorp=instance)
242 ticket = Mock(sources='blip glorp')285 ticket = Mock(sources='blip glorp', request_id=5)
243 manager = Manager(ticket)286 manager = Manager(ticket)
244 manager.diff()287 manager.diff()
245 package.pre_diff.assert_called_once_with(ticket)
246 package.post_diff.assert_called_once_with(ticket)
247 self.assertEqual(called, [True, True])288 self.assertEqual(called, [True, True])
248 self.assertEqual(DIFF_ENV, dict(INCLUDES='blip glorp'))289 self.assertEqual(DIFF_ENV['INCLUDES'], 'blip glorp')
249 packages.cache_clear.assert_called_once_with()290 packages.cache_clear.assert_called_once_with()
250291 env = POST_DIFF.mock_calls[0][2]['env']
251 @patch('bileto.worker.manager.lp')292 self.assertEqual(env['ACTION'], 'swift_upload')
252 def test_manager_nuke(self, lp_mock):293 self.assertSequenceEqual(POST_DIFF.mock_calls, [
253 """Delete ephemeral PPA during nuke phase."""294 call(join(Config.ROOT, 'scripts', 'vcs.sh'),
254 ticket = Mock(request_id='5')295 env=env, stdout=PIPE, stderr=PIPE),
255 manager = Manager(ticket)296 ])
256 manager.nuke()297 self.assertEqual(ticket.mock_calls, [
257 self.assertEqual(298 call.commit(),
258 str(lp_mock.mock_calls),299 ])
259 "[call.ppa_team.getPPAByName(name='05'),\n"300 self.assertEqual(ticket.artifacts, 'swift/diff (9000 lines)')
260 " call.ppa_team.getPPAByName().web_link.__str__(),\n"301
261 " call.ppa_team.getPPAByName().lp_delete()]")302 @patch('bileto.worker.manager.Manager.ppa')
262303 @patch('bileto.worker.vcs.create_subprocess_exec', POST_NUKE)
263 @patch('bileto.worker.manager.lp')304 def test_manager_nuke(self, ppa):
264 def test_manager_nuke_again(self, lp_mock):305 """Empty PPA when freeing."""
265 """Don't explode if the same PPA is nuked twice in a row."""306 source = Mock(status='Published')
266 lp_mock.ppa_team.getPPAByName.return_value.lp_delete.side_effect = (307 ppa.getPublishedSources.return_value = [source]
267 BadRequest(b'', b'Archive already deleted'))308 ticket = Mock(request_id='5')
268 ticket = Mock(request_id='5')309 manager = Manager(ticket)
269 manager = Manager(ticket)310 manager.nuke('From Orbit')
270 manager.nuke()311 source.requestDeletion.assert_called_once_with()
271 self.assertEqual(312 self.assertEqual(ticket.mock_calls, [
272 str(lp_mock.mock_calls),313 call.set_status('From Orbit', siloname=EMPTY, artifacts=EMPTY),
273 "[call.ppa_team.getPPAByName(name='05'),\n"314 call.commit(),
274 " call.ppa_team.getPPAByName().web_link.__str__(),\n"315 ])
275 " call.ppa_team.getPPAByName().lp_delete()]")316
276317 # @patch('bileto.worker.manager.lp')
277 @patch('bileto.worker.manager.lp')318 # def test_manager_nuke(self, lp_mock):
278 def test_manager_nuke_invalid(self, lp_mock):319 # """Delete ephemeral PPA during nuke phase."""
279 """Don't explode if a non-existing PPA is nuked."""320 # ticket = Mock(request_id='5')
280 lp_mock.ppa_team.getPPAByName.return_value.lp_delete.side_effect = (321 # manager = Manager(ticket)
281 AttributeError)322 # manager.nuke()
282 ticket = Mock(request_id='5')323 # self.assertEqual(
283 manager = Manager(ticket)324 # str(lp_mock.mock_calls),
284 manager.nuke()325 # "[call.ppa_team.getPPAByName(name='05'),\n"
285 self.assertEqual(326 # " call.ppa_team.getPPAByName().web_link.__str__(),\n"
286 str(lp_mock.mock_calls),327 # " call.ppa_team.getPPAByName().lp_delete()]")
287 "[call.ppa_team.getPPAByName(name='05'),\n"328 #
288 " call.ppa_team.getPPAByName().web_link.__str__(),\n"329 # @patch('bileto.worker.manager.lp')
289 " call.ppa_team.getPPAByName().lp_delete()]")330 # def test_manager_nuke_again(self, lp_mock):
331 # """Don't explode if the same PPA is nuked twice in a row."""
332 # lp_mock.ppa_team.getPPAByName.return_value.lp_delete.side_effect = (
333 # BadRequest(b'', b'Archive already deleted'))
334 # ticket = Mock(request_id='5')
335 # manager = Manager(ticket)
336 # manager.nuke()
337 # self.assertEqual(
338 # str(lp_mock.mock_calls),
339 # "[call.ppa_team.getPPAByName(name='05'),\n"
340 # " call.ppa_team.getPPAByName().web_link.__str__(),\n"
341 # " call.ppa_team.getPPAByName().lp_delete()]")
342
343 # @patch('bileto.worker.manager.lp')
344 # def test_manager_nuke_invalid(self, lp_mock):
345 # """Don't explode if a non-existing PPA is nuked."""
346 # lp_mock.ppa_team.getPPAByName.return_value.lp_delete.side_effect = (
347 # AttributeError)
348 # ticket = Mock(request_id='5')
349 # manager = Manager(ticket)
350 # manager.nuke()
351 # self.assertEqual(
352 # str(lp_mock.mock_calls),
353 # "[call.ppa_team.getPPAByName(name='05'),\n"
354 # " call.ppa_team.getPPAByName().web_link.__str__(),\n"
355 # " call.ppa_team.getPPAByName().lp_delete()]")
290356
291 @patch('bileto.worker.manager.lp')357 @patch('bileto.worker.manager.lp')
292 def test_manager_ppa_ephemeral(self, lp_mock):358 def test_manager_ppa_ephemeral(self, lp_mock):
293359
=== modified file 'tests/test_worker_merge.py'
--- tests/test_worker_merge.py 2016-06-15 06:14:03 +0000
+++ tests/test_worker_merge.py 2016-06-16 06:25:31 +0000
@@ -31,6 +31,8 @@
31 (b'Jimmy <j@bob.com>, Sally <s@lly.com>, Suzy <su@zy.com>, ', b''), STD,31 (b'Jimmy <j@bob.com>, Sally <s@lly.com>, Suzy <su@zy.com>, ', b''), STD,
32])32])
33EXPOSE = fake_subproc(0, [STD])33EXPOSE = fake_subproc(0, [STD])
34MERGE = fake_subproc(0, [STD, STD])
35PUSH = fake_subproc(0, [STD])
3436
3537
36class MergeTestCase(WorkerTestCase):38class MergeTestCase(WorkerTestCase):
@@ -151,3 +153,39 @@
151 'lp:~/ofono/ofono-ubuntu-xenial-12321',153 'lp:~/ofono/ofono-ubuntu-xenial-12321',
152 env=env, stdout=PIPE, stderr=PIPE),154 env=env, stdout=PIPE, stderr=PIPE),
153 ])155 ])
156
157 @patch('bileto.worker.vcs.create_subprocess_exec', MERGE)
158 def test_merge_merge(self):
159 """Merge trunks back into workdir."""
160 Merge.all_mps = dict(ofono=[self.mp])
161 merge = Merge('ofono', self.distro, self.series, self.dest, self.ppa)
162 self.run_coro(merge.merge())
163 env1 = MERGE.mock_calls[0][2]['env']
164 self.assertEqual(env1['SOURCE'], 'ofono')
165 self.assertEqual(env1['SERIES'], 'xenial')
166 self.assertEqual(env1['ACTION'], 'fetch_work_branch')
167 env2 = MERGE.mock_calls[1][2]['env']
168 self.assertEqual(env2['ACTION'], 'merge_trunk')
169 self.assertSequenceEqual(MERGE.mock_calls, [
170 call(join(Config.ROOT, 'scripts', 'vcs.sh'),
171 'lp:~/ofono/ofono-ubuntu-xenial-12321',
172 env=env1, stdout=PIPE, stderr=PIPE),
173 call(join(Config.ROOT, 'scripts', 'vcs.sh'),
174 'lp:ofono',
175 env=env2, stdout=PIPE, stderr=PIPE),
176 ])
177
178 @patch('bileto.worker.vcs.create_subprocess_exec', PUSH)
179 def test_merge_push(self):
180 """Push to trunks."""
181 Merge.all_mps = dict(ofono=[self.mp])
182 merge = Merge('ofono', self.distro, self.series, self.dest, self.ppa)
183 self.run_coro(merge.push())
184 env = PUSH.mock_calls[0][2]['env']
185 self.assertEqual(env['SOURCE'], 'ofono')
186 self.assertEqual(env['SERIES'], 'xenial')
187 self.assertEqual(env['ACTION'], 'push_to_trunk')
188 self.assertSequenceEqual(PUSH.mock_calls, [
189 call(join(Config.ROOT, 'scripts', 'vcs.sh'),
190 'lp:ofono', env=env, stdout=PIPE, stderr=PIPE),
191 ])
154192
=== modified file 'tests/test_worker_package.py'
--- tests/test_worker_package.py 2016-06-04 00:33:30 +0000
+++ tests/test_worker_package.py 2016-06-16 06:25:31 +0000
@@ -12,26 +12,15 @@
12from bileto.settings import Config12from bileto.settings import Config
13from bileto.worker.package import Package13from bileto.worker.package import Package
1414
1515EMPTY = ''
16BUILD = fake_subproc(0, [(b'', b'')])16BUILD = fake_subproc(0, [(b'', b'')])
17UPLOAD = fake_subproc(0, [(b'', b'')])17UPLOAD = fake_subproc(0, [(b'', b'')])
18DIFF = fake_subproc(0, [(b'', b''), (b'', b'')])18DIFF = fake_subproc(0, [(b'', b''), (b'', b'')])
19POST_DIFF = fake_subproc(0, [(b'swift/diff (9000 lines)', b'')])
2019
2120
22class PackageTestCase(WorkerTestCase):21class PackageTestCase(WorkerTestCase):
23 """Test Packages."""22 """Test Packages."""
2423
25 def test_package_post_validate(self):
26 """Do global validation steps."""
27 ticket = Mock()
28 package = Package('foo', self.distro, self.series, self.dest, self.ppa)
29 package.post_validate(ticket)
30 self.assertEqual(ticket.mock_calls, [
31 call.validate(),
32 call.summarize(),
33 ])
34
35 @patch('bileto.worker.vcs.create_subprocess_exec', BUILD)24 @patch('bileto.worker.vcs.create_subprocess_exec', BUILD)
36 def test_package_build(self):25 def test_package_build(self):
37 """Invoke package building."""26 """Invoke package building."""
@@ -101,28 +90,3 @@
101 call(source_name='dif', distro_series=self.series,90 call(source_name='dif', distro_series=self.series,
102 exact_match=True, order_by_date=True),91 exact_match=True, order_by_date=True),
103 ])92 ])
104
105 @patch('bileto.worker.vcs.create_subprocess_exec', POST_DIFF)
106 def test_package_post_diff(self):
107 """Invoke swift diff uploading."""
108 ticket = Mock(request_id='5')
109 Package.post_diff(ticket)
110 env = POST_DIFF.mock_calls[0][2]['env']
111 self.assertEqual(env['REQUESTID'], '5')
112 self.assertEqual(env['ACTION'], 'swift_upload')
113 self.assertSequenceEqual(POST_DIFF.mock_calls, [
114 call(join(Config.ROOT, 'scripts', 'vcs.sh'),
115 env=env, stdout=PIPE, stderr=PIPE),
116 ])
117 self.assertEqual(ticket.mock_calls, [
118 call.commit(),
119 ])
120 self.assertEqual(ticket.artifacts, 'swift/diff (9000 lines)')
121
122 def test_package_post_abandon(self):
123 """Set abandonment status."""
124 ticket = Mock(request_id='5')
125 Package.post_abandon(ticket)
126 self.assertEqual(ticket.mock_calls, [
127 call.set_status('Abandoned'),
128 ])

Subscribers

People subscribed via source and target branches