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