Merge lp:~allenap/launchpad/undo-read-only-transactions-in-buildmaster into lp:launchpad

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