Merge lp:~allenap/launchpad/undo-read-only-transactions-in-buildmaster into lp:launchpad
- undo-read-only-transactions-in-buildmaster
- Merge into devel
Proposed by
Gavin Panella
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 14552 |
Proposed branch: | lp:~allenap/launchpad/undo-read-only-transactions-in-buildmaster |
Merge into: | lp:launchpad |
Diff against target: |
1620 lines (+251/-470) 13 files modified
lib/lp/archiveuploader/tests/test_uploadprocessor.py (+2/-4) lib/lp/buildmaster/interfaces/builder.py (+4/-6) lib/lp/buildmaster/manager.py (+50/-78) lib/lp/buildmaster/model/builder.py (+15/-28) lib/lp/buildmaster/model/buildfarmjobbehavior.py (+33/-63) lib/lp/buildmaster/model/packagebuild.py (+57/-91) lib/lp/buildmaster/tests/test_builder.py (+7/-27) lib/lp/buildmaster/tests/test_manager.py (+40/-127) lib/lp/buildmaster/tests/test_packagebuild.py (+20/-10) lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+8/-6) lib/lp/services/database/transaction_policy.py (+2/-5) lib/lp/soyuz/tests/test_binarypackagebuild.py (+4/-11) lib/lp/translations/model/translationtemplatesbuildbehavior.py (+9/-14) |
To merge this branch: | bzr merge lp:~allenap/launchpad/undo-read-only-transactions-in-buildmaster |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Approve | ||
Review via email: mp+86299@code.launchpad.net |
Commit message
[r=allenap][bug=905853,905855,906079] Revert r14499 and r14459 because read-only transactions in buildmaster are causing production issues.
Description of the change
Revert r14499 and r14459 because read-only transactions in buildmaster
are causing production issues. This should bring stable into line with
current cowboy on cesium.
To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py' | |||
2 | --- lib/lp/archiveuploader/tests/test_uploadprocessor.py 2011-12-13 17:10:46 +0000 | |||
3 | +++ lib/lp/archiveuploader/tests/test_uploadprocessor.py 2011-12-19 21:48:31 +0000 | |||
4 | @@ -629,8 +629,7 @@ | |||
5 | 629 | from_addr, to_addrs, raw_msg = stub.test_emails.pop() | 629 | from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
6 | 630 | foo_bar = "Foo Bar <foo.bar@canonical.com>" | 630 | foo_bar = "Foo Bar <foo.bar@canonical.com>" |
7 | 631 | daniel = "Daniel Silverstone <daniel.silverstone@canonical.com>" | 631 | daniel = "Daniel Silverstone <daniel.silverstone@canonical.com>" |
10 | 632 | self.assertContentEqual( | 632 | self.assertEqual([e.strip() for e in to_addrs], [foo_bar, daniel]) |
9 | 633 | [foo_bar, daniel], [e.strip() for e in to_addrs]) | ||
11 | 634 | self.assertTrue( | 633 | self.assertTrue( |
12 | 635 | "NEW" in raw_msg, "Expected email containing 'NEW', got:\n%s" | 634 | "NEW" in raw_msg, "Expected email containing 'NEW', got:\n%s" |
13 | 636 | % raw_msg) | 635 | % raw_msg) |
14 | @@ -664,8 +663,7 @@ | |||
15 | 664 | from_addr, to_addrs, raw_msg = stub.test_emails.pop() | 663 | from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
16 | 665 | daniel = "Daniel Silverstone <daniel.silverstone@canonical.com>" | 664 | daniel = "Daniel Silverstone <daniel.silverstone@canonical.com>" |
17 | 666 | foo_bar = "Foo Bar <foo.bar@canonical.com>" | 665 | foo_bar = "Foo Bar <foo.bar@canonical.com>" |
20 | 667 | self.assertContentEqual( | 666 | self.assertEqual([e.strip() for e in to_addrs], [foo_bar, daniel]) |
19 | 668 | [foo_bar, daniel], [e.strip() for e in to_addrs]) | ||
21 | 669 | self.assertTrue("Waiting for approval" in raw_msg, | 667 | self.assertTrue("Waiting for approval" in raw_msg, |
22 | 670 | "Expected an 'upload awaits approval' email.\n" | 668 | "Expected an 'upload awaits approval' email.\n" |
23 | 671 | "Got:\n%s" % raw_msg) | 669 | "Got:\n%s" % raw_msg) |
24 | 672 | 670 | ||
25 | === modified file 'lib/lp/buildmaster/interfaces/builder.py' | |||
26 | --- lib/lp/buildmaster/interfaces/builder.py 2011-11-09 11:50:17 +0000 | |||
27 | +++ lib/lp/buildmaster/interfaces/builder.py 2011-12-19 21:48:31 +0000 | |||
28 | @@ -1,4 +1,4 @@ | |||
30 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
31 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
32 | 3 | 3 | ||
33 | 4 | # pylint: disable-msg=E0211,E0213 | 4 | # pylint: disable-msg=E0211,E0213 |
34 | @@ -25,10 +25,10 @@ | |||
35 | 25 | export_as_webservice_entry, | 25 | export_as_webservice_entry, |
36 | 26 | export_read_operation, | 26 | export_read_operation, |
37 | 27 | exported, | 27 | exported, |
38 | 28 | operation_for_version, | ||
39 | 29 | operation_parameters, | 28 | operation_parameters, |
40 | 29 | operation_returns_entry, | ||
41 | 30 | operation_returns_collection_of, | 30 | operation_returns_collection_of, |
43 | 31 | operation_returns_entry, | 31 | operation_for_version, |
44 | 32 | ) | 32 | ) |
45 | 33 | from lazr.restful.fields import ( | 33 | from lazr.restful.fields import ( |
46 | 34 | Reference, | 34 | Reference, |
47 | @@ -50,12 +50,12 @@ | |||
48 | 50 | from lp.app.validators.name import name_validator | 50 | from lp.app.validators.name import name_validator |
49 | 51 | from lp.app.validators.url import builder_url_validator | 51 | from lp.app.validators.url import builder_url_validator |
50 | 52 | from lp.registry.interfaces.role import IHasOwner | 52 | from lp.registry.interfaces.role import IHasOwner |
51 | 53 | from lp.soyuz.interfaces.processor import IProcessor | ||
52 | 53 | from lp.services.fields import ( | 54 | from lp.services.fields import ( |
53 | 54 | Description, | 55 | Description, |
54 | 55 | PersonChoice, | 56 | PersonChoice, |
55 | 56 | Title, | 57 | Title, |
56 | 57 | ) | 58 | ) |
57 | 58 | from lp.soyuz.interfaces.processor import IProcessor | ||
58 | 59 | 59 | ||
59 | 60 | 60 | ||
60 | 61 | class BuildDaemonError(Exception): | 61 | class BuildDaemonError(Exception): |
61 | @@ -195,8 +195,6 @@ | |||
62 | 195 | 195 | ||
63 | 196 | def setSlaveForTesting(proxy): | 196 | def setSlaveForTesting(proxy): |
64 | 197 | """Sets the RPC proxy through which to operate the build slave.""" | 197 | """Sets the RPC proxy through which to operate the build slave.""" |
65 | 198 | # XXX JeroenVermeulen 2011-11-09, bug=888010: Don't use this. | ||
66 | 199 | # It's a trap. See bug for details. | ||
67 | 200 | 198 | ||
68 | 201 | def verifySlaveBuildCookie(slave_build_id): | 199 | def verifySlaveBuildCookie(slave_build_id): |
69 | 202 | """Verify that a slave's build cookie is consistent. | 200 | """Verify that a slave's build cookie is consistent. |
70 | 203 | 201 | ||
71 | === modified file 'lib/lp/buildmaster/manager.py' | |||
72 | --- lib/lp/buildmaster/manager.py 2011-12-08 11:39:10 +0000 | |||
73 | +++ lib/lp/buildmaster/manager.py 2011-12-19 21:48:31 +0000 | |||
74 | @@ -1,4 +1,4 @@ | |||
76 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
77 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
78 | 3 | 3 | ||
79 | 4 | """Soyuz buildd slave manager logic.""" | 4 | """Soyuz buildd slave manager logic.""" |
80 | @@ -23,6 +23,10 @@ | |||
81 | 23 | from zope.component import getUtility | 23 | from zope.component import getUtility |
82 | 24 | 24 | ||
83 | 25 | from lp.buildmaster.enums import BuildStatus | 25 | from lp.buildmaster.enums import BuildStatus |
84 | 26 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( | ||
85 | 27 | BuildBehaviorMismatch, | ||
86 | 28 | ) | ||
87 | 29 | from lp.buildmaster.model.builder import Builder | ||
88 | 26 | from lp.buildmaster.interfaces.builder import ( | 30 | from lp.buildmaster.interfaces.builder import ( |
89 | 27 | BuildDaemonError, | 31 | BuildDaemonError, |
90 | 28 | BuildSlaveFailure, | 32 | BuildSlaveFailure, |
91 | @@ -30,11 +34,6 @@ | |||
92 | 30 | CannotFetchFile, | 34 | CannotFetchFile, |
93 | 31 | CannotResumeHost, | 35 | CannotResumeHost, |
94 | 32 | ) | 36 | ) |
95 | 33 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( | ||
96 | 34 | BuildBehaviorMismatch, | ||
97 | 35 | ) | ||
98 | 36 | from lp.buildmaster.model.builder import Builder | ||
99 | 37 | from lp.services.database.transaction_policy import DatabaseTransactionPolicy | ||
100 | 38 | 37 | ||
101 | 39 | 38 | ||
102 | 40 | BUILDD_MANAGER_LOG_NAME = "slave-scanner" | 39 | BUILDD_MANAGER_LOG_NAME = "slave-scanner" |
103 | @@ -112,17 +111,13 @@ | |||
104 | 112 | # algorithm for polling. | 111 | # algorithm for polling. |
105 | 113 | SCAN_INTERVAL = 15 | 112 | SCAN_INTERVAL = 15 |
106 | 114 | 113 | ||
108 | 115 | def __init__(self, builder_name, logger, clock=None): | 114 | def __init__(self, builder_name, logger): |
109 | 116 | self.builder_name = builder_name | 115 | self.builder_name = builder_name |
110 | 117 | self.logger = logger | 116 | self.logger = logger |
111 | 118 | if clock is None: | ||
112 | 119 | clock = reactor | ||
113 | 120 | self._clock = clock | ||
114 | 121 | 117 | ||
115 | 122 | def startCycle(self): | 118 | def startCycle(self): |
116 | 123 | """Scan the builder and dispatch to it or deal with failures.""" | 119 | """Scan the builder and dispatch to it or deal with failures.""" |
117 | 124 | self.loop = LoopingCall(self.singleCycle) | 120 | self.loop = LoopingCall(self.singleCycle) |
118 | 125 | self.loop.clock = self._clock | ||
119 | 126 | self.stopping_deferred = self.loop.start(self.SCAN_INTERVAL) | 121 | self.stopping_deferred = self.loop.start(self.SCAN_INTERVAL) |
120 | 127 | return self.stopping_deferred | 122 | return self.stopping_deferred |
121 | 128 | 123 | ||
122 | @@ -143,58 +138,51 @@ | |||
123 | 143 | 1. Print the error in the log | 138 | 1. Print the error in the log |
124 | 144 | 2. Increment and assess failure counts on the builder and job. | 139 | 2. Increment and assess failure counts on the builder and job. |
125 | 145 | """ | 140 | """ |
128 | 146 | # Since this is a failure path, we could be in a broken | 141 | # Make sure that pending database updates are removed as it |
129 | 147 | # transaction. Get us a fresh one. | 142 | # could leave the database in an inconsistent state (e.g. The |
130 | 143 | # job says it's running but the buildqueue has no builder set). | ||
131 | 148 | transaction.abort() | 144 | transaction.abort() |
132 | 149 | 145 | ||
133 | 150 | # If we don't recognise the exception include a stack trace with | 146 | # If we don't recognise the exception include a stack trace with |
134 | 151 | # the error. | 147 | # the error. |
135 | 152 | error_message = failure.getErrorMessage() | 148 | error_message = failure.getErrorMessage() |
137 | 153 | familiar_error = failure.check( | 149 | if failure.check( |
138 | 154 | BuildSlaveFailure, CannotBuild, BuildBehaviorMismatch, | 150 | BuildSlaveFailure, CannotBuild, BuildBehaviorMismatch, |
144 | 155 | CannotResumeHost, BuildDaemonError, CannotFetchFile) | 151 | CannotResumeHost, BuildDaemonError, CannotFetchFile): |
145 | 156 | if familiar_error: | 152 | self.logger.info("Scanning %s failed with: %s" % ( |
146 | 157 | self.logger.info( | 153 | self.builder_name, error_message)) |
142 | 158 | "Scanning %s failed with: %s", | ||
143 | 159 | self.builder_name, error_message) | ||
147 | 160 | else: | 154 | else: |
150 | 161 | self.logger.info( | 155 | self.logger.info("Scanning %s failed with: %s\n%s" % ( |
149 | 162 | "Scanning %s failed with: %s\n%s", | ||
151 | 163 | self.builder_name, failure.getErrorMessage(), | 156 | self.builder_name, failure.getErrorMessage(), |
153 | 164 | failure.getTraceback()) | 157 | failure.getTraceback())) |
154 | 165 | 158 | ||
155 | 166 | # Decide if we need to terminate the job or fail the | 159 | # Decide if we need to terminate the job or fail the |
156 | 167 | # builder. | 160 | # builder. |
157 | 168 | try: | 161 | try: |
158 | 169 | builder = get_builder(self.builder_name) | 162 | builder = get_builder(self.builder_name) |
174 | 170 | transaction.commit() | 163 | builder.gotFailure() |
175 | 171 | 164 | if builder.currentjob is not None: | |
176 | 172 | with DatabaseTransactionPolicy(read_only=False): | 165 | build_farm_job = builder.getCurrentBuildFarmJob() |
177 | 173 | builder.gotFailure() | 166 | build_farm_job.gotFailure() |
178 | 174 | 167 | self.logger.info( | |
179 | 175 | if builder.currentjob is None: | 168 | "builder %s failure count: %s, " |
180 | 176 | self.logger.info( | 169 | "job '%s' failure count: %s" % ( |
166 | 177 | "Builder %s failed a probe, count: %s", | ||
167 | 178 | self.builder_name, builder.failure_count) | ||
168 | 179 | else: | ||
169 | 180 | build_farm_job = builder.getCurrentBuildFarmJob() | ||
170 | 181 | build_farm_job.gotFailure() | ||
171 | 182 | self.logger.info( | ||
172 | 183 | "builder %s failure count: %s, " | ||
173 | 184 | "job '%s' failure count: %s", | ||
181 | 185 | self.builder_name, | 170 | self.builder_name, |
182 | 186 | builder.failure_count, | 171 | builder.failure_count, |
183 | 187 | build_farm_job.title, | 172 | build_farm_job.title, |
188 | 188 | build_farm_job.failure_count) | 173 | build_farm_job.failure_count)) |
189 | 189 | 174 | else: | |
190 | 190 | assessFailureCounts(builder, failure.getErrorMessage()) | 175 | self.logger.info( |
191 | 191 | transaction.commit() | 176 | "Builder %s failed a probe, count: %s" % ( |
192 | 177 | self.builder_name, builder.failure_count)) | ||
193 | 178 | assessFailureCounts(builder, failure.getErrorMessage()) | ||
194 | 179 | transaction.commit() | ||
195 | 192 | except: | 180 | except: |
196 | 193 | # Catastrophic code failure! Not much we can do. | 181 | # Catastrophic code failure! Not much we can do. |
197 | 194 | transaction.abort() | ||
198 | 195 | self.logger.error( | 182 | self.logger.error( |
199 | 196 | "Miserable failure when trying to examine failure counts:\n", | 183 | "Miserable failure when trying to examine failure counts:\n", |
200 | 197 | exc_info=True) | 184 | exc_info=True) |
201 | 185 | transaction.abort() | ||
202 | 198 | 186 | ||
203 | 199 | def checkCancellation(self, builder): | 187 | def checkCancellation(self, builder): |
204 | 200 | """See if there is a pending cancellation request. | 188 | """See if there is a pending cancellation request. |
205 | @@ -248,9 +236,14 @@ | |||
206 | 248 | """ | 236 | """ |
207 | 249 | # We need to re-fetch the builder object on each cycle as the | 237 | # We need to re-fetch the builder object on each cycle as the |
208 | 250 | # Storm store is invalidated over transaction boundaries. | 238 | # Storm store is invalidated over transaction boundaries. |
209 | 239 | |||
210 | 251 | self.builder = get_builder(self.builder_name) | 240 | self.builder = get_builder(self.builder_name) |
211 | 252 | 241 | ||
212 | 253 | def status_updated(ignored): | 242 | def status_updated(ignored): |
213 | 243 | # Commit the changes done while possibly rescuing jobs, to | ||
214 | 244 | # avoid holding table locks. | ||
215 | 245 | transaction.commit() | ||
216 | 246 | |||
217 | 254 | # See if we think there's an active build on the builder. | 247 | # See if we think there's an active build on the builder. |
218 | 255 | buildqueue = self.builder.getBuildQueue() | 248 | buildqueue = self.builder.getBuildQueue() |
219 | 256 | 249 | ||
220 | @@ -260,10 +253,14 @@ | |||
221 | 260 | return self.builder.updateBuild(buildqueue) | 253 | return self.builder.updateBuild(buildqueue) |
222 | 261 | 254 | ||
223 | 262 | def build_updated(ignored): | 255 | def build_updated(ignored): |
224 | 256 | # Commit changes done while updating the build, to avoid | ||
225 | 257 | # holding table locks. | ||
226 | 258 | transaction.commit() | ||
227 | 259 | |||
228 | 263 | # If the builder is in manual mode, don't dispatch anything. | 260 | # If the builder is in manual mode, don't dispatch anything. |
229 | 264 | if self.builder.manual: | 261 | if self.builder.manual: |
230 | 265 | self.logger.debug( | 262 | self.logger.debug( |
232 | 266 | '%s is in manual mode, not dispatching.', | 263 | '%s is in manual mode, not dispatching.' % |
233 | 267 | self.builder.name) | 264 | self.builder.name) |
234 | 268 | return | 265 | return |
235 | 269 | 266 | ||
236 | @@ -281,33 +278,22 @@ | |||
237 | 281 | job = self.builder.currentjob | 278 | job = self.builder.currentjob |
238 | 282 | if job is not None and not self.builder.builderok: | 279 | if job is not None and not self.builder.builderok: |
239 | 283 | self.logger.info( | 280 | self.logger.info( |
242 | 284 | "%s was made unavailable; resetting attached job.", | 281 | "%s was made unavailable, resetting attached " |
243 | 285 | self.builder.name) | 282 | "job" % self.builder.name) |
244 | 283 | job.reset() | ||
245 | 286 | transaction.commit() | 284 | transaction.commit() |
246 | 287 | with DatabaseTransactionPolicy(read_only=False): | ||
247 | 288 | job.reset() | ||
248 | 289 | transaction.commit() | ||
249 | 290 | return | 285 | return |
250 | 291 | 286 | ||
251 | 292 | # See if there is a job we can dispatch to the builder slave. | 287 | # See if there is a job we can dispatch to the builder slave. |
252 | 293 | 288 | ||
253 | 294 | # XXX JeroenVermeulen 2011-10-11, bug=872112: The job's | ||
254 | 295 | # failure count will be reset once the job has started | ||
255 | 296 | # successfully. Because of intervening commits, you may see | ||
256 | 297 | # a build with a nonzero failure count that's actually going | ||
257 | 298 | # to succeed later (and have a failure count of zero). Or | ||
258 | 299 | # it may fail yet end up with a lower failure count than you | ||
259 | 300 | # saw earlier. | ||
260 | 301 | d = self.builder.findAndStartJob() | 289 | d = self.builder.findAndStartJob() |
261 | 302 | 290 | ||
262 | 303 | def job_started(candidate): | 291 | def job_started(candidate): |
263 | 304 | if self.builder.currentjob is not None: | 292 | if self.builder.currentjob is not None: |
264 | 305 | # After a successful dispatch we can reset the | 293 | # After a successful dispatch we can reset the |
265 | 306 | # failure_count. | 294 | # failure_count. |
266 | 295 | self.builder.resetFailureCount() | ||
267 | 307 | transaction.commit() | 296 | transaction.commit() |
268 | 308 | with DatabaseTransactionPolicy(read_only=False): | ||
269 | 309 | self.builder.resetFailureCount() | ||
270 | 310 | transaction.commit() | ||
271 | 311 | return self.builder.slave | 297 | return self.builder.slave |
272 | 312 | else: | 298 | else: |
273 | 313 | return None | 299 | return None |
274 | @@ -386,7 +372,6 @@ | |||
275 | 386 | self.logger = self._setupLogger() | 372 | self.logger = self._setupLogger() |
276 | 387 | self.new_builders_scanner = NewBuildersScanner( | 373 | self.new_builders_scanner = NewBuildersScanner( |
277 | 388 | manager=self, clock=clock) | 374 | manager=self, clock=clock) |
278 | 389 | self.transaction_policy = DatabaseTransactionPolicy(read_only=True) | ||
279 | 390 | 375 | ||
280 | 391 | def _setupLogger(self): | 376 | def _setupLogger(self): |
281 | 392 | """Set up a 'slave-scanner' logger that redirects to twisted. | 377 | """Set up a 'slave-scanner' logger that redirects to twisted. |
282 | @@ -405,28 +390,16 @@ | |||
283 | 405 | logger.setLevel(level) | 390 | logger.setLevel(level) |
284 | 406 | return logger | 391 | return logger |
285 | 407 | 392 | ||
286 | 408 | def enterReadOnlyDatabasePolicy(self): | ||
287 | 409 | """Set the database transaction policy to read-only. | ||
288 | 410 | |||
289 | 411 | Any previously pending changes are committed first. | ||
290 | 412 | """ | ||
291 | 413 | transaction.commit() | ||
292 | 414 | self.transaction_policy.__enter__() | ||
293 | 415 | |||
294 | 416 | def exitReadOnlyDatabasePolicy(self, *args): | ||
295 | 417 | """Reset database transaction policy to the default read-write.""" | ||
296 | 418 | self.transaction_policy.__exit__(None, None, None) | ||
297 | 419 | |||
298 | 420 | def startService(self): | 393 | def startService(self): |
299 | 421 | """Service entry point, called when the application starts.""" | 394 | """Service entry point, called when the application starts.""" |
300 | 395 | |||
301 | 396 | # Get a list of builders and set up scanners on each one. | ||
302 | 397 | |||
303 | 422 | # Avoiding circular imports. | 398 | # Avoiding circular imports. |
304 | 423 | from lp.buildmaster.interfaces.builder import IBuilderSet | 399 | from lp.buildmaster.interfaces.builder import IBuilderSet |
311 | 424 | 400 | builder_set = getUtility(IBuilderSet) | |
312 | 425 | self.enterReadOnlyDatabasePolicy() | 401 | builders = [builder.name for builder in builder_set] |
313 | 426 | 402 | self.addScanForBuilders(builders) | |
308 | 427 | # Get a list of builders and set up scanners on each one. | ||
309 | 428 | self.addScanForBuilders( | ||
310 | 429 | [builder.name for builder in getUtility(IBuilderSet)]) | ||
314 | 430 | self.new_builders_scanner.scheduleScan() | 403 | self.new_builders_scanner.scheduleScan() |
315 | 431 | 404 | ||
316 | 432 | # Events will now fire in the SlaveScanner objects to scan each | 405 | # Events will now fire in the SlaveScanner objects to scan each |
317 | @@ -447,7 +420,6 @@ | |||
318 | 447 | # stopped, so we can wait on them all at once here before | 420 | # stopped, so we can wait on them all at once here before |
319 | 448 | # exiting. | 421 | # exiting. |
320 | 449 | d = defer.DeferredList(deferreds, consumeErrors=True) | 422 | d = defer.DeferredList(deferreds, consumeErrors=True) |
321 | 450 | d.addCallback(self.exitReadOnlyDatabasePolicy) | ||
322 | 451 | return d | 423 | return d |
323 | 452 | 424 | ||
324 | 453 | def addScanForBuilders(self, builders): | 425 | def addScanForBuilders(self, builders): |
325 | 454 | 426 | ||
326 | === modified file 'lib/lp/buildmaster/model/builder.py' | |||
327 | --- lib/lp/buildmaster/model/builder.py 2011-12-13 13:33:04 +0000 | |||
328 | +++ lib/lp/buildmaster/model/builder.py 2011-12-19 21:48:31 +0000 | |||
329 | @@ -1,4 +1,4 @@ | |||
331 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009,2011 Canonical Ltd. This software is licensed under the |
332 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
333 | 3 | 3 | ||
334 | 4 | # pylint: disable-msg=E0611,W0212 | 4 | # pylint: disable-msg=E0611,W0212 |
335 | @@ -76,7 +76,6 @@ | |||
336 | 76 | specific_job_classes, | 76 | specific_job_classes, |
337 | 77 | ) | 77 | ) |
338 | 78 | from lp.registry.interfaces.person import validate_public_person | 78 | from lp.registry.interfaces.person import validate_public_person |
339 | 79 | from lp.services.database.transaction_policy import DatabaseTransactionPolicy | ||
340 | 80 | from lp.services.job.interfaces.job import JobStatus | 79 | from lp.services.job.interfaces.job import JobStatus |
341 | 81 | from lp.services.job.model.job import Job | 80 | from lp.services.job.model.job import Job |
342 | 82 | from lp.services.propertycache import ( | 81 | from lp.services.propertycache import ( |
343 | @@ -546,8 +545,6 @@ | |||
344 | 546 | 545 | ||
345 | 547 | def setSlaveForTesting(self, proxy): | 546 | def setSlaveForTesting(self, proxy): |
346 | 548 | """See IBuilder.""" | 547 | """See IBuilder.""" |
347 | 549 | # XXX JeroenVermeulen 2011-11-09, bug=888010: Don't use this. | ||
348 | 550 | # It's a trap. See bug for details. | ||
349 | 551 | self._testing_slave = proxy | 548 | self._testing_slave = proxy |
350 | 552 | del get_property_cache(self).slave | 549 | del get_property_cache(self).slave |
351 | 553 | 550 | ||
352 | @@ -676,13 +673,10 @@ | |||
353 | 676 | bytes_written = out_file.tell() | 673 | bytes_written = out_file.tell() |
354 | 677 | out_file.seek(0) | 674 | out_file.seek(0) |
355 | 678 | 675 | ||
363 | 679 | transaction.commit() | 676 | library_file = getUtility(ILibraryFileAliasSet).create( |
364 | 680 | with DatabaseTransactionPolicy(read_only=False): | 677 | filename, bytes_written, out_file, |
365 | 681 | library_file = getUtility(ILibraryFileAliasSet).create( | 678 | contentType=filenameToContentType(filename), |
366 | 682 | filename, bytes_written, out_file, | 679 | restricted=private) |
360 | 683 | contentType=filenameToContentType(filename), | ||
361 | 684 | restricted=private) | ||
362 | 685 | transaction.commit() | ||
367 | 686 | finally: | 680 | finally: |
368 | 687 | # Remove the temporary file. getFile() closes the file | 681 | # Remove the temporary file. getFile() closes the file |
369 | 688 | # object. | 682 | # object. |
370 | @@ -720,7 +714,7 @@ | |||
371 | 720 | def acquireBuildCandidate(self): | 714 | def acquireBuildCandidate(self): |
372 | 721 | """Acquire a build candidate in an atomic fashion. | 715 | """Acquire a build candidate in an atomic fashion. |
373 | 722 | 716 | ||
375 | 723 | When retrieving a candidate we need to mark it as building | 717 | When retrieiving a candidate we need to mark it as building |
376 | 724 | immediately so that it is not dispatched by another builder in the | 718 | immediately so that it is not dispatched by another builder in the |
377 | 725 | build manager. | 719 | build manager. |
378 | 726 | 720 | ||
379 | @@ -730,15 +724,12 @@ | |||
380 | 730 | can be in this code at the same time. | 724 | can be in this code at the same time. |
381 | 731 | 725 | ||
382 | 732 | If there's ever more than one build manager running at once, then | 726 | If there's ever more than one build manager running at once, then |
385 | 733 | this code will need some sort of mutex, or run in a single | 727 | this code will need some sort of mutex. |
384 | 734 | transaction. | ||
386 | 735 | """ | 728 | """ |
387 | 736 | candidate = self._findBuildCandidate() | 729 | candidate = self._findBuildCandidate() |
388 | 737 | if candidate is not None: | 730 | if candidate is not None: |
389 | 731 | candidate.markAsBuilding(self) | ||
390 | 738 | transaction.commit() | 732 | transaction.commit() |
391 | 739 | with DatabaseTransactionPolicy(read_only=False): | ||
392 | 740 | candidate.markAsBuilding(self) | ||
393 | 741 | transaction.commit() | ||
394 | 742 | return candidate | 733 | return candidate |
395 | 743 | 734 | ||
396 | 744 | def _findBuildCandidate(self): | 735 | def _findBuildCandidate(self): |
397 | @@ -801,17 +792,13 @@ | |||
398 | 801 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | 792 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
399 | 802 | candidate_jobs = store.execute(query).get_all() | 793 | candidate_jobs = store.execute(query).get_all() |
400 | 803 | 794 | ||
412 | 804 | transaction.commit() | 795 | for (candidate_id,) in candidate_jobs: |
413 | 805 | with DatabaseTransactionPolicy(read_only=False): | 796 | candidate = getUtility(IBuildQueueSet).get(candidate_id) |
414 | 806 | for (candidate_id,) in candidate_jobs: | 797 | job_class = job_classes[candidate.job_type] |
415 | 807 | candidate = getUtility(IBuildQueueSet).get(candidate_id) | 798 | candidate_approved = job_class.postprocessCandidate( |
416 | 808 | job_class = job_classes[candidate.job_type] | 799 | candidate, logger) |
417 | 809 | candidate_approved = job_class.postprocessCandidate( | 800 | if candidate_approved: |
418 | 810 | candidate, logger) | 801 | return candidate |
408 | 811 | if candidate_approved: | ||
409 | 812 | transaction.commit() | ||
410 | 813 | return candidate | ||
411 | 814 | transaction.commit() | ||
419 | 815 | 802 | ||
420 | 816 | return None | 803 | return None |
421 | 817 | 804 | ||
422 | 818 | 805 | ||
423 | === modified file 'lib/lp/buildmaster/model/buildfarmjobbehavior.py' | |||
424 | --- lib/lp/buildmaster/model/buildfarmjobbehavior.py 2011-10-11 07:15:10 +0000 | |||
425 | +++ lib/lp/buildmaster/model/buildfarmjobbehavior.py 2011-12-19 21:48:31 +0000 | |||
426 | @@ -1,4 +1,4 @@ | |||
428 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
429 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
430 | 3 | 3 | ||
431 | 4 | # pylint: disable-msg=E0211,E0213 | 4 | # pylint: disable-msg=E0211,E0213 |
432 | @@ -16,8 +16,8 @@ | |||
433 | 16 | import socket | 16 | import socket |
434 | 17 | import xmlrpclib | 17 | import xmlrpclib |
435 | 18 | 18 | ||
436 | 19 | import transaction | ||
437 | 20 | from twisted.internet import defer | 19 | from twisted.internet import defer |
438 | 20 | |||
439 | 21 | from zope.component import getUtility | 21 | from zope.component import getUtility |
440 | 22 | from zope.interface import implements | 22 | from zope.interface import implements |
441 | 23 | from zope.security.proxy import removeSecurityProxy | 23 | from zope.security.proxy import removeSecurityProxy |
442 | @@ -32,7 +32,6 @@ | |||
443 | 32 | IBuildFarmJobBehavior, | 32 | IBuildFarmJobBehavior, |
444 | 33 | ) | 33 | ) |
445 | 34 | from lp.services import encoding | 34 | from lp.services import encoding |
446 | 35 | from lp.services.database.transaction_policy import DatabaseTransactionPolicy | ||
447 | 36 | from lp.services.job.interfaces.job import JobStatus | 35 | from lp.services.job.interfaces.job import JobStatus |
448 | 37 | 36 | ||
449 | 38 | 37 | ||
450 | @@ -71,25 +70,6 @@ | |||
451 | 71 | if slave_build_cookie != expected_cookie: | 70 | if slave_build_cookie != expected_cookie: |
452 | 72 | raise CorruptBuildCookie("Invalid slave build cookie.") | 71 | raise CorruptBuildCookie("Invalid slave build cookie.") |
453 | 73 | 72 | ||
454 | 74 | def _getBuilderStatusHandler(self, status_text, logger): | ||
455 | 75 | """Look up the handler method for a given builder status. | ||
456 | 76 | |||
457 | 77 | If status is not a known one, logs an error and returns None. | ||
458 | 78 | """ | ||
459 | 79 | builder_status_handlers = { | ||
460 | 80 | 'BuilderStatus.IDLE': self.updateBuild_IDLE, | ||
461 | 81 | 'BuilderStatus.BUILDING': self.updateBuild_BUILDING, | ||
462 | 82 | 'BuilderStatus.ABORTING': self.updateBuild_ABORTING, | ||
463 | 83 | 'BuilderStatus.ABORTED': self.updateBuild_ABORTED, | ||
464 | 84 | 'BuilderStatus.WAITING': self.updateBuild_WAITING, | ||
465 | 85 | } | ||
466 | 86 | handler = builder_status_handlers.get(status_text) | ||
467 | 87 | if handler is None: | ||
468 | 88 | logger.critical( | ||
469 | 89 | "Builder on %s returned unknown status %s; failing it.", | ||
470 | 90 | self._builder.url, status_text) | ||
471 | 91 | return handler | ||
472 | 92 | |||
473 | 93 | def updateBuild(self, queueItem): | 73 | def updateBuild(self, queueItem): |
474 | 94 | """See `IBuildFarmJobBehavior`.""" | 74 | """See `IBuildFarmJobBehavior`.""" |
475 | 95 | logger = logging.getLogger('slave-scanner') | 75 | logger = logging.getLogger('slave-scanner') |
476 | @@ -97,7 +77,6 @@ | |||
477 | 97 | d = self._builder.slaveStatus() | 77 | d = self._builder.slaveStatus() |
478 | 98 | 78 | ||
479 | 99 | def got_failure(failure): | 79 | def got_failure(failure): |
480 | 100 | transaction.abort() | ||
481 | 101 | failure.trap(xmlrpclib.Fault, socket.error) | 80 | failure.trap(xmlrpclib.Fault, socket.error) |
482 | 102 | info = failure.value | 81 | info = failure.value |
483 | 103 | info = ("Could not contact the builder %s, caught a (%s)" | 82 | info = ("Could not contact the builder %s, caught a (%s)" |
484 | @@ -105,22 +84,27 @@ | |||
485 | 105 | raise BuildSlaveFailure(info) | 84 | raise BuildSlaveFailure(info) |
486 | 106 | 85 | ||
487 | 107 | def got_status(slave_status): | 86 | def got_status(slave_status): |
488 | 87 | builder_status_handlers = { | ||
489 | 88 | 'BuilderStatus.IDLE': self.updateBuild_IDLE, | ||
490 | 89 | 'BuilderStatus.BUILDING': self.updateBuild_BUILDING, | ||
491 | 90 | 'BuilderStatus.ABORTING': self.updateBuild_ABORTING, | ||
492 | 91 | 'BuilderStatus.ABORTED': self.updateBuild_ABORTED, | ||
493 | 92 | 'BuilderStatus.WAITING': self.updateBuild_WAITING, | ||
494 | 93 | } | ||
495 | 94 | |||
496 | 108 | builder_status = slave_status['builder_status'] | 95 | builder_status = slave_status['builder_status'] |
501 | 109 | status_handler = self._getBuilderStatusHandler( | 96 | if builder_status not in builder_status_handlers: |
502 | 110 | builder_status, logger) | 97 | logger.critical( |
503 | 111 | if status_handler is None: | 98 | "Builder on %s returned unknown status %s, failing it" |
504 | 112 | error = ( | 99 | % (self._builder.url, builder_status)) |
505 | 100 | self._builder.failBuilder( | ||
506 | 113 | "Unknown status code (%s) returned from status() probe." | 101 | "Unknown status code (%s) returned from status() probe." |
507 | 114 | % builder_status) | 102 | % builder_status) |
517 | 115 | transaction.commit() | 103 | # XXX: This will leave the build and job in a bad state, but |
518 | 116 | with DatabaseTransactionPolicy(read_only=False): | 104 | # should never be possible, since our builder statuses are |
519 | 117 | self._builder.failBuilder(error) | 105 | # known. |
520 | 118 | # XXX: This will leave the build and job in a bad | 106 | queueItem._builder = None |
521 | 119 | # state, but should never be possible since our | 107 | queueItem.setDateStarted(None) |
513 | 120 | # builder statuses are known. | ||
514 | 121 | queueItem._builder = None | ||
515 | 122 | queueItem.setDateStarted(None) | ||
516 | 123 | transaction.commit() | ||
522 | 124 | return | 108 | return |
523 | 125 | 109 | ||
524 | 126 | # Since logtail is a xmlrpclib.Binary container and it is | 110 | # Since logtail is a xmlrpclib.Binary container and it is |
525 | @@ -130,8 +114,9 @@ | |||
526 | 130 | # will simply remove the proxy. | 114 | # will simply remove the proxy. |
527 | 131 | logtail = removeSecurityProxy(slave_status.get('logtail')) | 115 | logtail = removeSecurityProxy(slave_status.get('logtail')) |
528 | 132 | 116 | ||
529 | 117 | method = builder_status_handlers[builder_status] | ||
530 | 133 | return defer.maybeDeferred( | 118 | return defer.maybeDeferred( |
532 | 134 | status_handler, queueItem, slave_status, logtail, logger) | 119 | method, queueItem, slave_status, logtail, logger) |
533 | 135 | 120 | ||
534 | 136 | d.addErrback(got_failure) | 121 | d.addErrback(got_failure) |
535 | 137 | d.addCallback(got_status) | 122 | d.addCallback(got_status) |
536 | @@ -143,32 +128,22 @@ | |||
537 | 143 | Log this and reset the record. | 128 | Log this and reset the record. |
538 | 144 | """ | 129 | """ |
539 | 145 | logger.warn( | 130 | logger.warn( |
547 | 146 | "Builder %s forgot about buildqueue %d -- " | 131 | "Builder %s forgot about buildqueue %d -- resetting buildqueue " |
548 | 147 | "resetting buildqueue record.", | 132 | "record" % (queueItem.builder.url, queueItem.id)) |
549 | 148 | queueItem.builder.url, queueItem.id) | 133 | queueItem.reset() |
543 | 149 | transaction.commit() | ||
544 | 150 | with DatabaseTransactionPolicy(read_only=False): | ||
545 | 151 | queueItem.reset() | ||
546 | 152 | transaction.commit() | ||
550 | 153 | 134 | ||
551 | 154 | def updateBuild_BUILDING(self, queueItem, slave_status, logtail, logger): | 135 | def updateBuild_BUILDING(self, queueItem, slave_status, logtail, logger): |
552 | 155 | """Build still building, collect the logtail""" | 136 | """Build still building, collect the logtail""" |
559 | 156 | transaction.commit() | 137 | if queueItem.job.status != JobStatus.RUNNING: |
560 | 157 | with DatabaseTransactionPolicy(read_only=False): | 138 | queueItem.job.start() |
561 | 158 | if queueItem.job.status != JobStatus.RUNNING: | 139 | queueItem.logtail = encoding.guess(str(logtail)) |
556 | 159 | queueItem.job.start() | ||
557 | 160 | queueItem.logtail = encoding.guess(str(logtail)) | ||
558 | 161 | transaction.commit() | ||
562 | 162 | 140 | ||
563 | 163 | def updateBuild_ABORTING(self, queueItem, slave_status, logtail, logger): | 141 | def updateBuild_ABORTING(self, queueItem, slave_status, logtail, logger): |
564 | 164 | """Build was ABORTED. | 142 | """Build was ABORTED. |
565 | 165 | 143 | ||
566 | 166 | Master-side should wait until the slave finish the process correctly. | 144 | Master-side should wait until the slave finish the process correctly. |
567 | 167 | """ | 145 | """ |
572 | 168 | transaction.commit() | 146 | queueItem.logtail = "Waiting for slave process to be terminated" |
569 | 169 | with DatabaseTransactionPolicy(read_only=False): | ||
570 | 170 | queueItem.logtail = "Waiting for slave process to be terminated" | ||
571 | 171 | transaction.commit() | ||
573 | 172 | 147 | ||
574 | 173 | def updateBuild_ABORTED(self, queueItem, slave_status, logtail, logger): | 148 | def updateBuild_ABORTED(self, queueItem, slave_status, logtail, logger): |
575 | 174 | """ABORTING process has successfully terminated. | 149 | """ABORTING process has successfully terminated. |
576 | @@ -176,16 +151,11 @@ | |||
577 | 176 | Clean the builder for another jobs. | 151 | Clean the builder for another jobs. |
578 | 177 | """ | 152 | """ |
579 | 178 | d = queueItem.builder.cleanSlave() | 153 | d = queueItem.builder.cleanSlave() |
580 | 179 | |||
581 | 180 | def got_cleaned(ignored): | 154 | def got_cleaned(ignored): |
590 | 181 | transaction.commit() | 155 | queueItem.builder = None |
591 | 182 | with DatabaseTransactionPolicy(read_only=False): | 156 | if queueItem.job.status != JobStatus.FAILED: |
592 | 183 | queueItem.builder = None | 157 | queueItem.job.fail() |
593 | 184 | if queueItem.job.status != JobStatus.FAILED: | 158 | queueItem.specific_job.jobAborted() |
586 | 185 | queueItem.job.fail() | ||
587 | 186 | queueItem.specific_job.jobAborted() | ||
588 | 187 | transaction.commit() | ||
589 | 188 | |||
594 | 189 | return d.addCallback(got_cleaned) | 159 | return d.addCallback(got_cleaned) |
595 | 190 | 160 | ||
596 | 191 | def extractBuildStatus(self, slave_status): | 161 | def extractBuildStatus(self, slave_status): |
597 | 192 | 162 | ||
598 | === modified file 'lib/lp/buildmaster/model/packagebuild.py' | |||
599 | --- lib/lp/buildmaster/model/packagebuild.py 2011-12-08 11:39:10 +0000 | |||
600 | +++ lib/lp/buildmaster/model/packagebuild.py 2011-12-19 21:48:31 +0000 | |||
601 | @@ -1,4 +1,4 @@ | |||
603 | 1 | # Copyright 2010-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
604 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
605 | 3 | 3 | ||
606 | 4 | __metaclass__ = type | 4 | __metaclass__ = type |
607 | @@ -9,11 +9,11 @@ | |||
608 | 9 | ] | 9 | ] |
609 | 10 | 10 | ||
610 | 11 | 11 | ||
611 | 12 | from cStringIO import StringIO | ||
612 | 13 | import datetime | 12 | import datetime |
613 | 14 | import logging | 13 | import logging |
614 | 15 | import os.path | 14 | import os.path |
615 | 16 | 15 | ||
616 | 16 | from cStringIO import StringIO | ||
617 | 17 | from lazr.delegates import delegates | 17 | from lazr.delegates import delegates |
618 | 18 | import pytz | 18 | import pytz |
619 | 19 | from storm.expr import Desc | 19 | from storm.expr import Desc |
620 | @@ -24,7 +24,6 @@ | |||
621 | 24 | Storm, | 24 | Storm, |
622 | 25 | Unicode, | 25 | Unicode, |
623 | 26 | ) | 26 | ) |
624 | 27 | import transaction | ||
625 | 28 | from zope.component import getUtility | 27 | from zope.component import getUtility |
626 | 29 | from zope.interface import ( | 28 | from zope.interface import ( |
627 | 30 | classProvides, | 29 | classProvides, |
628 | @@ -44,8 +43,8 @@ | |||
629 | 44 | MAIN_STORE, | 43 | MAIN_STORE, |
630 | 45 | ) | 44 | ) |
631 | 46 | from lp.buildmaster.enums import ( | 45 | from lp.buildmaster.enums import ( |
632 | 46 | BuildStatus, | ||
633 | 47 | BuildFarmJobType, | 47 | BuildFarmJobType, |
634 | 48 | BuildStatus, | ||
635 | 49 | ) | 48 | ) |
636 | 50 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource | 49 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource |
637 | 51 | from lp.buildmaster.interfaces.packagebuild import ( | 50 | from lp.buildmaster.interfaces.packagebuild import ( |
638 | @@ -58,8 +57,9 @@ | |||
639 | 58 | BuildFarmJobDerived, | 57 | BuildFarmJobDerived, |
640 | 59 | ) | 58 | ) |
641 | 60 | from lp.buildmaster.model.buildqueue import BuildQueue | 59 | from lp.buildmaster.model.buildqueue import BuildQueue |
644 | 61 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 60 | from lp.registry.interfaces.pocket import ( |
645 | 62 | from lp.services.database.transaction_policy import DatabaseTransactionPolicy | 61 | PackagePublishingPocket, |
646 | 62 | ) | ||
647 | 63 | from lp.soyuz.adapters.archivedependencies import ( | 63 | from lp.soyuz.adapters.archivedependencies import ( |
648 | 64 | default_component_dependency_name, | 64 | default_component_dependency_name, |
649 | 65 | ) | 65 | ) |
650 | @@ -179,24 +179,19 @@ | |||
651 | 179 | def storeBuildInfo(build, librarian, slave_status): | 179 | def storeBuildInfo(build, librarian, slave_status): |
652 | 180 | """See `IPackageBuild`.""" | 180 | """See `IPackageBuild`.""" |
653 | 181 | def got_log(lfa_id): | 181 | def got_log(lfa_id): |
654 | 182 | dependencies = slave_status.get('dependencies') | ||
655 | 183 | if dependencies is not None: | ||
656 | 184 | dependencies = unicode(dependencies) | ||
657 | 185 | |||
658 | 186 | # log, builder and date_finished are read-only, so we must | 182 | # log, builder and date_finished are read-only, so we must |
659 | 187 | # currently remove the security proxy to set them. | 183 | # currently remove the security proxy to set them. |
660 | 188 | naked_build = removeSecurityProxy(build) | 184 | naked_build = removeSecurityProxy(build) |
672 | 189 | 185 | naked_build.log = lfa_id | |
673 | 190 | transaction.commit() | 186 | naked_build.builder = build.buildqueue_record.builder |
674 | 191 | with DatabaseTransactionPolicy(read_only=False): | 187 | # XXX cprov 20060615 bug=120584: Currently buildduration includes |
675 | 192 | naked_build.log = lfa_id | 188 | # the scanner latency, it should really be asking the slave for |
676 | 193 | naked_build.builder = build.buildqueue_record.builder | 189 | # the duration spent building locally. |
677 | 194 | # XXX cprov 20060615 bug=120584: Currently buildduration | 190 | naked_build.date_finished = datetime.datetime.now(pytz.UTC) |
678 | 195 | # includes the scanner latency. It should really be asking | 191 | if slave_status.get('dependencies') is not None: |
679 | 196 | # the slave for the duration spent building locally. | 192 | build.dependencies = unicode(slave_status.get('dependencies')) |
680 | 197 | naked_build.date_finished = datetime.datetime.now(pytz.UTC) | 193 | else: |
681 | 198 | build.dependencies = dependencies | 194 | build.dependencies = None |
671 | 199 | transaction.commit() | ||
682 | 200 | 195 | ||
683 | 201 | d = build.getLogFromSlave(build) | 196 | d = build.getLogFromSlave(build) |
684 | 202 | return d.addCallback(got_log) | 197 | return d.addCallback(got_log) |
685 | @@ -297,41 +292,22 @@ | |||
686 | 297 | 292 | ||
687 | 298 | def handleStatus(self, status, librarian, slave_status): | 293 | def handleStatus(self, status, librarian, slave_status): |
688 | 299 | """See `IPackageBuild`.""" | 294 | """See `IPackageBuild`.""" |
689 | 300 | # Avoid circular imports. | ||
690 | 301 | from lp.buildmaster.manager import BUILDD_MANAGER_LOG_NAME | 295 | from lp.buildmaster.manager import BUILDD_MANAGER_LOG_NAME |
691 | 302 | |||
692 | 303 | logger = logging.getLogger(BUILDD_MANAGER_LOG_NAME) | 296 | logger = logging.getLogger(BUILDD_MANAGER_LOG_NAME) |
693 | 304 | send_notification = status in self.ALLOWED_STATUS_NOTIFICATIONS | 297 | send_notification = status in self.ALLOWED_STATUS_NOTIFICATIONS |
694 | 305 | method = getattr(self, '_handleStatus_' + status, None) | 298 | method = getattr(self, '_handleStatus_' + status, None) |
695 | 306 | if method is None: | 299 | if method is None: |
701 | 307 | logger.critical( | 300 | logger.critical("Unknown BuildStatus '%s' for builder '%s'" |
702 | 308 | "Unknown BuildStatus '%s' for builder '%s'", | 301 | % (status, self.buildqueue_record.builder.url)) |
703 | 309 | status, self.buildqueue_record.builder.url) | 302 | return |
699 | 310 | return None | ||
700 | 311 | |||
704 | 312 | d = method(librarian, slave_status, logger, send_notification) | 303 | d = method(librarian, slave_status, logger, send_notification) |
705 | 313 | return d | 304 | return d |
706 | 314 | 305 | ||
707 | 315 | def _destroy_buildqueue_record(self, unused_arg): | ||
708 | 316 | """Destroy this build's `BuildQueue` record.""" | ||
709 | 317 | transaction.commit() | ||
710 | 318 | with DatabaseTransactionPolicy(read_only=False): | ||
711 | 319 | self.buildqueue_record.destroySelf() | ||
712 | 320 | transaction.commit() | ||
713 | 321 | |||
714 | 322 | def _release_builder_and_remove_queue_item(self): | 306 | def _release_builder_and_remove_queue_item(self): |
715 | 323 | # Release the builder for another job. | 307 | # Release the builder for another job. |
716 | 324 | d = self.buildqueue_record.builder.cleanSlave() | 308 | d = self.buildqueue_record.builder.cleanSlave() |
717 | 325 | # Remove BuildQueue record. | 309 | # Remove BuildQueue record. |
727 | 326 | return d.addCallback(self._destroy_buildqueue_record) | 310 | return d.addCallback(lambda x: self.buildqueue_record.destroySelf()) |
719 | 327 | |||
720 | 328 | def _notify_if_appropriate(self, appropriate=True, extra_info=None): | ||
721 | 329 | """If `appropriate`, call `self.notify` in a write transaction.""" | ||
722 | 330 | if appropriate: | ||
723 | 331 | transaction.commit() | ||
724 | 332 | with DatabaseTransactionPolicy(read_only=False): | ||
725 | 333 | self.notify(extra_info=extra_info) | ||
726 | 334 | transaction.commit() | ||
728 | 335 | 311 | ||
729 | 336 | def _handleStatus_OK(self, librarian, slave_status, logger, | 312 | def _handleStatus_OK(self, librarian, slave_status, logger, |
730 | 337 | send_notification): | 313 | send_notification): |
731 | @@ -347,19 +323,16 @@ | |||
732 | 347 | self.buildqueue_record.specific_job.build.title, | 323 | self.buildqueue_record.specific_job.build.title, |
733 | 348 | self.buildqueue_record.builder.name)) | 324 | self.buildqueue_record.builder.name)) |
734 | 349 | 325 | ||
737 | 350 | # If this is a binary package build for a source that is no | 326 | # If this is a binary package build, discard it if its source is |
738 | 351 | # longer published, discard it. | 327 | # no longer published. |
739 | 352 | if self.build_farm_job_type == BuildFarmJobType.PACKAGEBUILD: | 328 | if self.build_farm_job_type == BuildFarmJobType.PACKAGEBUILD: |
740 | 353 | build = self.buildqueue_record.specific_job.build | 329 | build = self.buildqueue_record.specific_job.build |
741 | 354 | if not build.current_source_publication: | 330 | if not build.current_source_publication: |
746 | 355 | transaction.commit() | 331 | build.status = BuildStatus.SUPERSEDED |
743 | 356 | with DatabaseTransactionPolicy(read_only=False): | ||
744 | 357 | build.status = BuildStatus.SUPERSEDED | ||
745 | 358 | transaction.commit() | ||
747 | 359 | return self._release_builder_and_remove_queue_item() | 332 | return self._release_builder_and_remove_queue_item() |
748 | 360 | 333 | ||
751 | 361 | # Explode rather than collect a binary that is denied in this | 334 | # Explode before collect a binary that is denied in this |
752 | 362 | # distroseries/pocket. | 335 | # distroseries/pocket |
753 | 363 | if not self.archive.allowUpdatesToReleasePocket(): | 336 | if not self.archive.allowUpdatesToReleasePocket(): |
754 | 364 | assert self.distro_series.canUploadToPocket(self.pocket), ( | 337 | assert self.distro_series.canUploadToPocket(self.pocket), ( |
755 | 365 | "%s (%s) can not be built for pocket %s: illegal status" | 338 | "%s (%s) can not be built for pocket %s: illegal status" |
756 | @@ -404,26 +377,18 @@ | |||
757 | 404 | # files from the slave. | 377 | # files from the slave. |
758 | 405 | if successful_copy_from_slave: | 378 | if successful_copy_from_slave: |
759 | 406 | logger.info( | 379 | logger.info( |
763 | 407 | "Gathered %s %d completely. " | 380 | "Gathered %s %d completely. Moving %s to uploader queue." |
764 | 408 | "Moving %s to uploader queue.", | 381 | % (self.__class__.__name__, self.id, upload_leaf)) |
762 | 409 | self.__class__.__name__, self.id, upload_leaf) | ||
765 | 410 | target_dir = os.path.join(root, "incoming") | 382 | target_dir = os.path.join(root, "incoming") |
767 | 411 | resulting_status = BuildStatus.UPLOADING | 383 | self.status = BuildStatus.UPLOADING |
768 | 412 | else: | 384 | else: |
769 | 413 | logger.warning( | 385 | logger.warning( |
772 | 414 | "Copy from slave for build %s was unsuccessful.", | 386 | "Copy from slave for build %s was unsuccessful.", self.id) |
773 | 415 | self.id) | 387 | self.status = BuildStatus.FAILEDTOUPLOAD |
774 | 388 | if send_notification: | ||
775 | 389 | self.notify( | ||
776 | 390 | extra_info='Copy from slave was unsuccessful.') | ||
777 | 416 | target_dir = os.path.join(root, "failed") | 391 | target_dir = os.path.join(root, "failed") |
778 | 417 | resulting_status = BuildStatus.FAILEDTOUPLOAD | ||
779 | 418 | |||
780 | 419 | transaction.commit() | ||
781 | 420 | with DatabaseTransactionPolicy(read_only=False): | ||
782 | 421 | self.status = resulting_status | ||
783 | 422 | transaction.commit() | ||
784 | 423 | |||
785 | 424 | if not successful_copy_from_slave: | ||
786 | 425 | self._notify_if_appropriate( | ||
787 | 426 | send_notification, "Copy from slave was unsuccessful.") | ||
788 | 427 | 392 | ||
789 | 428 | if not os.path.exists(target_dir): | 393 | if not os.path.exists(target_dir): |
790 | 429 | os.mkdir(target_dir) | 394 | os.mkdir(target_dir) |
791 | @@ -431,6 +396,10 @@ | |||
792 | 431 | # Release the builder for another job. | 396 | # Release the builder for another job. |
793 | 432 | d = self._release_builder_and_remove_queue_item() | 397 | d = self._release_builder_and_remove_queue_item() |
794 | 433 | 398 | ||
795 | 399 | # Commit so there are no race conditions with archiveuploader | ||
796 | 400 | # about self.status. | ||
797 | 401 | Store.of(self).commit() | ||
798 | 402 | |||
799 | 434 | # Move the directory used to grab the binaries into | 403 | # Move the directory used to grab the binaries into |
800 | 435 | # the incoming directory so the upload processor never | 404 | # the incoming directory so the upload processor never |
801 | 436 | # sees half-finished uploads. | 405 | # sees half-finished uploads. |
802 | @@ -454,15 +423,14 @@ | |||
803 | 454 | set the job status as FAILEDTOBUILD, store available info and | 423 | set the job status as FAILEDTOBUILD, store available info and |
804 | 455 | remove Buildqueue entry. | 424 | remove Buildqueue entry. |
805 | 456 | """ | 425 | """ |
810 | 457 | transaction.commit() | 426 | self.status = BuildStatus.FAILEDTOBUILD |
807 | 458 | with DatabaseTransactionPolicy(read_only=False): | ||
808 | 459 | self.status = BuildStatus.FAILEDTOBUILD | ||
809 | 460 | transaction.commit() | ||
811 | 461 | 427 | ||
812 | 462 | def build_info_stored(ignored): | 428 | def build_info_stored(ignored): |
814 | 463 | self._notify_if_appropriate(send_notification) | 429 | if send_notification: |
815 | 430 | self.notify() | ||
816 | 464 | d = self.buildqueue_record.builder.cleanSlave() | 431 | d = self.buildqueue_record.builder.cleanSlave() |
818 | 465 | return d.addCallback(self._destroy_buildqueue_record) | 432 | return d.addCallback( |
819 | 433 | lambda x: self.buildqueue_record.destroySelf()) | ||
820 | 466 | 434 | ||
821 | 467 | d = self.storeBuildInfo(self, librarian, slave_status) | 435 | d = self.storeBuildInfo(self, librarian, slave_status) |
822 | 468 | return d.addCallback(build_info_stored) | 436 | return d.addCallback(build_info_stored) |
823 | @@ -480,9 +448,11 @@ | |||
824 | 480 | def build_info_stored(ignored): | 448 | def build_info_stored(ignored): |
825 | 481 | logger.critical("***** %s is MANUALDEPWAIT *****" | 449 | logger.critical("***** %s is MANUALDEPWAIT *****" |
826 | 482 | % self.buildqueue_record.builder.name) | 450 | % self.buildqueue_record.builder.name) |
828 | 483 | self._notify_if_appropriate(send_notification) | 451 | if send_notification: |
829 | 452 | self.notify() | ||
830 | 484 | d = self.buildqueue_record.builder.cleanSlave() | 453 | d = self.buildqueue_record.builder.cleanSlave() |
832 | 485 | return d.addCallback(self._destroy_buildqueue_record) | 454 | return d.addCallback( |
833 | 455 | lambda x: self.buildqueue_record.destroySelf()) | ||
834 | 486 | 456 | ||
835 | 487 | d = self.storeBuildInfo(self, librarian, slave_status) | 457 | d = self.storeBuildInfo(self, librarian, slave_status) |
836 | 488 | return d.addCallback(build_info_stored) | 458 | return d.addCallback(build_info_stored) |
837 | @@ -498,24 +468,17 @@ | |||
838 | 498 | self.status = BuildStatus.CHROOTWAIT | 468 | self.status = BuildStatus.CHROOTWAIT |
839 | 499 | 469 | ||
840 | 500 | def build_info_stored(ignored): | 470 | def build_info_stored(ignored): |
846 | 501 | logger.critical( | 471 | logger.critical("***** %s is CHROOTWAIT *****" % |
847 | 502 | "***** %s is CHROOTWAIT *****", | 472 | self.buildqueue_record.builder.name) |
848 | 503 | self.buildqueue_record.builder.name) | 473 | if send_notification: |
849 | 504 | 474 | self.notify() | |
845 | 505 | self._notify_if_appropriate(send_notification) | ||
850 | 506 | d = self.buildqueue_record.builder.cleanSlave() | 475 | d = self.buildqueue_record.builder.cleanSlave() |
852 | 507 | return d.addCallback(self._destroy_buildqueue_record) | 476 | return d.addCallback( |
853 | 477 | lambda x: self.buildqueue_record.destroySelf()) | ||
854 | 508 | 478 | ||
855 | 509 | d = self.storeBuildInfo(self, librarian, slave_status) | 479 | d = self.storeBuildInfo(self, librarian, slave_status) |
856 | 510 | return d.addCallback(build_info_stored) | 480 | return d.addCallback(build_info_stored) |
857 | 511 | 481 | ||
858 | 512 | def _reset_buildqueue_record(self, ignored_arg=None): | ||
859 | 513 | """Reset the `BuildQueue` record, in a write transaction.""" | ||
860 | 514 | transaction.commit() | ||
861 | 515 | with DatabaseTransactionPolicy(read_only=False): | ||
862 | 516 | self.buildqueue_record.reset() | ||
863 | 517 | transaction.commit() | ||
864 | 518 | |||
865 | 519 | def _handleStatus_BUILDERFAIL(self, librarian, slave_status, logger, | 482 | def _handleStatus_BUILDERFAIL(self, librarian, slave_status, logger, |
866 | 520 | send_notification): | 483 | send_notification): |
867 | 521 | """Handle builder failures. | 484 | """Handle builder failures. |
868 | @@ -529,8 +492,11 @@ | |||
869 | 529 | self.buildqueue_record.builder.failBuilder( | 492 | self.buildqueue_record.builder.failBuilder( |
870 | 530 | "Builder returned BUILDERFAIL when asked for its status") | 493 | "Builder returned BUILDERFAIL when asked for its status") |
871 | 531 | 494 | ||
872 | 495 | def build_info_stored(ignored): | ||
873 | 496 | # simply reset job | ||
874 | 497 | self.buildqueue_record.reset() | ||
875 | 532 | d = self.storeBuildInfo(self, librarian, slave_status) | 498 | d = self.storeBuildInfo(self, librarian, slave_status) |
877 | 533 | return d.addCallback(self._reset_buildqueue_record) | 499 | return d.addCallback(build_info_stored) |
878 | 534 | 500 | ||
879 | 535 | def _handleStatus_GIVENBACK(self, librarian, slave_status, logger, | 501 | def _handleStatus_GIVENBACK(self, librarian, slave_status, logger, |
880 | 536 | send_notification): | 502 | send_notification): |
881 | @@ -550,7 +516,7 @@ | |||
882 | 550 | # the next Paris Summit, infinity has some ideas about how | 516 | # the next Paris Summit, infinity has some ideas about how |
883 | 551 | # to use this content. For now we just ensure it's stored. | 517 | # to use this content. For now we just ensure it's stored. |
884 | 552 | d = self.buildqueue_record.builder.cleanSlave() | 518 | d = self.buildqueue_record.builder.cleanSlave() |
886 | 553 | self._reset_buildqueue_record() | 519 | self.buildqueue_record.reset() |
887 | 554 | return d | 520 | return d |
888 | 555 | 521 | ||
889 | 556 | d = self.storeBuildInfo(self, librarian, slave_status) | 522 | d = self.storeBuildInfo(self, librarian, slave_status) |
890 | 557 | 523 | ||
891 | === modified file 'lib/lp/buildmaster/tests/test_builder.py' | |||
892 | --- lib/lp/buildmaster/tests/test_builder.py 2011-12-13 13:33:04 +0000 | |||
893 | +++ lib/lp/buildmaster/tests/test_builder.py 2011-12-19 21:48:31 +0000 | |||
894 | @@ -1,4 +1,4 @@ | |||
896 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
897 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
898 | 3 | 3 | ||
899 | 4 | """Test Builder features.""" | 4 | """Test Builder features.""" |
900 | @@ -8,14 +8,13 @@ | |||
901 | 8 | import tempfile | 8 | import tempfile |
902 | 9 | import xmlrpclib | 9 | import xmlrpclib |
903 | 10 | 10 | ||
904 | 11 | from lpbuildd.slave import BuilderStatus | ||
905 | 12 | from testtools.deferredruntest import ( | 11 | from testtools.deferredruntest import ( |
906 | 13 | assert_fails_with, | 12 | assert_fails_with, |
907 | 14 | AsynchronousDeferredRunTest, | 13 | AsynchronousDeferredRunTest, |
908 | 15 | AsynchronousDeferredRunTestForBrokenTwisted, | 14 | AsynchronousDeferredRunTestForBrokenTwisted, |
909 | 16 | SynchronousDeferredRunTest, | 15 | SynchronousDeferredRunTest, |
910 | 17 | ) | 16 | ) |
912 | 18 | import transaction | 17 | |
913 | 19 | from twisted.internet.defer import ( | 18 | from twisted.internet.defer import ( |
914 | 20 | CancelledError, | 19 | CancelledError, |
915 | 21 | DeferredList, | 20 | DeferredList, |
916 | @@ -23,12 +22,15 @@ | |||
917 | 23 | from twisted.internet.task import Clock | 22 | from twisted.internet.task import Clock |
918 | 24 | from twisted.python.failure import Failure | 23 | from twisted.python.failure import Failure |
919 | 25 | from twisted.web.client import getPage | 24 | from twisted.web.client import getPage |
920 | 25 | |||
921 | 26 | from zope.component import getUtility | 26 | from zope.component import getUtility |
922 | 27 | from zope.security.proxy import ( | 27 | from zope.security.proxy import ( |
923 | 28 | isinstance as zope_isinstance, | 28 | isinstance as zope_isinstance, |
924 | 29 | removeSecurityProxy, | 29 | removeSecurityProxy, |
925 | 30 | ) | 30 | ) |
926 | 31 | 31 | ||
927 | 32 | from lpbuildd.slave import BuilderStatus | ||
928 | 33 | |||
929 | 32 | from canonical.config import config | 34 | from canonical.config import config |
930 | 33 | from canonical.database.sqlbase import flush_database_updates | 35 | from canonical.database.sqlbase import flush_database_updates |
931 | 34 | from canonical.launchpad.webapp.interfaces import ( | 36 | from canonical.launchpad.webapp.interfaces import ( |
932 | @@ -43,7 +45,6 @@ | |||
933 | 43 | from lp.buildmaster.enums import BuildStatus | 45 | from lp.buildmaster.enums import BuildStatus |
934 | 44 | from lp.buildmaster.interfaces.builder import ( | 46 | from lp.buildmaster.interfaces.builder import ( |
935 | 45 | CannotFetchFile, | 47 | CannotFetchFile, |
936 | 46 | CannotResumeHost, | ||
937 | 47 | IBuilder, | 48 | IBuilder, |
938 | 48 | IBuilderSet, | 49 | IBuilderSet, |
939 | 49 | ) | 50 | ) |
940 | @@ -51,6 +52,7 @@ | |||
941 | 51 | IBuildFarmJobBehavior, | 52 | IBuildFarmJobBehavior, |
942 | 52 | ) | 53 | ) |
943 | 53 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet | 54 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
944 | 55 | from lp.buildmaster.interfaces.builder import CannotResumeHost | ||
945 | 54 | from lp.buildmaster.model.builder import ( | 56 | from lp.buildmaster.model.builder import ( |
946 | 55 | BuilderSlave, | 57 | BuilderSlave, |
947 | 56 | ProxyWithConnectionTimeout, | 58 | ProxyWithConnectionTimeout, |
948 | @@ -72,8 +74,6 @@ | |||
949 | 72 | TrivialBehavior, | 74 | TrivialBehavior, |
950 | 73 | WaitingSlave, | 75 | WaitingSlave, |
951 | 74 | ) | 76 | ) |
952 | 75 | from lp.registry.interfaces.pocket import PackagePublishingPocket | ||
953 | 76 | from lp.services.database.transaction_policy import DatabaseTransactionPolicy | ||
954 | 77 | from lp.services.job.interfaces.job import JobStatus | 77 | from lp.services.job.interfaces.job import JobStatus |
955 | 78 | from lp.services.log.logger import BufferLogger | 78 | from lp.services.log.logger import BufferLogger |
956 | 79 | from lp.soyuz.enums import ( | 79 | from lp.soyuz.enums import ( |
957 | @@ -155,7 +155,7 @@ | |||
958 | 155 | d = lostbuilding_builder.updateStatus(BufferLogger()) | 155 | d = lostbuilding_builder.updateStatus(BufferLogger()) |
959 | 156 | def check_slave_status(failure): | 156 | def check_slave_status(failure): |
960 | 157 | self.assertIn('abort', slave.call_log) | 157 | self.assertIn('abort', slave.call_log) |
962 | 158 | # 'Fault' comes from the LostBuildingBrokenSlave. This is | 158 | # 'Fault' comes from the LostBuildingBrokenSlave, this is |
963 | 159 | # just testing that the value is passed through. | 159 | # just testing that the value is passed through. |
964 | 160 | self.assertIsInstance(failure.value, xmlrpclib.Fault) | 160 | self.assertIsInstance(failure.value, xmlrpclib.Fault) |
965 | 161 | return d.addBoth(check_slave_status) | 161 | return d.addBoth(check_slave_status) |
966 | @@ -534,26 +534,6 @@ | |||
967 | 534 | # And the old_candidate is superseded: | 534 | # And the old_candidate is superseded: |
968 | 535 | self.assertEqual(BuildStatus.SUPERSEDED, build.status) | 535 | self.assertEqual(BuildStatus.SUPERSEDED, build.status) |
969 | 536 | 536 | ||
970 | 537 | def test_findBuildCandidate_postprocesses_in_read_write_policy(self): | ||
971 | 538 | # _findBuildCandidate invokes BuildFarmJob.postprocessCandidate, | ||
972 | 539 | # which may modify the database. This happens in a read-write | ||
973 | 540 | # transaction even if _findBuildCandidate itself runs in a | ||
974 | 541 | # read-only transaction policy. | ||
975 | 542 | |||
976 | 543 | # PackageBuildJob.postprocessCandidate will attempt to delete | ||
977 | 544 | # security builds. | ||
978 | 545 | pub = self.publisher.getPubSource( | ||
979 | 546 | sourcename="gedit", status=PackagePublishingStatus.PUBLISHED, | ||
980 | 547 | archive=self.factory.makeArchive(), | ||
981 | 548 | pocket=PackagePublishingPocket.SECURITY) | ||
982 | 549 | pub.createMissingBuilds() | ||
983 | 550 | transaction.commit() | ||
984 | 551 | with DatabaseTransactionPolicy(read_only=True): | ||
985 | 552 | removeSecurityProxy(self.frog_builder)._findBuildCandidate() | ||
986 | 553 | # The test is that this passes without a "transaction is | ||
987 | 554 | # read-only" error. | ||
988 | 555 | transaction.commit() | ||
989 | 556 | |||
990 | 557 | def test_acquireBuildCandidate_marks_building(self): | 537 | def test_acquireBuildCandidate_marks_building(self): |
991 | 558 | # acquireBuildCandidate() should call _findBuildCandidate and | 538 | # acquireBuildCandidate() should call _findBuildCandidate and |
992 | 559 | # mark the build as building. | 539 | # mark the build as building. |
993 | 560 | 540 | ||
994 | === modified file 'lib/lp/buildmaster/tests/test_manager.py' | |||
995 | --- lib/lp/buildmaster/tests/test_manager.py 2011-12-08 11:51:59 +0000 | |||
996 | +++ lib/lp/buildmaster/tests/test_manager.py 2011-12-19 21:48:31 +0000 | |||
997 | @@ -1,36 +1,40 @@ | |||
999 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
1000 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1001 | 3 | 3 | ||
1002 | 4 | """Tests for the renovated slave scanner aka BuilddManager.""" | 4 | """Tests for the renovated slave scanner aka BuilddManager.""" |
1003 | 5 | 5 | ||
1004 | 6 | from collections import namedtuple | ||
1005 | 7 | import os | 6 | import os |
1006 | 8 | import signal | 7 | import signal |
1007 | 9 | import time | 8 | import time |
1008 | 10 | import xmlrpclib | 9 | import xmlrpclib |
1009 | 11 | 10 | ||
1010 | 12 | from lpbuildd.tests import BuilddSlaveTestSetup | ||
1011 | 13 | from testtools.deferredruntest import ( | 11 | from testtools.deferredruntest import ( |
1012 | 14 | assert_fails_with, | 12 | assert_fails_with, |
1013 | 15 | AsynchronousDeferredRunTest, | 13 | AsynchronousDeferredRunTest, |
1014 | 16 | ) | 14 | ) |
1015 | 15 | |||
1016 | 17 | import transaction | 16 | import transaction |
1017 | 17 | |||
1018 | 18 | from twisted.internet import ( | 18 | from twisted.internet import ( |
1019 | 19 | defer, | 19 | defer, |
1020 | 20 | reactor, | 20 | reactor, |
1021 | 21 | task, | 21 | task, |
1022 | 22 | ) | 22 | ) |
1024 | 23 | from twisted.internet.task import deferLater | 23 | from twisted.internet.task import ( |
1025 | 24 | deferLater, | ||
1026 | 25 | ) | ||
1027 | 24 | from twisted.python.failure import Failure | 26 | from twisted.python.failure import Failure |
1028 | 25 | from zope.component import getUtility | 27 | from zope.component import getUtility |
1029 | 26 | from zope.security.proxy import removeSecurityProxy | 28 | from zope.security.proxy import removeSecurityProxy |
1030 | 27 | 29 | ||
1031 | 30 | from lpbuildd.tests import BuilddSlaveTestSetup | ||
1032 | 31 | |||
1033 | 28 | from canonical.config import config | 32 | from canonical.config import config |
1034 | 29 | from canonical.database.constants import UTC_NOW | ||
1035 | 30 | from canonical.launchpad.ftests import ( | 33 | from canonical.launchpad.ftests import ( |
1036 | 31 | ANONYMOUS, | 34 | ANONYMOUS, |
1037 | 32 | login, | 35 | login, |
1038 | 33 | ) | 36 | ) |
1039 | 37 | from lp.services.log.logger import BufferLogger | ||
1040 | 34 | from canonical.testing.layers import ( | 38 | from canonical.testing.layers import ( |
1041 | 35 | LaunchpadScriptLayer, | 39 | LaunchpadScriptLayer, |
1042 | 36 | LaunchpadZopelessLayer, | 40 | LaunchpadZopelessLayer, |
1043 | @@ -46,18 +50,14 @@ | |||
1044 | 46 | SlaveScanner, | 50 | SlaveScanner, |
1045 | 47 | ) | 51 | ) |
1046 | 48 | from lp.buildmaster.model.builder import Builder | 52 | from lp.buildmaster.model.builder import Builder |
1047 | 49 | from lp.buildmaster.model.packagebuild import PackageBuild | ||
1048 | 50 | from lp.buildmaster.tests.harness import BuilddManagerTestSetup | 53 | from lp.buildmaster.tests.harness import BuilddManagerTestSetup |
1049 | 51 | from lp.buildmaster.tests.mock_slaves import ( | 54 | from lp.buildmaster.tests.mock_slaves import ( |
1050 | 52 | BrokenSlave, | 55 | BrokenSlave, |
1051 | 53 | BuildingSlave, | 56 | BuildingSlave, |
1052 | 54 | make_publisher, | 57 | make_publisher, |
1053 | 55 | OkSlave, | 58 | OkSlave, |
1054 | 56 | WaitingSlave, | ||
1055 | 57 | ) | 59 | ) |
1056 | 58 | from lp.registry.interfaces.distribution import IDistributionSet | 60 | from lp.registry.interfaces.distribution import IDistributionSet |
1057 | 59 | from lp.services.database.transaction_policy import DatabaseTransactionPolicy | ||
1058 | 60 | from lp.services.log.logger import BufferLogger | ||
1059 | 61 | from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet | 61 | from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet |
1060 | 62 | from lp.testing import ( | 62 | from lp.testing import ( |
1061 | 63 | TestCase, | 63 | TestCase, |
1062 | @@ -65,13 +65,10 @@ | |||
1063 | 65 | ) | 65 | ) |
1064 | 66 | from lp.testing.factory import LaunchpadObjectFactory | 66 | from lp.testing.factory import LaunchpadObjectFactory |
1065 | 67 | from lp.testing.fakemethod import FakeMethod | 67 | from lp.testing.fakemethod import FakeMethod |
1073 | 68 | from lp.testing.sampledata import ( | 68 | from lp.testing.sampledata import BOB_THE_BUILDER_NAME |
1074 | 69 | BOB_THE_BUILDER_NAME, | 69 | |
1075 | 70 | FROG_THE_BUILDER_NAME, | 70 | |
1076 | 71 | ) | 71 | class TestSlaveScannerScan(TestCase): |
1070 | 72 | |||
1071 | 73 | |||
1072 | 74 | class TestSlaveScannerScan(TestCaseWithFactory): | ||
1077 | 75 | """Tests `SlaveScanner.scan` method. | 72 | """Tests `SlaveScanner.scan` method. |
1078 | 76 | 73 | ||
1079 | 77 | This method uses the old framework for scanning and dispatching builds. | 74 | This method uses the old framework for scanning and dispatching builds. |
1080 | @@ -86,8 +83,6 @@ | |||
1081 | 86 | 'bob' builder. | 83 | 'bob' builder. |
1082 | 87 | """ | 84 | """ |
1083 | 88 | super(TestSlaveScannerScan, self).setUp() | 85 | super(TestSlaveScannerScan, self).setUp() |
1084 | 89 | self.read_only = DatabaseTransactionPolicy(read_only=True) | ||
1085 | 90 | |||
1086 | 91 | # Creating the required chroots needed for dispatching. | 86 | # Creating the required chroots needed for dispatching. |
1087 | 92 | test_publisher = make_publisher() | 87 | test_publisher = make_publisher() |
1088 | 93 | ubuntu = getUtility(IDistributionSet).getByName('ubuntu') | 88 | ubuntu = getUtility(IDistributionSet).getByName('ubuntu') |
1089 | @@ -95,15 +90,6 @@ | |||
1090 | 95 | test_publisher.setUpDefaultDistroSeries(hoary) | 90 | test_publisher.setUpDefaultDistroSeries(hoary) |
1091 | 96 | test_publisher.addFakeChroots() | 91 | test_publisher.addFakeChroots() |
1092 | 97 | 92 | ||
1093 | 98 | def _enterReadOnly(self): | ||
1094 | 99 | """Go into read-only transaction policy.""" | ||
1095 | 100 | self.read_only.__enter__() | ||
1096 | 101 | self.addCleanup(self._exitReadOnly) | ||
1097 | 102 | |||
1098 | 103 | def _exitReadOnly(self): | ||
1099 | 104 | """Leave read-only transaction policy.""" | ||
1100 | 105 | self.read_only.__exit__(None, None, None) | ||
1101 | 106 | |||
1102 | 107 | def _resetBuilder(self, builder): | 93 | def _resetBuilder(self, builder): |
1103 | 108 | """Reset the given builder and its job.""" | 94 | """Reset the given builder and its job.""" |
1104 | 109 | 95 | ||
1105 | @@ -114,23 +100,6 @@ | |||
1106 | 114 | 100 | ||
1107 | 115 | transaction.commit() | 101 | transaction.commit() |
1108 | 116 | 102 | ||
1109 | 117 | def getFreshBuilder(self, slave=None, name=BOB_THE_BUILDER_NAME, | ||
1110 | 118 | failure_count=0): | ||
1111 | 119 | """Return a builder. | ||
1112 | 120 | |||
1113 | 121 | The builder is taken from sample data, but reset to a usable state. | ||
1114 | 122 | Be careful: this is not a proper factory method. Identical calls | ||
1115 | 123 | return (and reset) the same builder. Don't rely on that though; | ||
1116 | 124 | maybe someday we'll have a proper factory here. | ||
1117 | 125 | """ | ||
1118 | 126 | if slave is None: | ||
1119 | 127 | slave = OkSlave() | ||
1120 | 128 | builder = getUtility(IBuilderSet)[name] | ||
1121 | 129 | self._resetBuilder(builder) | ||
1122 | 130 | builder.setSlaveForTesting(slave) | ||
1123 | 131 | builder.failure_count = failure_count | ||
1124 | 132 | return builder | ||
1125 | 133 | |||
1126 | 134 | def assertBuildingJob(self, job, builder, logtail=None): | 103 | def assertBuildingJob(self, job, builder, logtail=None): |
1127 | 135 | """Assert the given job is building on the given builder.""" | 104 | """Assert the given job is building on the given builder.""" |
1128 | 136 | from lp.services.job.interfaces.job import JobStatus | 105 | from lp.services.job.interfaces.job import JobStatus |
1129 | @@ -145,14 +114,14 @@ | |||
1130 | 145 | self.assertEqual(build.status, BuildStatus.BUILDING) | 114 | self.assertEqual(build.status, BuildStatus.BUILDING) |
1131 | 146 | self.assertEqual(job.logtail, logtail) | 115 | self.assertEqual(job.logtail, logtail) |
1132 | 147 | 116 | ||
1134 | 148 | def _getScanner(self, builder_name=None, clock=None): | 117 | def _getScanner(self, builder_name=None): |
1135 | 149 | """Instantiate a SlaveScanner object. | 118 | """Instantiate a SlaveScanner object. |
1136 | 150 | 119 | ||
1137 | 151 | Replace its default logging handler by a testing version. | 120 | Replace its default logging handler by a testing version. |
1138 | 152 | """ | 121 | """ |
1139 | 153 | if builder_name is None: | 122 | if builder_name is None: |
1140 | 154 | builder_name = BOB_THE_BUILDER_NAME | 123 | builder_name = BOB_THE_BUILDER_NAME |
1142 | 155 | scanner = SlaveScanner(builder_name, BufferLogger(), clock=clock) | 124 | scanner = SlaveScanner(builder_name, BufferLogger()) |
1143 | 156 | scanner.logger.name = 'slave-scanner' | 125 | scanner.logger.name = 'slave-scanner' |
1144 | 157 | 126 | ||
1145 | 158 | return scanner | 127 | return scanner |
1146 | @@ -168,15 +137,17 @@ | |||
1147 | 168 | def testScanDispatchForResetBuilder(self): | 137 | def testScanDispatchForResetBuilder(self): |
1148 | 169 | # A job gets dispatched to the sampledata builder after it's reset. | 138 | # A job gets dispatched to the sampledata builder after it's reset. |
1149 | 170 | 139 | ||
1154 | 171 | # Obtain a builder. Initialize failure count to 1 so that | 140 | # Reset sampledata builder. |
1155 | 172 | # _checkDispatch can make sure that a successful dispatch resets | 141 | builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME] |
1156 | 173 | # the count to 0. | 142 | self._resetBuilder(builder) |
1157 | 174 | builder = self.getFreshBuilder(failure_count=1) | 143 | builder.setSlaveForTesting(OkSlave()) |
1158 | 144 | # Set this to 1 here so that _checkDispatch can make sure it's | ||
1159 | 145 | # reset to 0 after a successful dispatch. | ||
1160 | 146 | builder.failure_count = 1 | ||
1161 | 175 | 147 | ||
1162 | 176 | # Run 'scan' and check its result. | 148 | # Run 'scan' and check its result. |
1163 | 177 | self.layer.txn.commit() | 149 | self.layer.txn.commit() |
1164 | 178 | self.layer.switchDbUser(config.builddmaster.dbuser) | 150 | self.layer.switchDbUser(config.builddmaster.dbuser) |
1165 | 179 | self._enterReadOnly() | ||
1166 | 180 | scanner = self._getScanner() | 151 | scanner = self._getScanner() |
1167 | 181 | d = defer.maybeDeferred(scanner.scan) | 152 | d = defer.maybeDeferred(scanner.scan) |
1168 | 182 | d.addCallback(self._checkDispatch, builder) | 153 | d.addCallback(self._checkDispatch, builder) |
1169 | @@ -189,18 +160,20 @@ | |||
1170 | 189 | to the asynchonous dispatcher and the builder remained active | 160 | to the asynchonous dispatcher and the builder remained active |
1171 | 190 | and IDLE. | 161 | and IDLE. |
1172 | 191 | """ | 162 | """ |
1174 | 192 | self.assertIs(None, slave, "Unexpected slave.") | 163 | self.assertTrue(slave is None, "Unexpected slave.") |
1175 | 193 | 164 | ||
1176 | 194 | builder = getUtility(IBuilderSet).get(builder.id) | 165 | builder = getUtility(IBuilderSet).get(builder.id) |
1177 | 195 | self.assertTrue(builder.builderok) | 166 | self.assertTrue(builder.builderok) |
1179 | 196 | self.assertIs(None, builder.currentjob) | 167 | self.assertTrue(builder.currentjob is None) |
1180 | 197 | 168 | ||
1181 | 198 | def testNoDispatchForMissingChroots(self): | 169 | def testNoDispatchForMissingChroots(self): |
1182 | 199 | # When a required chroot is not present the `scan` method | 170 | # When a required chroot is not present the `scan` method |
1183 | 200 | # should not return any `RecordingSlaves` to be processed | 171 | # should not return any `RecordingSlaves` to be processed |
1184 | 201 | # and the builder used should remain active and IDLE. | 172 | # and the builder used should remain active and IDLE. |
1185 | 202 | 173 | ||
1187 | 203 | builder = self.getFreshBuilder() | 174 | # Reset sampledata builder. |
1188 | 175 | builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME] | ||
1189 | 176 | self._resetBuilder(builder) | ||
1190 | 204 | 177 | ||
1191 | 205 | # Remove hoary/i386 chroot. | 178 | # Remove hoary/i386 chroot. |
1192 | 206 | login('foo.bar@canonical.com') | 179 | login('foo.bar@canonical.com') |
1193 | @@ -213,7 +186,6 @@ | |||
1194 | 213 | 186 | ||
1195 | 214 | # Run 'scan' and check its result. | 187 | # Run 'scan' and check its result. |
1196 | 215 | self.layer.switchDbUser(config.builddmaster.dbuser) | 188 | self.layer.switchDbUser(config.builddmaster.dbuser) |
1197 | 216 | self._enterReadOnly() | ||
1198 | 217 | scanner = self._getScanner() | 189 | scanner = self._getScanner() |
1199 | 218 | d = defer.maybeDeferred(scanner.singleCycle) | 190 | d = defer.maybeDeferred(scanner.singleCycle) |
1200 | 219 | d.addCallback(self._checkNoDispatch, builder) | 191 | d.addCallback(self._checkNoDispatch, builder) |
1201 | @@ -255,7 +227,6 @@ | |||
1202 | 255 | 227 | ||
1203 | 256 | # Run 'scan' and check its result. | 228 | # Run 'scan' and check its result. |
1204 | 257 | self.layer.switchDbUser(config.builddmaster.dbuser) | 229 | self.layer.switchDbUser(config.builddmaster.dbuser) |
1205 | 258 | self._enterReadOnly() | ||
1206 | 259 | scanner = self._getScanner() | 230 | scanner = self._getScanner() |
1207 | 260 | d = defer.maybeDeferred(scanner.scan) | 231 | d = defer.maybeDeferred(scanner.scan) |
1208 | 261 | d.addCallback(self._checkJobRescued, builder, job) | 232 | d.addCallback(self._checkJobRescued, builder, job) |
1209 | @@ -291,27 +262,25 @@ | |||
1210 | 291 | 262 | ||
1211 | 292 | # Run 'scan' and check its result. | 263 | # Run 'scan' and check its result. |
1212 | 293 | self.layer.switchDbUser(config.builddmaster.dbuser) | 264 | self.layer.switchDbUser(config.builddmaster.dbuser) |
1213 | 294 | self._enterReadOnly() | ||
1214 | 295 | scanner = self._getScanner() | 265 | scanner = self._getScanner() |
1215 | 296 | d = defer.maybeDeferred(scanner.scan) | 266 | d = defer.maybeDeferred(scanner.scan) |
1216 | 297 | d.addCallback(self._checkJobUpdated, builder, job) | 267 | d.addCallback(self._checkJobUpdated, builder, job) |
1217 | 298 | return d | 268 | return d |
1218 | 299 | 269 | ||
1219 | 300 | def test_scan_with_nothing_to_dispatch(self): | 270 | def test_scan_with_nothing_to_dispatch(self): |
1221 | 301 | builder = self.factory.makeBuilder() | 271 | factory = LaunchpadObjectFactory() |
1222 | 272 | builder = factory.makeBuilder() | ||
1223 | 302 | builder.setSlaveForTesting(OkSlave()) | 273 | builder.setSlaveForTesting(OkSlave()) |
1224 | 303 | transaction.commit() | ||
1225 | 304 | self._enterReadOnly() | ||
1226 | 305 | scanner = self._getScanner(builder_name=builder.name) | 274 | scanner = self._getScanner(builder_name=builder.name) |
1227 | 306 | d = scanner.scan() | 275 | d = scanner.scan() |
1228 | 307 | return d.addCallback(self._checkNoDispatch, builder) | 276 | return d.addCallback(self._checkNoDispatch, builder) |
1229 | 308 | 277 | ||
1230 | 309 | def test_scan_with_manual_builder(self): | 278 | def test_scan_with_manual_builder(self): |
1231 | 310 | # Reset sampledata builder. | 279 | # Reset sampledata builder. |
1233 | 311 | builder = self.getFreshBuilder() | 280 | builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME] |
1234 | 281 | self._resetBuilder(builder) | ||
1235 | 282 | builder.setSlaveForTesting(OkSlave()) | ||
1236 | 312 | builder.manual = True | 283 | builder.manual = True |
1237 | 313 | transaction.commit() | ||
1238 | 314 | self._enterReadOnly() | ||
1239 | 315 | scanner = self._getScanner() | 284 | scanner = self._getScanner() |
1240 | 316 | d = scanner.scan() | 285 | d = scanner.scan() |
1241 | 317 | d.addCallback(self._checkNoDispatch, builder) | 286 | d.addCallback(self._checkNoDispatch, builder) |
1242 | @@ -319,10 +288,10 @@ | |||
1243 | 319 | 288 | ||
1244 | 320 | def test_scan_with_not_ok_builder(self): | 289 | def test_scan_with_not_ok_builder(self): |
1245 | 321 | # Reset sampledata builder. | 290 | # Reset sampledata builder. |
1247 | 322 | builder = self.getFreshBuilder() | 291 | builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME] |
1248 | 292 | self._resetBuilder(builder) | ||
1249 | 293 | builder.setSlaveForTesting(OkSlave()) | ||
1250 | 323 | builder.builderok = False | 294 | builder.builderok = False |
1251 | 324 | transaction.commit() | ||
1252 | 325 | self._enterReadOnly() | ||
1253 | 326 | scanner = self._getScanner() | 295 | scanner = self._getScanner() |
1254 | 327 | d = scanner.scan() | 296 | d = scanner.scan() |
1255 | 328 | # Because the builder is not ok, we can't use _checkNoDispatch. | 297 | # Because the builder is not ok, we can't use _checkNoDispatch. |
1256 | @@ -331,27 +300,25 @@ | |||
1257 | 331 | return d | 300 | return d |
1258 | 332 | 301 | ||
1259 | 333 | def test_scan_of_broken_slave(self): | 302 | def test_scan_of_broken_slave(self): |
1263 | 334 | builder = self.getFreshBuilder(slave=BrokenSlave()) | 303 | builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME] |
1264 | 335 | transaction.commit() | 304 | self._resetBuilder(builder) |
1265 | 336 | self._enterReadOnly() | 305 | builder.setSlaveForTesting(BrokenSlave()) |
1266 | 306 | builder.failure_count = 0 | ||
1267 | 337 | scanner = self._getScanner(builder_name=builder.name) | 307 | scanner = self._getScanner(builder_name=builder.name) |
1268 | 338 | d = scanner.scan() | 308 | d = scanner.scan() |
1269 | 339 | return assert_fails_with(d, xmlrpclib.Fault) | 309 | return assert_fails_with(d, xmlrpclib.Fault) |
1270 | 340 | 310 | ||
1271 | 341 | def _assertFailureCounting(self, builder_count, job_count, | 311 | def _assertFailureCounting(self, builder_count, job_count, |
1272 | 342 | expected_builder_count, expected_job_count): | 312 | expected_builder_count, expected_job_count): |
1273 | 343 | # Avoid circular imports. | ||
1274 | 344 | from lp.buildmaster import manager as manager_module | ||
1275 | 345 | |||
1276 | 346 | # If scan() fails with an exception, failure_counts should be | 313 | # If scan() fails with an exception, failure_counts should be |
1277 | 347 | # incremented. What we do with the results of the failure | 314 | # incremented. What we do with the results of the failure |
1278 | 348 | # counts is tested below separately, this test just makes sure that | 315 | # counts is tested below separately, this test just makes sure that |
1279 | 349 | # scan() is setting the counts. | 316 | # scan() is setting the counts. |
1280 | 350 | def failing_scan(): | 317 | def failing_scan(): |
1281 | 351 | return defer.fail(Exception("fake exception")) | 318 | return defer.fail(Exception("fake exception")) |
1282 | 352 | |||
1283 | 353 | scanner = self._getScanner() | 319 | scanner = self._getScanner() |
1284 | 354 | scanner.scan = failing_scan | 320 | scanner.scan = failing_scan |
1285 | 321 | from lp.buildmaster import manager as manager_module | ||
1286 | 355 | self.patch(manager_module, 'assessFailureCounts', FakeMethod()) | 322 | self.patch(manager_module, 'assessFailureCounts', FakeMethod()) |
1287 | 356 | builder = getUtility(IBuilderSet)[scanner.builder_name] | 323 | builder = getUtility(IBuilderSet)[scanner.builder_name] |
1288 | 357 | 324 | ||
1289 | @@ -499,60 +466,6 @@ | |||
1290 | 499 | d.addCallback(check_cancelled, builder, buildqueue) | 466 | d.addCallback(check_cancelled, builder, buildqueue) |
1291 | 500 | return d | 467 | return d |
1292 | 501 | 468 | ||
1293 | 502 | def makeFakeFailure(self): | ||
1294 | 503 | """Produce a fake failure for use with SlaveScanner._scanFailed.""" | ||
1295 | 504 | FakeFailure = namedtuple('FakeFailure', ['getErrorMessage', 'check']) | ||
1296 | 505 | return FakeFailure( | ||
1297 | 506 | FakeMethod(self.factory.getUniqueString()), | ||
1298 | 507 | FakeMethod(True)) | ||
1299 | 508 | |||
1300 | 509 | def test_interleaved_success_and_failure_do_not_interfere(self): | ||
1301 | 510 | # It's possible for one builder to fail while another continues | ||
1302 | 511 | # to function properly. When that happens, the failed builder | ||
1303 | 512 | # may cause database changes to be rolled back. But that does | ||
1304 | 513 | # not affect the functioning builder. | ||
1305 | 514 | clock = task.Clock() | ||
1306 | 515 | |||
1307 | 516 | broken_builder = self.getFreshBuilder( | ||
1308 | 517 | slave=BrokenSlave(), name=BOB_THE_BUILDER_NAME) | ||
1309 | 518 | broken_scanner = self._getScanner(builder_name=broken_builder.name) | ||
1310 | 519 | good_builder = self.getFreshBuilder( | ||
1311 | 520 | slave=WaitingSlave(), name=FROG_THE_BUILDER_NAME) | ||
1312 | 521 | good_build = self.factory.makeBinaryPackageBuild( | ||
1313 | 522 | distroarchseries=self.factory.makeDistroArchSeries()) | ||
1314 | 523 | |||
1315 | 524 | # The good build is being handled by the good builder. | ||
1316 | 525 | buildqueue = good_build.queueBuild() | ||
1317 | 526 | buildqueue.builder = good_builder | ||
1318 | 527 | |||
1319 | 528 | removeSecurityProxy(good_build.build_farm_job).date_started = UTC_NOW | ||
1320 | 529 | |||
1321 | 530 | # The good builder requests information from a successful build, | ||
1322 | 531 | # and up receiving it, updates the build's metadata. | ||
1323 | 532 | # Our dependencies string goes into the build, and its | ||
1324 | 533 | # date_finished will be set. | ||
1325 | 534 | dependencies = self.factory.getUniqueString() | ||
1326 | 535 | PackageBuild.storeBuildInfo( | ||
1327 | 536 | good_build, None, {'dependencies': dependencies}) | ||
1328 | 537 | clock.advance(1) | ||
1329 | 538 | |||
1330 | 539 | # The broken scanner experiences a failure before the good | ||
1331 | 540 | # scanner is receiving its data. This aborts the ongoing | ||
1332 | 541 | # transaction. | ||
1333 | 542 | # As a somewhat weird example, if the builder changed its own | ||
1334 | 543 | # title, that change will be rolled back. | ||
1335 | 544 | original_broken_builder_title = broken_builder.title | ||
1336 | 545 | broken_builder.title = self.factory.getUniqueString() | ||
1337 | 546 | broken_scanner._scanFailed(self.makeFakeFailure()) | ||
1338 | 547 | |||
1339 | 548 | # The work done by the good scanner is retained. The | ||
1340 | 549 | # storeBuildInfo code committed it. | ||
1341 | 550 | self.assertEqual(dependencies, good_build.dependencies) | ||
1342 | 551 | self.assertIsNot(None, good_build.date_finished) | ||
1343 | 552 | |||
1344 | 553 | # The work done by the broken scanner is rolled back. | ||
1345 | 554 | self.assertEqual(original_broken_builder_title, broken_builder.title) | ||
1346 | 555 | |||
1347 | 556 | 469 | ||
1348 | 557 | class TestCancellationChecking(TestCaseWithFactory): | 470 | class TestCancellationChecking(TestCaseWithFactory): |
1349 | 558 | """Unit tests for the checkCancellation method.""" | 471 | """Unit tests for the checkCancellation method.""" |
1350 | 559 | 472 | ||
1351 | === modified file 'lib/lp/buildmaster/tests/test_packagebuild.py' | |||
1352 | --- lib/lp/buildmaster/tests/test_packagebuild.py 2011-11-09 11:50:17 +0000 | |||
1353 | +++ lib/lp/buildmaster/tests/test_packagebuild.py 2011-12-19 21:48:31 +0000 | |||
1354 | @@ -1,4 +1,4 @@ | |||
1356 | 1 | # Copyright 2010-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
1357 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1358 | 3 | 3 | ||
1359 | 4 | """Tests for `IPackageBuild`.""" | 4 | """Tests for `IPackageBuild`.""" |
1360 | @@ -22,7 +22,9 @@ | |||
1361 | 22 | LaunchpadFunctionalLayer, | 22 | LaunchpadFunctionalLayer, |
1362 | 23 | LaunchpadZopelessLayer, | 23 | LaunchpadZopelessLayer, |
1363 | 24 | ) | 24 | ) |
1365 | 25 | from lp.archiveuploader.uploadprocessor import parse_build_upload_leaf_name | 25 | from lp.archiveuploader.uploadprocessor import ( |
1366 | 26 | parse_build_upload_leaf_name, | ||
1367 | 27 | ) | ||
1368 | 26 | from lp.buildmaster.enums import ( | 28 | from lp.buildmaster.enums import ( |
1369 | 27 | BuildFarmJobType, | 29 | BuildFarmJobType, |
1370 | 28 | BuildStatus, | 30 | BuildStatus, |
1371 | @@ -32,11 +34,12 @@ | |||
1372 | 32 | IPackageBuildSet, | 34 | IPackageBuildSet, |
1373 | 33 | IPackageBuildSource, | 35 | IPackageBuildSource, |
1374 | 34 | ) | 36 | ) |
1375 | 35 | from lp.buildmaster.model.builder import BuilderSlave | ||
1376 | 36 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob | 37 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
1377 | 37 | from lp.buildmaster.model.packagebuild import PackageBuild | 38 | from lp.buildmaster.model.packagebuild import PackageBuild |
1378 | 38 | from lp.buildmaster.tests.mock_slaves import WaitingSlave | 39 | from lp.buildmaster.tests.mock_slaves import WaitingSlave |
1380 | 39 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 40 | from lp.registry.interfaces.pocket import ( |
1381 | 41 | PackagePublishingPocket, | ||
1382 | 42 | ) | ||
1383 | 40 | from lp.testing import ( | 43 | from lp.testing import ( |
1384 | 41 | login, | 44 | login, |
1385 | 42 | login_person, | 45 | login_person, |
1386 | @@ -282,7 +285,10 @@ | |||
1387 | 282 | 285 | ||
1388 | 283 | 286 | ||
1389 | 284 | class TestHandleStatusMixin: | 287 | class TestHandleStatusMixin: |
1391 | 285 | """Tests for `IPackageBuild`s handleStatus method.""" | 288 | """Tests for `IPackageBuild`s handleStatus method. |
1392 | 289 | |||
1393 | 290 | This should be run with a Trial TestCase. | ||
1394 | 291 | """ | ||
1395 | 286 | 292 | ||
1396 | 287 | layer = LaunchpadZopelessLayer | 293 | layer = LaunchpadZopelessLayer |
1397 | 288 | 294 | ||
1398 | @@ -301,7 +307,7 @@ | |||
1399 | 301 | self.build.buildqueue_record.setDateStarted(UTC_NOW) | 307 | self.build.buildqueue_record.setDateStarted(UTC_NOW) |
1400 | 302 | self.slave = WaitingSlave('BuildStatus.OK') | 308 | self.slave = WaitingSlave('BuildStatus.OK') |
1401 | 303 | self.slave.valid_file_hashes.append('test_file_hash') | 309 | self.slave.valid_file_hashes.append('test_file_hash') |
1403 | 304 | self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(self.slave)) | 310 | builder.setSlaveForTesting(self.slave) |
1404 | 305 | 311 | ||
1405 | 306 | # We overwrite the buildmaster root to use a temp directory. | 312 | # We overwrite the buildmaster root to use a temp directory. |
1406 | 307 | tempdir = tempfile.mkdtemp() | 313 | tempdir = tempfile.mkdtemp() |
1407 | @@ -342,7 +348,7 @@ | |||
1408 | 342 | def got_status(ignored): | 348 | def got_status(ignored): |
1409 | 343 | self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status) | 349 | self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status) |
1410 | 344 | self.assertResultCount(0, "failed") | 350 | self.assertResultCount(0, "failed") |
1412 | 345 | self.assertIs(None, self.build.buildqueue_record) | 351 | self.assertIdentical(None, self.build.buildqueue_record) |
1413 | 346 | 352 | ||
1414 | 347 | d = self.build.handleStatus('OK', None, { | 353 | d = self.build.handleStatus('OK', None, { |
1415 | 348 | 'filemap': {'/tmp/myfile.py': 'test_file_hash'}, | 354 | 'filemap': {'/tmp/myfile.py': 'test_file_hash'}, |
1416 | @@ -384,10 +390,14 @@ | |||
1417 | 384 | 390 | ||
1418 | 385 | def got_status(ignored): | 391 | def got_status(ignored): |
1419 | 386 | if expected_notification: | 392 | if expected_notification: |
1422 | 387 | self.assertNotEqual( | 393 | self.failIf( |
1423 | 388 | 0, len(pop_notifications()), "No notifications received.") | 394 | len(pop_notifications()) == 0, |
1424 | 395 | "No notifications received") | ||
1425 | 389 | else: | 396 | else: |
1427 | 390 | self.assertContentEqual([], pop_notifications()) | 397 | self.failIf( |
1428 | 398 | len(pop_notifications()) > 0, | ||
1429 | 399 | "Notifications received") | ||
1430 | 400 | |||
1431 | 391 | d = self.build.handleStatus(status, None, {}) | 401 | d = self.build.handleStatus(status, None, {}) |
1432 | 392 | return d.addCallback(got_status) | 402 | return d.addCallback(got_status) |
1433 | 393 | 403 | ||
1434 | 394 | 404 | ||
1435 | === modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py' | |||
1436 | --- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2011-12-08 16:04:13 +0000 | |||
1437 | +++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2011-12-19 21:48:31 +0000 | |||
1438 | @@ -13,8 +13,8 @@ | |||
1439 | 13 | 13 | ||
1440 | 14 | from pytz import utc | 14 | from pytz import utc |
1441 | 15 | from storm.locals import Store | 15 | from storm.locals import Store |
1442 | 16 | from testtools.deferredruntest import AsynchronousDeferredRunTest | ||
1443 | 17 | import transaction | 16 | import transaction |
1444 | 17 | from twisted.trial.unittest import TestCase as TrialTestCase | ||
1445 | 18 | from zope.component import getUtility | 18 | from zope.component import getUtility |
1446 | 19 | from zope.security.proxy import removeSecurityProxy | 19 | from zope.security.proxy import removeSecurityProxy |
1447 | 20 | 20 | ||
1448 | @@ -28,7 +28,6 @@ | |||
1449 | 28 | from lp.app.errors import NotFoundError | 28 | from lp.app.errors import NotFoundError |
1450 | 29 | from lp.buildmaster.enums import BuildStatus | 29 | from lp.buildmaster.enums import BuildStatus |
1451 | 30 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue | 30 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue |
1452 | 31 | from lp.buildmaster.model.builder import BuilderSlave | ||
1453 | 32 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob | 31 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
1454 | 33 | from lp.buildmaster.model.packagebuild import PackageBuild | 32 | from lp.buildmaster.model.packagebuild import PackageBuild |
1455 | 34 | from lp.buildmaster.tests.mock_slaves import WaitingSlave | 33 | from lp.buildmaster.tests.mock_slaves import WaitingSlave |
1456 | @@ -589,11 +588,14 @@ | |||
1457 | 589 | self.assertEquals(0, len(notifications)) | 588 | self.assertEquals(0, len(notifications)) |
1458 | 590 | 589 | ||
1459 | 591 | 590 | ||
1461 | 592 | class TestBuildNotifications(TestCaseWithFactory): | 591 | class TestBuildNotifications(TrialTestCase): |
1462 | 593 | 592 | ||
1463 | 594 | layer = LaunchpadZopelessLayer | 593 | layer = LaunchpadZopelessLayer |
1464 | 595 | 594 | ||
1466 | 596 | run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20) | 595 | def setUp(self): |
1467 | 596 | super(TestBuildNotifications, self).setUp() | ||
1468 | 597 | from lp.testing.factory import LaunchpadObjectFactory | ||
1469 | 598 | self.factory = LaunchpadObjectFactory() | ||
1470 | 597 | 599 | ||
1471 | 598 | def prepare_build(self, fake_successful_upload=False): | 600 | def prepare_build(self, fake_successful_upload=False): |
1472 | 599 | queue_record = self.factory.makeSourcePackageRecipeBuildJob() | 601 | queue_record = self.factory.makeSourcePackageRecipeBuildJob() |
1473 | @@ -606,7 +608,7 @@ | |||
1474 | 606 | result=True) | 608 | result=True) |
1475 | 607 | queue_record.builder = self.factory.makeBuilder() | 609 | queue_record.builder = self.factory.makeBuilder() |
1476 | 608 | slave = WaitingSlave('BuildStatus.OK') | 610 | slave = WaitingSlave('BuildStatus.OK') |
1478 | 609 | self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave)) | 611 | queue_record.builder.setSlaveForTesting(slave) |
1479 | 610 | return build | 612 | return build |
1480 | 611 | 613 | ||
1481 | 612 | def assertDeferredNotifyCount(self, status, build, expected_count): | 614 | def assertDeferredNotifyCount(self, status, build, expected_count): |
1482 | @@ -664,5 +666,5 @@ | |||
1483 | 664 | 666 | ||
1484 | 665 | 667 | ||
1485 | 666 | class TestHandleStatusForSPRBuild( | 668 | class TestHandleStatusForSPRBuild( |
1487 | 667 | MakeSPRecipeBuildMixin, TestHandleStatusMixin, TestCaseWithFactory): | 669 | MakeSPRecipeBuildMixin, TestHandleStatusMixin, TrialTestCase): |
1488 | 668 | """IPackageBuild.handleStatus works with SPRecipe builds.""" | 670 | """IPackageBuild.handleStatus works with SPRecipe builds.""" |
1489 | 669 | 671 | ||
1490 | === modified file 'lib/lp/services/database/transaction_policy.py' | |||
1491 | --- lib/lp/services/database/transaction_policy.py 2011-10-10 06:23:12 +0000 | |||
1492 | +++ lib/lp/services/database/transaction_policy.py 2011-12-19 21:48:31 +0000 | |||
1493 | @@ -133,11 +133,8 @@ | |||
1494 | 133 | def _isInTransaction(self): | 133 | def _isInTransaction(self): |
1495 | 134 | """Is our store currently in a transaction?""" | 134 | """Is our store currently in a transaction?""" |
1496 | 135 | pg_connection = self.store._connection._raw_connection | 135 | pg_connection = self.store._connection._raw_connection |
1502 | 136 | if pg_connection is None: | 136 | status = pg_connection.get_transaction_status() |
1503 | 137 | return False | 137 | return status != TRANSACTION_STATUS_IDLE |
1499 | 138 | else: | ||
1500 | 139 | status = pg_connection.get_transaction_status() | ||
1501 | 140 | return status != TRANSACTION_STATUS_IDLE | ||
1504 | 141 | 138 | ||
1505 | 142 | def _checkNoTransaction(self, error_msg): | 139 | def _checkNoTransaction(self, error_msg): |
1506 | 143 | """Verify that no transaction is ongoing. | 140 | """Verify that no transaction is ongoing. |
1507 | 144 | 141 | ||
1508 | === modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py' | |||
1509 | --- lib/lp/soyuz/tests/test_binarypackagebuild.py 2011-11-14 06:36:57 +0000 | |||
1510 | +++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2011-12-19 21:48:31 +0000 | |||
1511 | @@ -1,4 +1,4 @@ | |||
1513 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
1514 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1515 | 3 | 3 | ||
1516 | 4 | """Test Build features.""" | 4 | """Test Build features.""" |
1517 | @@ -10,7 +10,7 @@ | |||
1518 | 10 | 10 | ||
1519 | 11 | import pytz | 11 | import pytz |
1520 | 12 | from storm.store import Store | 12 | from storm.store import Store |
1522 | 13 | from testtools.deferredruntest import AsynchronousDeferredRunTest | 13 | from twisted.trial.unittest import TestCase as TrialTestCase |
1523 | 14 | from zope.component import getUtility | 14 | from zope.component import getUtility |
1524 | 15 | from zope.security.proxy import removeSecurityProxy | 15 | from zope.security.proxy import removeSecurityProxy |
1525 | 16 | 16 | ||
1526 | @@ -25,7 +25,6 @@ | |||
1527 | 25 | from lp.buildmaster.interfaces.builder import IBuilderSet | 25 | from lp.buildmaster.interfaces.builder import IBuilderSet |
1528 | 26 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue | 26 | from lp.buildmaster.interfaces.buildqueue import IBuildQueue |
1529 | 27 | from lp.buildmaster.interfaces.packagebuild import IPackageBuild | 27 | from lp.buildmaster.interfaces.packagebuild import IPackageBuild |
1530 | 28 | from lp.buildmaster.model.builder import BuilderSlave | ||
1531 | 29 | from lp.buildmaster.model.buildqueue import BuildQueue | 28 | from lp.buildmaster.model.buildqueue import BuildQueue |
1532 | 30 | from lp.buildmaster.tests.mock_slaves import WaitingSlave | 29 | from lp.buildmaster.tests.mock_slaves import WaitingSlave |
1533 | 31 | from lp.buildmaster.tests.test_packagebuild import ( | 30 | from lp.buildmaster.tests.test_packagebuild import ( |
1534 | @@ -54,7 +53,6 @@ | |||
1535 | 54 | logout, | 53 | logout, |
1536 | 55 | TestCaseWithFactory, | 54 | TestCaseWithFactory, |
1537 | 56 | ) | 55 | ) |
1538 | 57 | from lp.testing.fakemethod import FakeMethod | ||
1539 | 58 | 56 | ||
1540 | 59 | 57 | ||
1541 | 60 | class TestBinaryPackageBuild(TestCaseWithFactory): | 58 | class TestBinaryPackageBuild(TestCaseWithFactory): |
1542 | @@ -524,9 +522,7 @@ | |||
1543 | 524 | self.build = gedit_src_hist.createMissingBuilds()[0] | 522 | self.build = gedit_src_hist.createMissingBuilds()[0] |
1544 | 525 | 523 | ||
1545 | 526 | self.builder = self.factory.makeBuilder() | 524 | self.builder = self.factory.makeBuilder() |
1549 | 527 | self.patch( | 525 | self.builder.setSlaveForTesting(WaitingSlave('BuildStatus.OK')) |
1547 | 528 | BuilderSlave, 'makeBuilderSlave', | ||
1548 | 529 | FakeMethod(WaitingSlave('BuildStatus.OK'))) | ||
1550 | 530 | self.build.buildqueue_record.markAsBuilding(self.builder) | 526 | self.build.buildqueue_record.markAsBuilding(self.builder) |
1551 | 531 | 527 | ||
1552 | 532 | def testDependencies(self): | 528 | def testDependencies(self): |
1553 | @@ -572,12 +568,9 @@ | |||
1554 | 572 | 568 | ||
1555 | 573 | 569 | ||
1556 | 574 | class TestHandleStatusForBinaryPackageBuild( | 570 | class TestHandleStatusForBinaryPackageBuild( |
1558 | 575 | MakeBinaryPackageBuildMixin, TestHandleStatusMixin, TestCaseWithFactory): | 571 | MakeBinaryPackageBuildMixin, TestHandleStatusMixin, TrialTestCase): |
1559 | 576 | """IPackageBuild.handleStatus works with binary builds.""" | 572 | """IPackageBuild.handleStatus works with binary builds.""" |
1560 | 577 | 573 | ||
1561 | 578 | layer = LaunchpadZopelessLayer | ||
1562 | 579 | run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20) | ||
1563 | 580 | |||
1564 | 581 | 574 | ||
1565 | 582 | class TestBinaryPackageBuildWebservice(TestCaseWithFactory): | 575 | class TestBinaryPackageBuildWebservice(TestCaseWithFactory): |
1566 | 583 | """Test cases for BinaryPackageBuild on the webservice. | 576 | """Test cases for BinaryPackageBuild on the webservice. |
1567 | 584 | 577 | ||
1568 | === modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py' | |||
1569 | --- lib/lp/translations/model/translationtemplatesbuildbehavior.py 2011-10-20 07:45:52 +0000 | |||
1570 | +++ lib/lp/translations/model/translationtemplatesbuildbehavior.py 2011-12-19 21:48:31 +0000 | |||
1571 | @@ -1,4 +1,4 @@ | |||
1573 | 1 | # Copyright 2010-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
1574 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1575 | 3 | 3 | ||
1576 | 4 | """An `IBuildFarmJobBehavior` for `TranslationTemplatesBuildJob`. | 4 | """An `IBuildFarmJobBehavior` for `TranslationTemplatesBuildJob`. |
1577 | @@ -13,10 +13,9 @@ | |||
1578 | 13 | 13 | ||
1579 | 14 | import datetime | 14 | import datetime |
1580 | 15 | import os | 15 | import os |
1581 | 16 | import pytz | ||
1582 | 16 | import tempfile | 17 | import tempfile |
1583 | 17 | 18 | ||
1584 | 18 | import pytz | ||
1585 | 19 | import transaction | ||
1586 | 20 | from twisted.internet import defer | 19 | from twisted.internet import defer |
1587 | 21 | from zope.component import getUtility | 20 | from zope.component import getUtility |
1588 | 22 | from zope.interface import implements | 21 | from zope.interface import implements |
1589 | @@ -29,7 +28,6 @@ | |||
1590 | 29 | ) | 28 | ) |
1591 | 30 | from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase | 29 | from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase |
1592 | 31 | from lp.registry.interfaces.productseries import IProductSeriesSet | 30 | from lp.registry.interfaces.productseries import IProductSeriesSet |
1593 | 32 | from lp.services.database.transaction_policy import DatabaseTransactionPolicy | ||
1594 | 33 | from lp.translations.interfaces.translationimportqueue import ( | 31 | from lp.translations.interfaces.translationimportqueue import ( |
1595 | 34 | ITranslationImportQueue, | 32 | ITranslationImportQueue, |
1596 | 35 | ) | 33 | ) |
1597 | @@ -134,16 +132,13 @@ | |||
1598 | 134 | def storeBuildInfo(build, queue_item, build_status): | 132 | def storeBuildInfo(build, queue_item, build_status): |
1599 | 135 | """See `IPackageBuild`.""" | 133 | """See `IPackageBuild`.""" |
1600 | 136 | def got_log(lfa_id): | 134 | def got_log(lfa_id): |
1611 | 137 | transaction.commit() | 135 | build.build.log = lfa_id |
1612 | 138 | with DatabaseTransactionPolicy(read_only=False): | 136 | build.build.builder = queue_item.builder |
1613 | 139 | build.build.log = lfa_id | 137 | build.build.date_started = queue_item.date_started |
1614 | 140 | build.build.builder = queue_item.builder | 138 | # XXX cprov 20060615 bug=120584: Currently buildduration includes |
1615 | 141 | build.build.date_started = queue_item.date_started | 139 | # the scanner latency, it should really be asking the slave for |
1616 | 142 | # XXX cprov 20060615 bug=120584: Currently buildduration | 140 | # the duration spent building locally. |
1617 | 143 | # includes the scanner latency. It should really be | 141 | build.build.date_finished = datetime.datetime.now(pytz.UTC) |
1608 | 144 | # asking the slave for the duration spent building locally. | ||
1609 | 145 | build.build.date_finished = datetime.datetime.now(pytz.UTC) | ||
1610 | 146 | transaction.commit() | ||
1618 | 147 | 142 | ||
1619 | 148 | d = build.getLogFromSlave(build, queue_item) | 143 | d = build.getLogFromSlave(build, queue_item) |
1620 | 149 | return d.addCallback(got_log) | 144 | return d.addCallback(got_log) |