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
=== modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py'
--- lib/lp/archiveuploader/tests/test_uploadprocessor.py 2011-12-13 17:10:46 +0000
+++ lib/lp/archiveuploader/tests/test_uploadprocessor.py 2011-12-19 21:48:31 +0000
@@ -629,8 +629,7 @@
629 from_addr, to_addrs, raw_msg = stub.test_emails.pop()629 from_addr, to_addrs, raw_msg = stub.test_emails.pop()
630 foo_bar = "Foo Bar <foo.bar@canonical.com>"630 foo_bar = "Foo Bar <foo.bar@canonical.com>"
631 daniel = "Daniel Silverstone <daniel.silverstone@canonical.com>"631 daniel = "Daniel Silverstone <daniel.silverstone@canonical.com>"
632 self.assertContentEqual(632 self.assertEqual([e.strip() for e in to_addrs], [foo_bar, daniel])
633 [foo_bar, daniel], [e.strip() for e in to_addrs])
634 self.assertTrue(633 self.assertTrue(
635 "NEW" in raw_msg, "Expected email containing 'NEW', got:\n%s"634 "NEW" in raw_msg, "Expected email containing 'NEW', got:\n%s"
636 % raw_msg)635 % raw_msg)
@@ -664,8 +663,7 @@
664 from_addr, to_addrs, raw_msg = stub.test_emails.pop()663 from_addr, to_addrs, raw_msg = stub.test_emails.pop()
665 daniel = "Daniel Silverstone <daniel.silverstone@canonical.com>"664 daniel = "Daniel Silverstone <daniel.silverstone@canonical.com>"
666 foo_bar = "Foo Bar <foo.bar@canonical.com>"665 foo_bar = "Foo Bar <foo.bar@canonical.com>"
667 self.assertContentEqual(666 self.assertEqual([e.strip() for e in to_addrs], [foo_bar, daniel])
668 [foo_bar, daniel], [e.strip() for e in to_addrs])
669 self.assertTrue("Waiting for approval" in raw_msg,667 self.assertTrue("Waiting for approval" in raw_msg,
670 "Expected an 'upload awaits approval' email.\n"668 "Expected an 'upload awaits approval' email.\n"
671 "Got:\n%s" % raw_msg)669 "Got:\n%s" % raw_msg)
672670
=== modified file 'lib/lp/buildmaster/interfaces/builder.py'
--- lib/lp/buildmaster/interfaces/builder.py 2011-11-09 11:50:17 +0000
+++ lib/lp/buildmaster/interfaces/builder.py 2011-12-19 21:48:31 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0211,E02134# pylint: disable-msg=E0211,E0213
@@ -25,10 +25,10 @@
25 export_as_webservice_entry,25 export_as_webservice_entry,
26 export_read_operation,26 export_read_operation,
27 exported,27 exported,
28 operation_for_version,
29 operation_parameters,28 operation_parameters,
29 operation_returns_entry,
30 operation_returns_collection_of,30 operation_returns_collection_of,
31 operation_returns_entry,31 operation_for_version,
32 )32 )
33from lazr.restful.fields import (33from lazr.restful.fields import (
34 Reference,34 Reference,
@@ -50,12 +50,12 @@
50from lp.app.validators.name import name_validator50from lp.app.validators.name import name_validator
51from lp.app.validators.url import builder_url_validator51from lp.app.validators.url import builder_url_validator
52from lp.registry.interfaces.role import IHasOwner52from lp.registry.interfaces.role import IHasOwner
53from lp.soyuz.interfaces.processor import IProcessor
53from lp.services.fields import (54from lp.services.fields import (
54 Description,55 Description,
55 PersonChoice,56 PersonChoice,
56 Title,57 Title,
57 )58 )
58from lp.soyuz.interfaces.processor import IProcessor
5959
6060
61class BuildDaemonError(Exception):61class BuildDaemonError(Exception):
@@ -195,8 +195,6 @@
195195
196 def setSlaveForTesting(proxy):196 def setSlaveForTesting(proxy):
197 """Sets the RPC proxy through which to operate the build slave."""197 """Sets the RPC proxy through which to operate the build slave."""
198 # XXX JeroenVermeulen 2011-11-09, bug=888010: Don't use this.
199 # It's a trap. See bug for details.
200198
201 def verifySlaveBuildCookie(slave_build_id):199 def verifySlaveBuildCookie(slave_build_id):
202 """Verify that a slave's build cookie is consistent.200 """Verify that a slave's build cookie is consistent.
203201
=== modified file 'lib/lp/buildmaster/manager.py'
--- lib/lp/buildmaster/manager.py 2011-12-08 11:39:10 +0000
+++ lib/lp/buildmaster/manager.py 2011-12-19 21:48:31 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Soyuz buildd slave manager logic."""4"""Soyuz buildd slave manager logic."""
@@ -23,6 +23,10 @@
23from zope.component import getUtility23from zope.component import getUtility
2424
25from lp.buildmaster.enums import BuildStatus25from lp.buildmaster.enums import BuildStatus
26from lp.buildmaster.interfaces.buildfarmjobbehavior import (
27 BuildBehaviorMismatch,
28 )
29from lp.buildmaster.model.builder import Builder
26from lp.buildmaster.interfaces.builder import (30from lp.buildmaster.interfaces.builder import (
27 BuildDaemonError,31 BuildDaemonError,
28 BuildSlaveFailure,32 BuildSlaveFailure,
@@ -30,11 +34,6 @@
30 CannotFetchFile,34 CannotFetchFile,
31 CannotResumeHost,35 CannotResumeHost,
32 )36 )
33from lp.buildmaster.interfaces.buildfarmjobbehavior import (
34 BuildBehaviorMismatch,
35 )
36from lp.buildmaster.model.builder import Builder
37from lp.services.database.transaction_policy import DatabaseTransactionPolicy
3837
3938
40BUILDD_MANAGER_LOG_NAME = "slave-scanner"39BUILDD_MANAGER_LOG_NAME = "slave-scanner"
@@ -112,17 +111,13 @@
112 # algorithm for polling.111 # algorithm for polling.
113 SCAN_INTERVAL = 15112 SCAN_INTERVAL = 15
114113
115 def __init__(self, builder_name, logger, clock=None):114 def __init__(self, builder_name, logger):
116 self.builder_name = builder_name115 self.builder_name = builder_name
117 self.logger = logger116 self.logger = logger
118 if clock is None:
119 clock = reactor
120 self._clock = clock
121117
122 def startCycle(self):118 def startCycle(self):
123 """Scan the builder and dispatch to it or deal with failures."""119 """Scan the builder and dispatch to it or deal with failures."""
124 self.loop = LoopingCall(self.singleCycle)120 self.loop = LoopingCall(self.singleCycle)
125 self.loop.clock = self._clock
126 self.stopping_deferred = self.loop.start(self.SCAN_INTERVAL)121 self.stopping_deferred = self.loop.start(self.SCAN_INTERVAL)
127 return self.stopping_deferred122 return self.stopping_deferred
128123
@@ -143,58 +138,51 @@
143 1. Print the error in the log138 1. Print the error in the log
144 2. Increment and assess failure counts on the builder and job.139 2. Increment and assess failure counts on the builder and job.
145 """140 """
146 # Since this is a failure path, we could be in a broken141 # Make sure that pending database updates are removed as it
147 # transaction. Get us a fresh one.142 # could leave the database in an inconsistent state (e.g. The
143 # job says it's running but the buildqueue has no builder set).
148 transaction.abort()144 transaction.abort()
149145
150 # If we don't recognise the exception include a stack trace with146 # If we don't recognise the exception include a stack trace with
151 # the error.147 # the error.
152 error_message = failure.getErrorMessage()148 error_message = failure.getErrorMessage()
153 familiar_error = failure.check(149 if failure.check(
154 BuildSlaveFailure, CannotBuild, BuildBehaviorMismatch,150 BuildSlaveFailure, CannotBuild, BuildBehaviorMismatch,
155 CannotResumeHost, BuildDaemonError, CannotFetchFile)151 CannotResumeHost, BuildDaemonError, CannotFetchFile):
156 if familiar_error:152 self.logger.info("Scanning %s failed with: %s" % (
157 self.logger.info(153 self.builder_name, error_message))
158 "Scanning %s failed with: %s",
159 self.builder_name, error_message)
160 else:154 else:
161 self.logger.info(155 self.logger.info("Scanning %s failed with: %s\n%s" % (
162 "Scanning %s failed with: %s\n%s",
163 self.builder_name, failure.getErrorMessage(),156 self.builder_name, failure.getErrorMessage(),
164 failure.getTraceback())157 failure.getTraceback()))
165158
166 # Decide if we need to terminate the job or fail the159 # Decide if we need to terminate the job or fail the
167 # builder.160 # builder.
168 try:161 try:
169 builder = get_builder(self.builder_name)162 builder = get_builder(self.builder_name)
170 transaction.commit()163 builder.gotFailure()
171164 if builder.currentjob is not None:
172 with DatabaseTransactionPolicy(read_only=False):165 build_farm_job = builder.getCurrentBuildFarmJob()
173 builder.gotFailure()166 build_farm_job.gotFailure()
174167 self.logger.info(
175 if builder.currentjob is None:168 "builder %s failure count: %s, "
176 self.logger.info(169 "job '%s' failure count: %s" % (
177 "Builder %s failed a probe, count: %s",
178 self.builder_name, builder.failure_count)
179 else:
180 build_farm_job = builder.getCurrentBuildFarmJob()
181 build_farm_job.gotFailure()
182 self.logger.info(
183 "builder %s failure count: %s, "
184 "job '%s' failure count: %s",
185 self.builder_name,170 self.builder_name,
186 builder.failure_count,171 builder.failure_count,
187 build_farm_job.title,172 build_farm_job.title,
188 build_farm_job.failure_count)173 build_farm_job.failure_count))
189174 else:
190 assessFailureCounts(builder, failure.getErrorMessage())175 self.logger.info(
191 transaction.commit()176 "Builder %s failed a probe, count: %s" % (
177 self.builder_name, builder.failure_count))
178 assessFailureCounts(builder, failure.getErrorMessage())
179 transaction.commit()
192 except:180 except:
193 # Catastrophic code failure! Not much we can do.181 # Catastrophic code failure! Not much we can do.
194 transaction.abort()
195 self.logger.error(182 self.logger.error(
196 "Miserable failure when trying to examine failure counts:\n",183 "Miserable failure when trying to examine failure counts:\n",
197 exc_info=True)184 exc_info=True)
185 transaction.abort()
198186
199 def checkCancellation(self, builder):187 def checkCancellation(self, builder):
200 """See if there is a pending cancellation request.188 """See if there is a pending cancellation request.
@@ -248,9 +236,14 @@
248 """236 """
249 # We need to re-fetch the builder object on each cycle as the237 # We need to re-fetch the builder object on each cycle as the
250 # Storm store is invalidated over transaction boundaries.238 # Storm store is invalidated over transaction boundaries.
239
251 self.builder = get_builder(self.builder_name)240 self.builder = get_builder(self.builder_name)
252241
253 def status_updated(ignored):242 def status_updated(ignored):
243 # Commit the changes done while possibly rescuing jobs, to
244 # avoid holding table locks.
245 transaction.commit()
246
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.
255 buildqueue = self.builder.getBuildQueue()248 buildqueue = self.builder.getBuildQueue()
256249
@@ -260,10 +253,14 @@
260 return self.builder.updateBuild(buildqueue)253 return self.builder.updateBuild(buildqueue)
261254
262 def build_updated(ignored):255 def build_updated(ignored):
256 # Commit changes done while updating the build, to avoid
257 # holding table locks.
258 transaction.commit()
259
263 # If the builder is in manual mode, don't dispatch anything.260 # If the builder is in manual mode, don't dispatch anything.
264 if self.builder.manual:261 if self.builder.manual:
265 self.logger.debug(262 self.logger.debug(
266 '%s is in manual mode, not dispatching.',263 '%s is in manual mode, not dispatching.' %
267 self.builder.name)264 self.builder.name)
268 return265 return
269266
@@ -281,33 +278,22 @@
281 job = self.builder.currentjob278 job = self.builder.currentjob
282 if job is not None and not self.builder.builderok:279 if job is not None and not self.builder.builderok:
283 self.logger.info(280 self.logger.info(
284 "%s was made unavailable; resetting attached job.",281 "%s was made unavailable, resetting attached "
285 self.builder.name)282 "job" % self.builder.name)
283 job.reset()
286 transaction.commit()284 transaction.commit()
287 with DatabaseTransactionPolicy(read_only=False):
288 job.reset()
289 transaction.commit()
290 return285 return
291286
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.
293288
294 # XXX JeroenVermeulen 2011-10-11, bug=872112: The job's
295 # failure count will be reset once the job has started
296 # successfully. Because of intervening commits, you may see
297 # a build with a nonzero failure count that's actually going
298 # to succeed later (and have a failure count of zero). Or
299 # it may fail yet end up with a lower failure count than you
300 # saw earlier.
301 d = self.builder.findAndStartJob()289 d = self.builder.findAndStartJob()
302290
303 def job_started(candidate):291 def job_started(candidate):
304 if self.builder.currentjob is not None:292 if self.builder.currentjob is not None:
305 # After a successful dispatch we can reset the293 # After a successful dispatch we can reset the
306 # failure_count.294 # failure_count.
295 self.builder.resetFailureCount()
307 transaction.commit()296 transaction.commit()
308 with DatabaseTransactionPolicy(read_only=False):
309 self.builder.resetFailureCount()
310 transaction.commit()
311 return self.builder.slave297 return self.builder.slave
312 else:298 else:
313 return None299 return None
@@ -386,7 +372,6 @@
386 self.logger = self._setupLogger()372 self.logger = self._setupLogger()
387 self.new_builders_scanner = NewBuildersScanner(373 self.new_builders_scanner = NewBuildersScanner(
388 manager=self, clock=clock)374 manager=self, clock=clock)
389 self.transaction_policy = DatabaseTransactionPolicy(read_only=True)
390375
391 def _setupLogger(self):376 def _setupLogger(self):
392 """Set up a 'slave-scanner' logger that redirects to twisted.377 """Set up a 'slave-scanner' logger that redirects to twisted.
@@ -405,28 +390,16 @@
405 logger.setLevel(level)390 logger.setLevel(level)
406 return logger391 return logger
407392
408 def enterReadOnlyDatabasePolicy(self):
409 """Set the database transaction policy to read-only.
410
411 Any previously pending changes are committed first.
412 """
413 transaction.commit()
414 self.transaction_policy.__enter__()
415
416 def exitReadOnlyDatabasePolicy(self, *args):
417 """Reset database transaction policy to the default read-write."""
418 self.transaction_policy.__exit__(None, None, None)
419
420 def startService(self):393 def startService(self):
421 """Service entry point, called when the application starts."""394 """Service entry point, called when the application starts."""
395
396 # Get a list of builders and set up scanners on each one.
397
422 # Avoiding circular imports.398 # Avoiding circular imports.
423 from lp.buildmaster.interfaces.builder import IBuilderSet399 from lp.buildmaster.interfaces.builder import IBuilderSet
424400 builder_set = getUtility(IBuilderSet)
425 self.enterReadOnlyDatabasePolicy()401 builders = [builder.name for builder in builder_set]
426402 self.addScanForBuilders(builders)
427 # Get a list of builders and set up scanners on each one.
428 self.addScanForBuilders(
429 [builder.name for builder in getUtility(IBuilderSet)])
430 self.new_builders_scanner.scheduleScan()403 self.new_builders_scanner.scheduleScan()
431404
432 # Events will now fire in the SlaveScanner objects to scan each405 # Events will now fire in the SlaveScanner objects to scan each
@@ -447,7 +420,6 @@
447 # stopped, so we can wait on them all at once here before420 # stopped, so we can wait on them all at once here before
448 # exiting.421 # exiting.
449 d = defer.DeferredList(deferreds, consumeErrors=True)422 d = defer.DeferredList(deferreds, consumeErrors=True)
450 d.addCallback(self.exitReadOnlyDatabasePolicy)
451 return d423 return d
452424
453 def addScanForBuilders(self, builders):425 def addScanForBuilders(self, builders):
454426
=== modified file 'lib/lp/buildmaster/model/builder.py'
--- lib/lp/buildmaster/model/builder.py 2011-12-13 13:33:04 +0000
+++ lib/lp/buildmaster/model/builder.py 2011-12-19 21:48:31 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009,2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0611,W02124# pylint: disable-msg=E0611,W0212
@@ -76,7 +76,6 @@
76 specific_job_classes,76 specific_job_classes,
77 )77 )
78from lp.registry.interfaces.person import validate_public_person78from lp.registry.interfaces.person import validate_public_person
79from lp.services.database.transaction_policy import DatabaseTransactionPolicy
80from lp.services.job.interfaces.job import JobStatus79from lp.services.job.interfaces.job import JobStatus
81from lp.services.job.model.job import Job80from lp.services.job.model.job import Job
82from lp.services.propertycache import (81from lp.services.propertycache import (
@@ -546,8 +545,6 @@
546545
547 def setSlaveForTesting(self, proxy):546 def setSlaveForTesting(self, proxy):
548 """See IBuilder."""547 """See IBuilder."""
549 # XXX JeroenVermeulen 2011-11-09, bug=888010: Don't use this.
550 # It's a trap. See bug for details.
551 self._testing_slave = proxy548 self._testing_slave = proxy
552 del get_property_cache(self).slave549 del get_property_cache(self).slave
553550
@@ -676,13 +673,10 @@
676 bytes_written = out_file.tell()673 bytes_written = out_file.tell()
677 out_file.seek(0)674 out_file.seek(0)
678675
679 transaction.commit()676 library_file = getUtility(ILibraryFileAliasSet).create(
680 with DatabaseTransactionPolicy(read_only=False):677 filename, bytes_written, out_file,
681 library_file = getUtility(ILibraryFileAliasSet).create(678 contentType=filenameToContentType(filename),
682 filename, bytes_written, out_file,679 restricted=private)
683 contentType=filenameToContentType(filename),
684 restricted=private)
685 transaction.commit()
686 finally:680 finally:
687 # Remove the temporary file. getFile() closes the file681 # Remove the temporary file. getFile() closes the file
688 # object.682 # object.
@@ -720,7 +714,7 @@
720 def acquireBuildCandidate(self):714 def acquireBuildCandidate(self):
721 """Acquire a build candidate in an atomic fashion.715 """Acquire a build candidate in an atomic fashion.
722716
723 When retrieving a candidate we need to mark it as building717 When retrieiving a candidate we need to mark it as building
724 immediately so that it is not dispatched by another builder in the718 immediately so that it is not dispatched by another builder in the
725 build manager.719 build manager.
726720
@@ -730,15 +724,12 @@
730 can be in this code at the same time.724 can be in this code at the same time.
731725
732 If there's ever more than one build manager running at once, then726 If there's ever more than one build manager running at once, then
733 this code will need some sort of mutex, or run in a single727 this code will need some sort of mutex.
734 transaction.
735 """728 """
736 candidate = self._findBuildCandidate()729 candidate = self._findBuildCandidate()
737 if candidate is not None:730 if candidate is not None:
731 candidate.markAsBuilding(self)
738 transaction.commit()732 transaction.commit()
739 with DatabaseTransactionPolicy(read_only=False):
740 candidate.markAsBuilding(self)
741 transaction.commit()
742 return candidate733 return candidate
743734
744 def _findBuildCandidate(self):735 def _findBuildCandidate(self):
@@ -801,17 +792,13 @@
801 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)792 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
802 candidate_jobs = store.execute(query).get_all()793 candidate_jobs = store.execute(query).get_all()
803794
804 transaction.commit()795 for (candidate_id,) in candidate_jobs:
805 with DatabaseTransactionPolicy(read_only=False):796 candidate = getUtility(IBuildQueueSet).get(candidate_id)
806 for (candidate_id,) in candidate_jobs:797 job_class = job_classes[candidate.job_type]
807 candidate = getUtility(IBuildQueueSet).get(candidate_id)798 candidate_approved = job_class.postprocessCandidate(
808 job_class = job_classes[candidate.job_type]799 candidate, logger)
809 candidate_approved = job_class.postprocessCandidate(800 if candidate_approved:
810 candidate, logger)801 return candidate
811 if candidate_approved:
812 transaction.commit()
813 return candidate
814 transaction.commit()
815802
816 return None803 return None
817804
818805
=== modified file 'lib/lp/buildmaster/model/buildfarmjobbehavior.py'
--- lib/lp/buildmaster/model/buildfarmjobbehavior.py 2011-10-11 07:15:10 +0000
+++ lib/lp/buildmaster/model/buildfarmjobbehavior.py 2011-12-19 21:48:31 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0211,E02134# pylint: disable-msg=E0211,E0213
@@ -16,8 +16,8 @@
16import socket16import socket
17import xmlrpclib17import xmlrpclib
1818
19import transaction
20from twisted.internet import defer19from twisted.internet import defer
20
21from zope.component import getUtility21from zope.component import getUtility
22from zope.interface import implements22from zope.interface import implements
23from zope.security.proxy import removeSecurityProxy23from zope.security.proxy import removeSecurityProxy
@@ -32,7 +32,6 @@
32 IBuildFarmJobBehavior,32 IBuildFarmJobBehavior,
33 )33 )
34from lp.services import encoding34from lp.services import encoding
35from lp.services.database.transaction_policy import DatabaseTransactionPolicy
36from lp.services.job.interfaces.job import JobStatus35from lp.services.job.interfaces.job import JobStatus
3736
3837
@@ -71,25 +70,6 @@
71 if slave_build_cookie != expected_cookie:70 if slave_build_cookie != expected_cookie:
72 raise CorruptBuildCookie("Invalid slave build cookie.")71 raise CorruptBuildCookie("Invalid slave build cookie.")
7372
74 def _getBuilderStatusHandler(self, status_text, logger):
75 """Look up the handler method for a given builder status.
76
77 If status is not a known one, logs an error and returns None.
78 """
79 builder_status_handlers = {
80 'BuilderStatus.IDLE': self.updateBuild_IDLE,
81 'BuilderStatus.BUILDING': self.updateBuild_BUILDING,
82 'BuilderStatus.ABORTING': self.updateBuild_ABORTING,
83 'BuilderStatus.ABORTED': self.updateBuild_ABORTED,
84 'BuilderStatus.WAITING': self.updateBuild_WAITING,
85 }
86 handler = builder_status_handlers.get(status_text)
87 if handler is None:
88 logger.critical(
89 "Builder on %s returned unknown status %s; failing it.",
90 self._builder.url, status_text)
91 return handler
92
93 def updateBuild(self, queueItem):73 def updateBuild(self, queueItem):
94 """See `IBuildFarmJobBehavior`."""74 """See `IBuildFarmJobBehavior`."""
95 logger = logging.getLogger('slave-scanner')75 logger = logging.getLogger('slave-scanner')
@@ -97,7 +77,6 @@
97 d = self._builder.slaveStatus()77 d = self._builder.slaveStatus()
9878
99 def got_failure(failure):79 def got_failure(failure):
100 transaction.abort()
101 failure.trap(xmlrpclib.Fault, socket.error)80 failure.trap(xmlrpclib.Fault, socket.error)
102 info = failure.value81 info = failure.value
103 info = ("Could not contact the builder %s, caught a (%s)"82 info = ("Could not contact the builder %s, caught a (%s)"
@@ -105,22 +84,27 @@
105 raise BuildSlaveFailure(info)84 raise BuildSlaveFailure(info)
10685
107 def got_status(slave_status):86 def got_status(slave_status):
87 builder_status_handlers = {
88 'BuilderStatus.IDLE': self.updateBuild_IDLE,
89 'BuilderStatus.BUILDING': self.updateBuild_BUILDING,
90 'BuilderStatus.ABORTING': self.updateBuild_ABORTING,
91 'BuilderStatus.ABORTED': self.updateBuild_ABORTED,
92 'BuilderStatus.WAITING': self.updateBuild_WAITING,
93 }
94
108 builder_status = slave_status['builder_status']95 builder_status = slave_status['builder_status']
109 status_handler = self._getBuilderStatusHandler(96 if builder_status not in builder_status_handlers:
110 builder_status, logger)97 logger.critical(
111 if status_handler is None:98 "Builder on %s returned unknown status %s, failing it"
112 error = (99 % (self._builder.url, builder_status))
100 self._builder.failBuilder(
113 "Unknown status code (%s) returned from status() probe."101 "Unknown status code (%s) returned from status() probe."
114 % builder_status)102 % builder_status)
115 transaction.commit()103 # XXX: This will leave the build and job in a bad state, but
116 with DatabaseTransactionPolicy(read_only=False):104 # should never be possible, since our builder statuses are
117 self._builder.failBuilder(error)105 # known.
118 # XXX: This will leave the build and job in a bad106 queueItem._builder = None
119 # state, but should never be possible since our107 queueItem.setDateStarted(None)
120 # builder statuses are known.
121 queueItem._builder = None
122 queueItem.setDateStarted(None)
123 transaction.commit()
124 return108 return
125109
126 # Since logtail is a xmlrpclib.Binary container and it is110 # Since logtail is a xmlrpclib.Binary container and it is
@@ -130,8 +114,9 @@
130 # will simply remove the proxy.114 # will simply remove the proxy.
131 logtail = removeSecurityProxy(slave_status.get('logtail'))115 logtail = removeSecurityProxy(slave_status.get('logtail'))
132116
117 method = builder_status_handlers[builder_status]
133 return defer.maybeDeferred(118 return defer.maybeDeferred(
134 status_handler, queueItem, slave_status, logtail, logger)119 method, queueItem, slave_status, logtail, logger)
135120
136 d.addErrback(got_failure)121 d.addErrback(got_failure)
137 d.addCallback(got_status)122 d.addCallback(got_status)
@@ -143,32 +128,22 @@
143 Log this and reset the record.128 Log this and reset the record.
144 """129 """
145 logger.warn(130 logger.warn(
146 "Builder %s forgot about buildqueue %d -- "131 "Builder %s forgot about buildqueue %d -- resetting buildqueue "
147 "resetting buildqueue record.",132 "record" % (queueItem.builder.url, queueItem.id))
148 queueItem.builder.url, queueItem.id)133 queueItem.reset()
149 transaction.commit()
150 with DatabaseTransactionPolicy(read_only=False):
151 queueItem.reset()
152 transaction.commit()
153134
154 def updateBuild_BUILDING(self, queueItem, slave_status, logtail, logger):135 def updateBuild_BUILDING(self, queueItem, slave_status, logtail, logger):
155 """Build still building, collect the logtail"""136 """Build still building, collect the logtail"""
156 transaction.commit()137 if queueItem.job.status != JobStatus.RUNNING:
157 with DatabaseTransactionPolicy(read_only=False):138 queueItem.job.start()
158 if queueItem.job.status != JobStatus.RUNNING:139 queueItem.logtail = encoding.guess(str(logtail))
159 queueItem.job.start()
160 queueItem.logtail = encoding.guess(str(logtail))
161 transaction.commit()
162140
163 def updateBuild_ABORTING(self, queueItem, slave_status, logtail, logger):141 def updateBuild_ABORTING(self, queueItem, slave_status, logtail, logger):
164 """Build was ABORTED.142 """Build was ABORTED.
165143
166 Master-side should wait until the slave finish the process correctly.144 Master-side should wait until the slave finish the process correctly.
167 """145 """
168 transaction.commit()146 queueItem.logtail = "Waiting for slave process to be terminated"
169 with DatabaseTransactionPolicy(read_only=False):
170 queueItem.logtail = "Waiting for slave process to be terminated"
171 transaction.commit()
172147
173 def updateBuild_ABORTED(self, queueItem, slave_status, logtail, logger):148 def updateBuild_ABORTED(self, queueItem, slave_status, logtail, logger):
174 """ABORTING process has successfully terminated.149 """ABORTING process has successfully terminated.
@@ -176,16 +151,11 @@
176 Clean the builder for another jobs.151 Clean the builder for another jobs.
177 """152 """
178 d = queueItem.builder.cleanSlave()153 d = queueItem.builder.cleanSlave()
179
180 def got_cleaned(ignored):154 def got_cleaned(ignored):
181 transaction.commit()155 queueItem.builder = None
182 with DatabaseTransactionPolicy(read_only=False):156 if queueItem.job.status != JobStatus.FAILED:
183 queueItem.builder = None157 queueItem.job.fail()
184 if queueItem.job.status != JobStatus.FAILED:158 queueItem.specific_job.jobAborted()
185 queueItem.job.fail()
186 queueItem.specific_job.jobAborted()
187 transaction.commit()
188
189 return d.addCallback(got_cleaned)159 return d.addCallback(got_cleaned)
190160
191 def extractBuildStatus(self, slave_status):161 def extractBuildStatus(self, slave_status):
192162
=== modified file 'lib/lp/buildmaster/model/packagebuild.py'
--- lib/lp/buildmaster/model/packagebuild.py 2011-12-08 11:39:10 +0000
+++ lib/lp/buildmaster/model/packagebuild.py 2011-12-19 21:48:31 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010-2011 Canonical Ltd. This software is licensed under the1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -9,11 +9,11 @@
9 ]9 ]
1010
1111
12from cStringIO import StringIO
13import datetime12import datetime
14import logging13import logging
15import os.path14import os.path
1615
16from cStringIO import StringIO
17from lazr.delegates import delegates17from lazr.delegates import delegates
18import pytz18import pytz
19from storm.expr import Desc19from storm.expr import Desc
@@ -24,7 +24,6 @@
24 Storm,24 Storm,
25 Unicode,25 Unicode,
26 )26 )
27import transaction
28from zope.component import getUtility27from zope.component import getUtility
29from zope.interface import (28from zope.interface import (
30 classProvides,29 classProvides,
@@ -44,8 +43,8 @@
44 MAIN_STORE,43 MAIN_STORE,
45 )44 )
46from lp.buildmaster.enums import (45from lp.buildmaster.enums import (
46 BuildStatus,
47 BuildFarmJobType,47 BuildFarmJobType,
48 BuildStatus,
49 )48 )
50from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource49from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
51from lp.buildmaster.interfaces.packagebuild import (50from lp.buildmaster.interfaces.packagebuild import (
@@ -58,8 +57,9 @@
58 BuildFarmJobDerived,57 BuildFarmJobDerived,
59 )58 )
60from lp.buildmaster.model.buildqueue import BuildQueue59from lp.buildmaster.model.buildqueue import BuildQueue
61from lp.registry.interfaces.pocket import PackagePublishingPocket60from lp.registry.interfaces.pocket import (
62from lp.services.database.transaction_policy import DatabaseTransactionPolicy61 PackagePublishingPocket,
62 )
63from lp.soyuz.adapters.archivedependencies import (63from lp.soyuz.adapters.archivedependencies import (
64 default_component_dependency_name,64 default_component_dependency_name,
65 )65 )
@@ -179,24 +179,19 @@
179 def storeBuildInfo(build, librarian, slave_status):179 def storeBuildInfo(build, librarian, slave_status):
180 """See `IPackageBuild`."""180 """See `IPackageBuild`."""
181 def got_log(lfa_id):181 def got_log(lfa_id):
182 dependencies = slave_status.get('dependencies')
183 if dependencies is not None:
184 dependencies = unicode(dependencies)
185
186 # log, builder and date_finished are read-only, so we must182 # log, builder and date_finished are read-only, so we must
187 # currently remove the security proxy to set them.183 # currently remove the security proxy to set them.
188 naked_build = removeSecurityProxy(build)184 naked_build = removeSecurityProxy(build)
189185 naked_build.log = lfa_id
190 transaction.commit()186 naked_build.builder = build.buildqueue_record.builder
191 with DatabaseTransactionPolicy(read_only=False):187 # XXX cprov 20060615 bug=120584: Currently buildduration includes
192 naked_build.log = lfa_id188 # the scanner latency, it should really be asking the slave for
193 naked_build.builder = build.buildqueue_record.builder189 # the duration spent building locally.
194 # XXX cprov 20060615 bug=120584: Currently buildduration190 naked_build.date_finished = datetime.datetime.now(pytz.UTC)
195 # includes the scanner latency. It should really be asking191 if slave_status.get('dependencies') is not None:
196 # the slave for the duration spent building locally.192 build.dependencies = unicode(slave_status.get('dependencies'))
197 naked_build.date_finished = datetime.datetime.now(pytz.UTC)193 else:
198 build.dependencies = dependencies194 build.dependencies = None
199 transaction.commit()
200195
201 d = build.getLogFromSlave(build)196 d = build.getLogFromSlave(build)
202 return d.addCallback(got_log)197 return d.addCallback(got_log)
@@ -297,41 +292,22 @@
297292
298 def handleStatus(self, status, librarian, slave_status):293 def handleStatus(self, status, librarian, slave_status):
299 """See `IPackageBuild`."""294 """See `IPackageBuild`."""
300 # Avoid circular imports.
301 from lp.buildmaster.manager import BUILDD_MANAGER_LOG_NAME295 from lp.buildmaster.manager import BUILDD_MANAGER_LOG_NAME
302
303 logger = logging.getLogger(BUILDD_MANAGER_LOG_NAME)296 logger = logging.getLogger(BUILDD_MANAGER_LOG_NAME)
304 send_notification = status in self.ALLOWED_STATUS_NOTIFICATIONS297 send_notification = status in self.ALLOWED_STATUS_NOTIFICATIONS
305 method = getattr(self, '_handleStatus_' + status, None)298 method = getattr(self, '_handleStatus_' + status, None)
306 if method is None:299 if method is None:
307 logger.critical(300 logger.critical("Unknown BuildStatus '%s' for builder '%s'"
308 "Unknown BuildStatus '%s' for builder '%s'",301 % (status, self.buildqueue_record.builder.url))
309 status, self.buildqueue_record.builder.url)302 return
310 return None
311
312 d = method(librarian, slave_status, logger, send_notification)303 d = method(librarian, slave_status, logger, send_notification)
313 return d304 return d
314305
315 def _destroy_buildqueue_record(self, unused_arg):
316 """Destroy this build's `BuildQueue` record."""
317 transaction.commit()
318 with DatabaseTransactionPolicy(read_only=False):
319 self.buildqueue_record.destroySelf()
320 transaction.commit()
321
322 def _release_builder_and_remove_queue_item(self):306 def _release_builder_and_remove_queue_item(self):
323 # Release the builder for another job.307 # Release the builder for another job.
324 d = self.buildqueue_record.builder.cleanSlave()308 d = self.buildqueue_record.builder.cleanSlave()
325 # Remove BuildQueue record.309 # Remove BuildQueue record.
326 return d.addCallback(self._destroy_buildqueue_record)310 return d.addCallback(lambda x: self.buildqueue_record.destroySelf())
327
328 def _notify_if_appropriate(self, appropriate=True, extra_info=None):
329 """If `appropriate`, call `self.notify` in a write transaction."""
330 if appropriate:
331 transaction.commit()
332 with DatabaseTransactionPolicy(read_only=False):
333 self.notify(extra_info=extra_info)
334 transaction.commit()
335311
336 def _handleStatus_OK(self, librarian, slave_status, logger,312 def _handleStatus_OK(self, librarian, slave_status, logger,
337 send_notification):313 send_notification):
@@ -347,19 +323,16 @@
347 self.buildqueue_record.specific_job.build.title,323 self.buildqueue_record.specific_job.build.title,
348 self.buildqueue_record.builder.name))324 self.buildqueue_record.builder.name))
349325
350 # If this is a binary package build for a source that is no326 # If this is a binary package build, discard it if its source is
351 # longer published, discard it.327 # no longer published.
352 if self.build_farm_job_type == BuildFarmJobType.PACKAGEBUILD:328 if self.build_farm_job_type == BuildFarmJobType.PACKAGEBUILD:
353 build = self.buildqueue_record.specific_job.build329 build = self.buildqueue_record.specific_job.build
354 if not build.current_source_publication:330 if not build.current_source_publication:
355 transaction.commit()331 build.status = BuildStatus.SUPERSEDED
356 with DatabaseTransactionPolicy(read_only=False):
357 build.status = BuildStatus.SUPERSEDED
358 transaction.commit()
359 return self._release_builder_and_remove_queue_item()332 return self._release_builder_and_remove_queue_item()
360333
361 # Explode rather than collect a binary that is denied in this334 # Explode before collect a binary that is denied in this
362 # distroseries/pocket.335 # distroseries/pocket
363 if not self.archive.allowUpdatesToReleasePocket():336 if not self.archive.allowUpdatesToReleasePocket():
364 assert self.distro_series.canUploadToPocket(self.pocket), (337 assert self.distro_series.canUploadToPocket(self.pocket), (
365 "%s (%s) can not be built for pocket %s: illegal status"338 "%s (%s) can not be built for pocket %s: illegal status"
@@ -404,26 +377,18 @@
404 # files from the slave.377 # files from the slave.
405 if successful_copy_from_slave:378 if successful_copy_from_slave:
406 logger.info(379 logger.info(
407 "Gathered %s %d completely. "380 "Gathered %s %d completely. Moving %s to uploader queue."
408 "Moving %s to uploader queue.",381 % (self.__class__.__name__, self.id, upload_leaf))
409 self.__class__.__name__, self.id, upload_leaf)
410 target_dir = os.path.join(root, "incoming")382 target_dir = os.path.join(root, "incoming")
411 resulting_status = BuildStatus.UPLOADING383 self.status = BuildStatus.UPLOADING
412 else:384 else:
413 logger.warning(385 logger.warning(
414 "Copy from slave for build %s was unsuccessful.",386 "Copy from slave for build %s was unsuccessful.", self.id)
415 self.id)387 self.status = BuildStatus.FAILEDTOUPLOAD
388 if send_notification:
389 self.notify(
390 extra_info='Copy from slave was unsuccessful.')
416 target_dir = os.path.join(root, "failed")391 target_dir = os.path.join(root, "failed")
417 resulting_status = BuildStatus.FAILEDTOUPLOAD
418
419 transaction.commit()
420 with DatabaseTransactionPolicy(read_only=False):
421 self.status = resulting_status
422 transaction.commit()
423
424 if not successful_copy_from_slave:
425 self._notify_if_appropriate(
426 send_notification, "Copy from slave was unsuccessful.")
427392
428 if not os.path.exists(target_dir):393 if not os.path.exists(target_dir):
429 os.mkdir(target_dir)394 os.mkdir(target_dir)
@@ -431,6 +396,10 @@
431 # Release the builder for another job.396 # Release the builder for another job.
432 d = self._release_builder_and_remove_queue_item()397 d = self._release_builder_and_remove_queue_item()
433398
399 # Commit so there are no race conditions with archiveuploader
400 # about self.status.
401 Store.of(self).commit()
402
434 # Move the directory used to grab the binaries into403 # Move the directory used to grab the binaries into
435 # the incoming directory so the upload processor never404 # the incoming directory so the upload processor never
436 # sees half-finished uploads.405 # sees half-finished uploads.
@@ -454,15 +423,14 @@
454 set the job status as FAILEDTOBUILD, store available info and423 set the job status as FAILEDTOBUILD, store available info and
455 remove Buildqueue entry.424 remove Buildqueue entry.
456 """425 """
457 transaction.commit()426 self.status = BuildStatus.FAILEDTOBUILD
458 with DatabaseTransactionPolicy(read_only=False):
459 self.status = BuildStatus.FAILEDTOBUILD
460 transaction.commit()
461427
462 def build_info_stored(ignored):428 def build_info_stored(ignored):
463 self._notify_if_appropriate(send_notification)429 if send_notification:
430 self.notify()
464 d = self.buildqueue_record.builder.cleanSlave()431 d = self.buildqueue_record.builder.cleanSlave()
465 return d.addCallback(self._destroy_buildqueue_record)432 return d.addCallback(
433 lambda x: self.buildqueue_record.destroySelf())
466434
467 d = self.storeBuildInfo(self, librarian, slave_status)435 d = self.storeBuildInfo(self, librarian, slave_status)
468 return d.addCallback(build_info_stored)436 return d.addCallback(build_info_stored)
@@ -480,9 +448,11 @@
480 def build_info_stored(ignored):448 def build_info_stored(ignored):
481 logger.critical("***** %s is MANUALDEPWAIT *****"449 logger.critical("***** %s is MANUALDEPWAIT *****"
482 % self.buildqueue_record.builder.name)450 % self.buildqueue_record.builder.name)
483 self._notify_if_appropriate(send_notification)451 if send_notification:
452 self.notify()
484 d = self.buildqueue_record.builder.cleanSlave()453 d = self.buildqueue_record.builder.cleanSlave()
485 return d.addCallback(self._destroy_buildqueue_record)454 return d.addCallback(
455 lambda x: self.buildqueue_record.destroySelf())
486456
487 d = self.storeBuildInfo(self, librarian, slave_status)457 d = self.storeBuildInfo(self, librarian, slave_status)
488 return d.addCallback(build_info_stored)458 return d.addCallback(build_info_stored)
@@ -498,24 +468,17 @@
498 self.status = BuildStatus.CHROOTWAIT468 self.status = BuildStatus.CHROOTWAIT
499469
500 def build_info_stored(ignored):470 def build_info_stored(ignored):
501 logger.critical(471 logger.critical("***** %s is CHROOTWAIT *****" %
502 "***** %s is CHROOTWAIT *****",472 self.buildqueue_record.builder.name)
503 self.buildqueue_record.builder.name)473 if send_notification:
504474 self.notify()
505 self._notify_if_appropriate(send_notification)
506 d = self.buildqueue_record.builder.cleanSlave()475 d = self.buildqueue_record.builder.cleanSlave()
507 return d.addCallback(self._destroy_buildqueue_record)476 return d.addCallback(
477 lambda x: self.buildqueue_record.destroySelf())
508478
509 d = self.storeBuildInfo(self, librarian, slave_status)479 d = self.storeBuildInfo(self, librarian, slave_status)
510 return d.addCallback(build_info_stored)480 return d.addCallback(build_info_stored)
511481
512 def _reset_buildqueue_record(self, ignored_arg=None):
513 """Reset the `BuildQueue` record, in a write transaction."""
514 transaction.commit()
515 with DatabaseTransactionPolicy(read_only=False):
516 self.buildqueue_record.reset()
517 transaction.commit()
518
519 def _handleStatus_BUILDERFAIL(self, librarian, slave_status, logger,482 def _handleStatus_BUILDERFAIL(self, librarian, slave_status, logger,
520 send_notification):483 send_notification):
521 """Handle builder failures.484 """Handle builder failures.
@@ -529,8 +492,11 @@
529 self.buildqueue_record.builder.failBuilder(492 self.buildqueue_record.builder.failBuilder(
530 "Builder returned BUILDERFAIL when asked for its status")493 "Builder returned BUILDERFAIL when asked for its status")
531494
495 def build_info_stored(ignored):
496 # simply reset job
497 self.buildqueue_record.reset()
532 d = self.storeBuildInfo(self, librarian, slave_status)498 d = self.storeBuildInfo(self, librarian, slave_status)
533 return d.addCallback(self._reset_buildqueue_record)499 return d.addCallback(build_info_stored)
534500
535 def _handleStatus_GIVENBACK(self, librarian, slave_status, logger,501 def _handleStatus_GIVENBACK(self, librarian, slave_status, logger,
536 send_notification):502 send_notification):
@@ -550,7 +516,7 @@
550 # the next Paris Summit, infinity has some ideas about how516 # the next Paris Summit, infinity has some ideas about how
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.
552 d = self.buildqueue_record.builder.cleanSlave()518 d = self.buildqueue_record.builder.cleanSlave()
553 self._reset_buildqueue_record()519 self.buildqueue_record.reset()
554 return d520 return d
555521
556 d = self.storeBuildInfo(self, librarian, slave_status)522 d = self.storeBuildInfo(self, librarian, slave_status)
557523
=== modified file 'lib/lp/buildmaster/tests/test_builder.py'
--- lib/lp/buildmaster/tests/test_builder.py 2011-12-13 13:33:04 +0000
+++ lib/lp/buildmaster/tests/test_builder.py 2011-12-19 21:48:31 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test Builder features."""4"""Test Builder features."""
@@ -8,14 +8,13 @@
8import tempfile8import tempfile
9import xmlrpclib9import xmlrpclib
1010
11from lpbuildd.slave import BuilderStatus
12from testtools.deferredruntest import (11from testtools.deferredruntest import (
13 assert_fails_with,12 assert_fails_with,
14 AsynchronousDeferredRunTest,13 AsynchronousDeferredRunTest,
15 AsynchronousDeferredRunTestForBrokenTwisted,14 AsynchronousDeferredRunTestForBrokenTwisted,
16 SynchronousDeferredRunTest,15 SynchronousDeferredRunTest,
17 )16 )
18import transaction17
19from twisted.internet.defer import (18from twisted.internet.defer import (
20 CancelledError,19 CancelledError,
21 DeferredList,20 DeferredList,
@@ -23,12 +22,15 @@
23from twisted.internet.task import Clock22from twisted.internet.task import Clock
24from twisted.python.failure import Failure23from twisted.python.failure import Failure
25from twisted.web.client import getPage24from twisted.web.client import getPage
25
26from zope.component import getUtility26from zope.component import getUtility
27from zope.security.proxy import (27from zope.security.proxy import (
28 isinstance as zope_isinstance,28 isinstance as zope_isinstance,
29 removeSecurityProxy,29 removeSecurityProxy,
30 )30 )
3131
32from lpbuildd.slave import BuilderStatus
33
32from canonical.config import config34from canonical.config import config
33from canonical.database.sqlbase import flush_database_updates35from canonical.database.sqlbase import flush_database_updates
34from canonical.launchpad.webapp.interfaces import (36from canonical.launchpad.webapp.interfaces import (
@@ -43,7 +45,6 @@
43from lp.buildmaster.enums import BuildStatus45from lp.buildmaster.enums import BuildStatus
44from lp.buildmaster.interfaces.builder import (46from lp.buildmaster.interfaces.builder import (
45 CannotFetchFile,47 CannotFetchFile,
46 CannotResumeHost,
47 IBuilder,48 IBuilder,
48 IBuilderSet,49 IBuilderSet,
49 )50 )
@@ -51,6 +52,7 @@
51 IBuildFarmJobBehavior,52 IBuildFarmJobBehavior,
52 )53 )
53from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet54from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
55from lp.buildmaster.interfaces.builder import CannotResumeHost
54from lp.buildmaster.model.builder import (56from lp.buildmaster.model.builder import (
55 BuilderSlave,57 BuilderSlave,
56 ProxyWithConnectionTimeout,58 ProxyWithConnectionTimeout,
@@ -72,8 +74,6 @@
72 TrivialBehavior,74 TrivialBehavior,
73 WaitingSlave,75 WaitingSlave,
74 )76 )
75from lp.registry.interfaces.pocket import PackagePublishingPocket
76from lp.services.database.transaction_policy import DatabaseTransactionPolicy
77from lp.services.job.interfaces.job import JobStatus77from lp.services.job.interfaces.job import JobStatus
78from lp.services.log.logger import BufferLogger78from lp.services.log.logger import BufferLogger
79from lp.soyuz.enums import (79from lp.soyuz.enums import (
@@ -155,7 +155,7 @@
155 d = lostbuilding_builder.updateStatus(BufferLogger())155 d = lostbuilding_builder.updateStatus(BufferLogger())
156 def check_slave_status(failure):156 def check_slave_status(failure):
157 self.assertIn('abort', slave.call_log)157 self.assertIn('abort', slave.call_log)
158 # 'Fault' comes from the LostBuildingBrokenSlave. This is158 # 'Fault' comes from the LostBuildingBrokenSlave, this is
159 # just testing that the value is passed through.159 # just testing that the value is passed through.
160 self.assertIsInstance(failure.value, xmlrpclib.Fault)160 self.assertIsInstance(failure.value, xmlrpclib.Fault)
161 return d.addBoth(check_slave_status)161 return d.addBoth(check_slave_status)
@@ -534,26 +534,6 @@
534 # And the old_candidate is superseded:534 # And the old_candidate is superseded:
535 self.assertEqual(BuildStatus.SUPERSEDED, build.status)535 self.assertEqual(BuildStatus.SUPERSEDED, build.status)
536536
537 def test_findBuildCandidate_postprocesses_in_read_write_policy(self):
538 # _findBuildCandidate invokes BuildFarmJob.postprocessCandidate,
539 # which may modify the database. This happens in a read-write
540 # transaction even if _findBuildCandidate itself runs in a
541 # read-only transaction policy.
542
543 # PackageBuildJob.postprocessCandidate will attempt to delete
544 # security builds.
545 pub = self.publisher.getPubSource(
546 sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
547 archive=self.factory.makeArchive(),
548 pocket=PackagePublishingPocket.SECURITY)
549 pub.createMissingBuilds()
550 transaction.commit()
551 with DatabaseTransactionPolicy(read_only=True):
552 removeSecurityProxy(self.frog_builder)._findBuildCandidate()
553 # The test is that this passes without a "transaction is
554 # read-only" error.
555 transaction.commit()
556
557 def test_acquireBuildCandidate_marks_building(self):537 def test_acquireBuildCandidate_marks_building(self):
558 # acquireBuildCandidate() should call _findBuildCandidate and538 # acquireBuildCandidate() should call _findBuildCandidate and
559 # mark the build as building.539 # mark the build as building.
560540
=== modified file 'lib/lp/buildmaster/tests/test_manager.py'
--- lib/lp/buildmaster/tests/test_manager.py 2011-12-08 11:51:59 +0000
+++ lib/lp/buildmaster/tests/test_manager.py 2011-12-19 21:48:31 +0000
@@ -1,36 +1,40 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for the renovated slave scanner aka BuilddManager."""4"""Tests for the renovated slave scanner aka BuilddManager."""
55
6from collections import namedtuple
7import os6import os
8import signal7import signal
9import time8import time
10import xmlrpclib9import xmlrpclib
1110
12from lpbuildd.tests import BuilddSlaveTestSetup
13from testtools.deferredruntest import (11from testtools.deferredruntest import (
14 assert_fails_with,12 assert_fails_with,
15 AsynchronousDeferredRunTest,13 AsynchronousDeferredRunTest,
16 )14 )
15
17import transaction16import transaction
17
18from twisted.internet import (18from twisted.internet import (
19 defer,19 defer,
20 reactor,20 reactor,
21 task,21 task,
22 )22 )
23from twisted.internet.task import deferLater23from twisted.internet.task import (
24 deferLater,
25 )
24from twisted.python.failure import Failure26from twisted.python.failure import Failure
25from zope.component import getUtility27from zope.component import getUtility
26from zope.security.proxy import removeSecurityProxy28from zope.security.proxy import removeSecurityProxy
2729
30from lpbuildd.tests import BuilddSlaveTestSetup
31
28from canonical.config import config32from canonical.config import config
29from canonical.database.constants import UTC_NOW
30from canonical.launchpad.ftests import (33from canonical.launchpad.ftests import (
31 ANONYMOUS,34 ANONYMOUS,
32 login,35 login,
33 )36 )
37from lp.services.log.logger import BufferLogger
34from canonical.testing.layers import (38from canonical.testing.layers import (
35 LaunchpadScriptLayer,39 LaunchpadScriptLayer,
36 LaunchpadZopelessLayer,40 LaunchpadZopelessLayer,
@@ -46,18 +50,14 @@
46 SlaveScanner,50 SlaveScanner,
47 )51 )
48from lp.buildmaster.model.builder import Builder52from lp.buildmaster.model.builder import Builder
49from lp.buildmaster.model.packagebuild import PackageBuild
50from lp.buildmaster.tests.harness import BuilddManagerTestSetup53from lp.buildmaster.tests.harness import BuilddManagerTestSetup
51from lp.buildmaster.tests.mock_slaves import (54from lp.buildmaster.tests.mock_slaves import (
52 BrokenSlave,55 BrokenSlave,
53 BuildingSlave,56 BuildingSlave,
54 make_publisher,57 make_publisher,
55 OkSlave,58 OkSlave,
56 WaitingSlave,
57 )59 )
58from lp.registry.interfaces.distribution import IDistributionSet60from lp.registry.interfaces.distribution import IDistributionSet
59from lp.services.database.transaction_policy import DatabaseTransactionPolicy
60from lp.services.log.logger import BufferLogger
61from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet61from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
62from lp.testing import (62from lp.testing import (
63 TestCase,63 TestCase,
@@ -65,13 +65,10 @@
65 )65 )
66from lp.testing.factory import LaunchpadObjectFactory66from lp.testing.factory import LaunchpadObjectFactory
67from lp.testing.fakemethod import FakeMethod67from lp.testing.fakemethod import FakeMethod
68from lp.testing.sampledata import (68from lp.testing.sampledata import BOB_THE_BUILDER_NAME
69 BOB_THE_BUILDER_NAME,69
70 FROG_THE_BUILDER_NAME,70
71 )71class TestSlaveScannerScan(TestCase):
72
73
74class TestSlaveScannerScan(TestCaseWithFactory):
75 """Tests `SlaveScanner.scan` method.72 """Tests `SlaveScanner.scan` method.
7673
77 This method uses the old framework for scanning and dispatching builds.74 This method uses the old framework for scanning and dispatching builds.
@@ -86,8 +83,6 @@
86 'bob' builder.83 'bob' builder.
87 """84 """
88 super(TestSlaveScannerScan, self).setUp()85 super(TestSlaveScannerScan, self).setUp()
89 self.read_only = DatabaseTransactionPolicy(read_only=True)
90
91 # Creating the required chroots needed for dispatching.86 # Creating the required chroots needed for dispatching.
92 test_publisher = make_publisher()87 test_publisher = make_publisher()
93 ubuntu = getUtility(IDistributionSet).getByName('ubuntu')88 ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
@@ -95,15 +90,6 @@
95 test_publisher.setUpDefaultDistroSeries(hoary)90 test_publisher.setUpDefaultDistroSeries(hoary)
96 test_publisher.addFakeChroots()91 test_publisher.addFakeChroots()
9792
98 def _enterReadOnly(self):
99 """Go into read-only transaction policy."""
100 self.read_only.__enter__()
101 self.addCleanup(self._exitReadOnly)
102
103 def _exitReadOnly(self):
104 """Leave read-only transaction policy."""
105 self.read_only.__exit__(None, None, None)
106
107 def _resetBuilder(self, builder):93 def _resetBuilder(self, builder):
108 """Reset the given builder and its job."""94 """Reset the given builder and its job."""
10995
@@ -114,23 +100,6 @@
114100
115 transaction.commit()101 transaction.commit()
116102
117 def getFreshBuilder(self, slave=None, name=BOB_THE_BUILDER_NAME,
118 failure_count=0):
119 """Return a builder.
120
121 The builder is taken from sample data, but reset to a usable state.
122 Be careful: this is not a proper factory method. Identical calls
123 return (and reset) the same builder. Don't rely on that though;
124 maybe someday we'll have a proper factory here.
125 """
126 if slave is None:
127 slave = OkSlave()
128 builder = getUtility(IBuilderSet)[name]
129 self._resetBuilder(builder)
130 builder.setSlaveForTesting(slave)
131 builder.failure_count = failure_count
132 return builder
133
134 def assertBuildingJob(self, job, builder, logtail=None):103 def assertBuildingJob(self, job, builder, logtail=None):
135 """Assert the given job is building on the given builder."""104 """Assert the given job is building on the given builder."""
136 from lp.services.job.interfaces.job import JobStatus105 from lp.services.job.interfaces.job import JobStatus
@@ -145,14 +114,14 @@
145 self.assertEqual(build.status, BuildStatus.BUILDING)114 self.assertEqual(build.status, BuildStatus.BUILDING)
146 self.assertEqual(job.logtail, logtail)115 self.assertEqual(job.logtail, logtail)
147116
148 def _getScanner(self, builder_name=None, clock=None):117 def _getScanner(self, builder_name=None):
149 """Instantiate a SlaveScanner object.118 """Instantiate a SlaveScanner object.
150119
151 Replace its default logging handler by a testing version.120 Replace its default logging handler by a testing version.
152 """121 """
153 if builder_name is None:122 if builder_name is None:
154 builder_name = BOB_THE_BUILDER_NAME123 builder_name = BOB_THE_BUILDER_NAME
155 scanner = SlaveScanner(builder_name, BufferLogger(), clock=clock)124 scanner = SlaveScanner(builder_name, BufferLogger())
156 scanner.logger.name = 'slave-scanner'125 scanner.logger.name = 'slave-scanner'
157126
158 return scanner127 return scanner
@@ -168,15 +137,17 @@
168 def testScanDispatchForResetBuilder(self):137 def testScanDispatchForResetBuilder(self):
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.
170139
171 # Obtain a builder. Initialize failure count to 1 so that140 # Reset sampledata builder.
172 # _checkDispatch can make sure that a successful dispatch resets141 builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME]
173 # the count to 0.142 self._resetBuilder(builder)
174 builder = self.getFreshBuilder(failure_count=1)143 builder.setSlaveForTesting(OkSlave())
144 # Set this to 1 here so that _checkDispatch can make sure it's
145 # reset to 0 after a successful dispatch.
146 builder.failure_count = 1
175147
176 # Run 'scan' and check its result.148 # Run 'scan' and check its result.
177 self.layer.txn.commit()149 self.layer.txn.commit()
178 self.layer.switchDbUser(config.builddmaster.dbuser)150 self.layer.switchDbUser(config.builddmaster.dbuser)
179 self._enterReadOnly()
180 scanner = self._getScanner()151 scanner = self._getScanner()
181 d = defer.maybeDeferred(scanner.scan)152 d = defer.maybeDeferred(scanner.scan)
182 d.addCallback(self._checkDispatch, builder)153 d.addCallback(self._checkDispatch, builder)
@@ -189,18 +160,20 @@
189 to the asynchonous dispatcher and the builder remained active160 to the asynchonous dispatcher and the builder remained active
190 and IDLE.161 and IDLE.
191 """162 """
192 self.assertIs(None, slave, "Unexpected slave.")163 self.assertTrue(slave is None, "Unexpected slave.")
193164
194 builder = getUtility(IBuilderSet).get(builder.id)165 builder = getUtility(IBuilderSet).get(builder.id)
195 self.assertTrue(builder.builderok)166 self.assertTrue(builder.builderok)
196 self.assertIs(None, builder.currentjob)167 self.assertTrue(builder.currentjob is None)
197168
198 def testNoDispatchForMissingChroots(self):169 def testNoDispatchForMissingChroots(self):
199 # When a required chroot is not present the `scan` method170 # When a required chroot is not present the `scan` method
200 # should not return any `RecordingSlaves` to be processed171 # should not return any `RecordingSlaves` to be processed
201 # and the builder used should remain active and IDLE.172 # and the builder used should remain active and IDLE.
202173
203 builder = self.getFreshBuilder()174 # Reset sampledata builder.
175 builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME]
176 self._resetBuilder(builder)
204177
205 # Remove hoary/i386 chroot.178 # Remove hoary/i386 chroot.
206 login('foo.bar@canonical.com')179 login('foo.bar@canonical.com')
@@ -213,7 +186,6 @@
213186
214 # Run 'scan' and check its result.187 # Run 'scan' and check its result.
215 self.layer.switchDbUser(config.builddmaster.dbuser)188 self.layer.switchDbUser(config.builddmaster.dbuser)
216 self._enterReadOnly()
217 scanner = self._getScanner()189 scanner = self._getScanner()
218 d = defer.maybeDeferred(scanner.singleCycle)190 d = defer.maybeDeferred(scanner.singleCycle)
219 d.addCallback(self._checkNoDispatch, builder)191 d.addCallback(self._checkNoDispatch, builder)
@@ -255,7 +227,6 @@
255227
256 # Run 'scan' and check its result.228 # Run 'scan' and check its result.
257 self.layer.switchDbUser(config.builddmaster.dbuser)229 self.layer.switchDbUser(config.builddmaster.dbuser)
258 self._enterReadOnly()
259 scanner = self._getScanner()230 scanner = self._getScanner()
260 d = defer.maybeDeferred(scanner.scan)231 d = defer.maybeDeferred(scanner.scan)
261 d.addCallback(self._checkJobRescued, builder, job)232 d.addCallback(self._checkJobRescued, builder, job)
@@ -291,27 +262,25 @@
291262
292 # Run 'scan' and check its result.263 # Run 'scan' and check its result.
293 self.layer.switchDbUser(config.builddmaster.dbuser)264 self.layer.switchDbUser(config.builddmaster.dbuser)
294 self._enterReadOnly()
295 scanner = self._getScanner()265 scanner = self._getScanner()
296 d = defer.maybeDeferred(scanner.scan)266 d = defer.maybeDeferred(scanner.scan)
297 d.addCallback(self._checkJobUpdated, builder, job)267 d.addCallback(self._checkJobUpdated, builder, job)
298 return d268 return d
299269
300 def test_scan_with_nothing_to_dispatch(self):270 def test_scan_with_nothing_to_dispatch(self):
301 builder = self.factory.makeBuilder()271 factory = LaunchpadObjectFactory()
272 builder = factory.makeBuilder()
302 builder.setSlaveForTesting(OkSlave())273 builder.setSlaveForTesting(OkSlave())
303 transaction.commit()
304 self._enterReadOnly()
305 scanner = self._getScanner(builder_name=builder.name)274 scanner = self._getScanner(builder_name=builder.name)
306 d = scanner.scan()275 d = scanner.scan()
307 return d.addCallback(self._checkNoDispatch, builder)276 return d.addCallback(self._checkNoDispatch, builder)
308277
309 def test_scan_with_manual_builder(self):278 def test_scan_with_manual_builder(self):
310 # Reset sampledata builder.279 # Reset sampledata builder.
311 builder = self.getFreshBuilder()280 builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME]
281 self._resetBuilder(builder)
282 builder.setSlaveForTesting(OkSlave())
312 builder.manual = True283 builder.manual = True
313 transaction.commit()
314 self._enterReadOnly()
315 scanner = self._getScanner()284 scanner = self._getScanner()
316 d = scanner.scan()285 d = scanner.scan()
317 d.addCallback(self._checkNoDispatch, builder)286 d.addCallback(self._checkNoDispatch, builder)
@@ -319,10 +288,10 @@
319288
320 def test_scan_with_not_ok_builder(self):289 def test_scan_with_not_ok_builder(self):
321 # Reset sampledata builder.290 # Reset sampledata builder.
322 builder = self.getFreshBuilder()291 builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME]
292 self._resetBuilder(builder)
293 builder.setSlaveForTesting(OkSlave())
323 builder.builderok = False294 builder.builderok = False
324 transaction.commit()
325 self._enterReadOnly()
326 scanner = self._getScanner()295 scanner = self._getScanner()
327 d = scanner.scan()296 d = scanner.scan()
328 # Because the builder is not ok, we can't use _checkNoDispatch.297 # Because the builder is not ok, we can't use _checkNoDispatch.
@@ -331,27 +300,25 @@
331 return d300 return d
332301
333 def test_scan_of_broken_slave(self):302 def test_scan_of_broken_slave(self):
334 builder = self.getFreshBuilder(slave=BrokenSlave())303 builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME]
335 transaction.commit()304 self._resetBuilder(builder)
336 self._enterReadOnly()305 builder.setSlaveForTesting(BrokenSlave())
306 builder.failure_count = 0
337 scanner = self._getScanner(builder_name=builder.name)307 scanner = self._getScanner(builder_name=builder.name)
338 d = scanner.scan()308 d = scanner.scan()
339 return assert_fails_with(d, xmlrpclib.Fault)309 return assert_fails_with(d, xmlrpclib.Fault)
340310
341 def _assertFailureCounting(self, builder_count, job_count,311 def _assertFailureCounting(self, builder_count, job_count,
342 expected_builder_count, expected_job_count):312 expected_builder_count, expected_job_count):
343 # Avoid circular imports.
344 from lp.buildmaster import manager as manager_module
345
346 # If scan() fails with an exception, failure_counts should be313 # If scan() fails with an exception, failure_counts should be
347 # incremented. What we do with the results of the failure314 # incremented. What we do with the results of the failure
348 # counts is tested below separately, this test just makes sure that315 # counts is tested below separately, this test just makes sure that
349 # scan() is setting the counts.316 # scan() is setting the counts.
350 def failing_scan():317 def failing_scan():
351 return defer.fail(Exception("fake exception"))318 return defer.fail(Exception("fake exception"))
352
353 scanner = self._getScanner()319 scanner = self._getScanner()
354 scanner.scan = failing_scan320 scanner.scan = failing_scan
321 from lp.buildmaster import manager as manager_module
355 self.patch(manager_module, 'assessFailureCounts', FakeMethod())322 self.patch(manager_module, 'assessFailureCounts', FakeMethod())
356 builder = getUtility(IBuilderSet)[scanner.builder_name]323 builder = getUtility(IBuilderSet)[scanner.builder_name]
357324
@@ -499,60 +466,6 @@
499 d.addCallback(check_cancelled, builder, buildqueue)466 d.addCallback(check_cancelled, builder, buildqueue)
500 return d467 return d
501468
502 def makeFakeFailure(self):
503 """Produce a fake failure for use with SlaveScanner._scanFailed."""
504 FakeFailure = namedtuple('FakeFailure', ['getErrorMessage', 'check'])
505 return FakeFailure(
506 FakeMethod(self.factory.getUniqueString()),
507 FakeMethod(True))
508
509 def test_interleaved_success_and_failure_do_not_interfere(self):
510 # It's possible for one builder to fail while another continues
511 # to function properly. When that happens, the failed builder
512 # may cause database changes to be rolled back. But that does
513 # not affect the functioning builder.
514 clock = task.Clock()
515
516 broken_builder = self.getFreshBuilder(
517 slave=BrokenSlave(), name=BOB_THE_BUILDER_NAME)
518 broken_scanner = self._getScanner(builder_name=broken_builder.name)
519 good_builder = self.getFreshBuilder(
520 slave=WaitingSlave(), name=FROG_THE_BUILDER_NAME)
521 good_build = self.factory.makeBinaryPackageBuild(
522 distroarchseries=self.factory.makeDistroArchSeries())
523
524 # The good build is being handled by the good builder.
525 buildqueue = good_build.queueBuild()
526 buildqueue.builder = good_builder
527
528 removeSecurityProxy(good_build.build_farm_job).date_started = UTC_NOW
529
530 # The good builder requests information from a successful build,
531 # and up receiving it, updates the build's metadata.
532 # Our dependencies string goes into the build, and its
533 # date_finished will be set.
534 dependencies = self.factory.getUniqueString()
535 PackageBuild.storeBuildInfo(
536 good_build, None, {'dependencies': dependencies})
537 clock.advance(1)
538
539 # The broken scanner experiences a failure before the good
540 # scanner is receiving its data. This aborts the ongoing
541 # transaction.
542 # As a somewhat weird example, if the builder changed its own
543 # title, that change will be rolled back.
544 original_broken_builder_title = broken_builder.title
545 broken_builder.title = self.factory.getUniqueString()
546 broken_scanner._scanFailed(self.makeFakeFailure())
547
548 # The work done by the good scanner is retained. The
549 # storeBuildInfo code committed it.
550 self.assertEqual(dependencies, good_build.dependencies)
551 self.assertIsNot(None, good_build.date_finished)
552
553 # The work done by the broken scanner is rolled back.
554 self.assertEqual(original_broken_builder_title, broken_builder.title)
555
556469
557class TestCancellationChecking(TestCaseWithFactory):470class TestCancellationChecking(TestCaseWithFactory):
558 """Unit tests for the checkCancellation method."""471 """Unit tests for the checkCancellation method."""
559472
=== modified file 'lib/lp/buildmaster/tests/test_packagebuild.py'
--- lib/lp/buildmaster/tests/test_packagebuild.py 2011-11-09 11:50:17 +0000
+++ lib/lp/buildmaster/tests/test_packagebuild.py 2011-12-19 21:48:31 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010-2011 Canonical Ltd. This software is licensed under the1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for `IPackageBuild`."""4"""Tests for `IPackageBuild`."""
@@ -22,7 +22,9 @@
22 LaunchpadFunctionalLayer,22 LaunchpadFunctionalLayer,
23 LaunchpadZopelessLayer,23 LaunchpadZopelessLayer,
24 )24 )
25from lp.archiveuploader.uploadprocessor import parse_build_upload_leaf_name25from lp.archiveuploader.uploadprocessor import (
26 parse_build_upload_leaf_name,
27 )
26from lp.buildmaster.enums import (28from lp.buildmaster.enums import (
27 BuildFarmJobType,29 BuildFarmJobType,
28 BuildStatus,30 BuildStatus,
@@ -32,11 +34,12 @@
32 IPackageBuildSet,34 IPackageBuildSet,
33 IPackageBuildSource,35 IPackageBuildSource,
34 )36 )
35from lp.buildmaster.model.builder import BuilderSlave
36from lp.buildmaster.model.buildfarmjob import BuildFarmJob37from lp.buildmaster.model.buildfarmjob import BuildFarmJob
37from lp.buildmaster.model.packagebuild import PackageBuild38from lp.buildmaster.model.packagebuild import PackageBuild
38from lp.buildmaster.tests.mock_slaves import WaitingSlave39from lp.buildmaster.tests.mock_slaves import WaitingSlave
39from lp.registry.interfaces.pocket import PackagePublishingPocket40from lp.registry.interfaces.pocket import (
41 PackagePublishingPocket,
42 )
40from lp.testing import (43from lp.testing import (
41 login,44 login,
42 login_person,45 login_person,
@@ -282,7 +285,10 @@
282285
283286
284class TestHandleStatusMixin:287class TestHandleStatusMixin:
285 """Tests for `IPackageBuild`s handleStatus method."""288 """Tests for `IPackageBuild`s handleStatus method.
289
290 This should be run with a Trial TestCase.
291 """
286292
287 layer = LaunchpadZopelessLayer293 layer = LaunchpadZopelessLayer
288294
@@ -301,7 +307,7 @@
301 self.build.buildqueue_record.setDateStarted(UTC_NOW)307 self.build.buildqueue_record.setDateStarted(UTC_NOW)
302 self.slave = WaitingSlave('BuildStatus.OK')308 self.slave = WaitingSlave('BuildStatus.OK')
303 self.slave.valid_file_hashes.append('test_file_hash')309 self.slave.valid_file_hashes.append('test_file_hash')
304 self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(self.slave))310 builder.setSlaveForTesting(self.slave)
305311
306 # We overwrite the buildmaster root to use a temp directory.312 # We overwrite the buildmaster root to use a temp directory.
307 tempdir = tempfile.mkdtemp()313 tempdir = tempfile.mkdtemp()
@@ -342,7 +348,7 @@
342 def got_status(ignored):348 def got_status(ignored):
343 self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status)349 self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status)
344 self.assertResultCount(0, "failed")350 self.assertResultCount(0, "failed")
345 self.assertIs(None, self.build.buildqueue_record)351 self.assertIdentical(None, self.build.buildqueue_record)
346352
347 d = self.build.handleStatus('OK', None, {353 d = self.build.handleStatus('OK', None, {
348 'filemap': {'/tmp/myfile.py': 'test_file_hash'},354 'filemap': {'/tmp/myfile.py': 'test_file_hash'},
@@ -384,10 +390,14 @@
384390
385 def got_status(ignored):391 def got_status(ignored):
386 if expected_notification:392 if expected_notification:
387 self.assertNotEqual(393 self.failIf(
388 0, len(pop_notifications()), "No notifications received.")394 len(pop_notifications()) == 0,
395 "No notifications received")
389 else:396 else:
390 self.assertContentEqual([], pop_notifications())397 self.failIf(
398 len(pop_notifications()) > 0,
399 "Notifications received")
400
391 d = self.build.handleStatus(status, None, {})401 d = self.build.handleStatus(status, None, {})
392 return d.addCallback(got_status)402 return d.addCallback(got_status)
393403
394404
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2011-12-08 16:04:13 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2011-12-19 21:48:31 +0000
@@ -13,8 +13,8 @@
1313
14from pytz import utc14from pytz import utc
15from storm.locals import Store15from storm.locals import Store
16from testtools.deferredruntest import AsynchronousDeferredRunTest
17import transaction16import transaction
17from twisted.trial.unittest import TestCase as TrialTestCase
18from zope.component import getUtility18from zope.component import getUtility
19from zope.security.proxy import removeSecurityProxy19from zope.security.proxy import removeSecurityProxy
2020
@@ -28,7 +28,6 @@
28from lp.app.errors import NotFoundError28from lp.app.errors import NotFoundError
29from lp.buildmaster.enums import BuildStatus29from lp.buildmaster.enums import BuildStatus
30from lp.buildmaster.interfaces.buildqueue import IBuildQueue30from lp.buildmaster.interfaces.buildqueue import IBuildQueue
31from lp.buildmaster.model.builder import BuilderSlave
32from lp.buildmaster.model.buildfarmjob import BuildFarmJob31from lp.buildmaster.model.buildfarmjob import BuildFarmJob
33from lp.buildmaster.model.packagebuild import PackageBuild32from lp.buildmaster.model.packagebuild import PackageBuild
34from lp.buildmaster.tests.mock_slaves import WaitingSlave33from lp.buildmaster.tests.mock_slaves import WaitingSlave
@@ -589,11 +588,14 @@
589 self.assertEquals(0, len(notifications))588 self.assertEquals(0, len(notifications))
590589
591590
592class TestBuildNotifications(TestCaseWithFactory):591class TestBuildNotifications(TrialTestCase):
593592
594 layer = LaunchpadZopelessLayer593 layer = LaunchpadZopelessLayer
595594
596 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20)595 def setUp(self):
596 super(TestBuildNotifications, self).setUp()
597 from lp.testing.factory import LaunchpadObjectFactory
598 self.factory = LaunchpadObjectFactory()
597599
598 def prepare_build(self, fake_successful_upload=False):600 def prepare_build(self, fake_successful_upload=False):
599 queue_record = self.factory.makeSourcePackageRecipeBuildJob()601 queue_record = self.factory.makeSourcePackageRecipeBuildJob()
@@ -606,7 +608,7 @@
606 result=True)608 result=True)
607 queue_record.builder = self.factory.makeBuilder()609 queue_record.builder = self.factory.makeBuilder()
608 slave = WaitingSlave('BuildStatus.OK')610 slave = WaitingSlave('BuildStatus.OK')
609 self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(slave))611 queue_record.builder.setSlaveForTesting(slave)
610 return build612 return build
611613
612 def assertDeferredNotifyCount(self, status, build, expected_count):614 def assertDeferredNotifyCount(self, status, build, expected_count):
@@ -664,5 +666,5 @@
664666
665667
666class TestHandleStatusForSPRBuild(668class TestHandleStatusForSPRBuild(
667 MakeSPRecipeBuildMixin, TestHandleStatusMixin, TestCaseWithFactory):669 MakeSPRecipeBuildMixin, TestHandleStatusMixin, TrialTestCase):
668 """IPackageBuild.handleStatus works with SPRecipe builds."""670 """IPackageBuild.handleStatus works with SPRecipe builds."""
669671
=== modified file 'lib/lp/services/database/transaction_policy.py'
--- lib/lp/services/database/transaction_policy.py 2011-10-10 06:23:12 +0000
+++ lib/lp/services/database/transaction_policy.py 2011-12-19 21:48:31 +0000
@@ -133,11 +133,8 @@
133 def _isInTransaction(self):133 def _isInTransaction(self):
134 """Is our store currently in a transaction?"""134 """Is our store currently in a transaction?"""
135 pg_connection = self.store._connection._raw_connection135 pg_connection = self.store._connection._raw_connection
136 if pg_connection is None:136 status = pg_connection.get_transaction_status()
137 return False137 return status != TRANSACTION_STATUS_IDLE
138 else:
139 status = pg_connection.get_transaction_status()
140 return status != TRANSACTION_STATUS_IDLE
141138
142 def _checkNoTransaction(self, error_msg):139 def _checkNoTransaction(self, error_msg):
143 """Verify that no transaction is ongoing.140 """Verify that no transaction is ongoing.
144141
=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
--- lib/lp/soyuz/tests/test_binarypackagebuild.py 2011-11-14 06:36:57 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2011-12-19 21:48:31 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test Build features."""4"""Test Build features."""
@@ -10,7 +10,7 @@
1010
11import pytz11import pytz
12from storm.store import Store12from storm.store import Store
13from testtools.deferredruntest import AsynchronousDeferredRunTest13from twisted.trial.unittest import TestCase as TrialTestCase
14from zope.component import getUtility14from zope.component import getUtility
15from zope.security.proxy import removeSecurityProxy15from zope.security.proxy import removeSecurityProxy
1616
@@ -25,7 +25,6 @@
25from lp.buildmaster.interfaces.builder import IBuilderSet25from lp.buildmaster.interfaces.builder import IBuilderSet
26from lp.buildmaster.interfaces.buildqueue import IBuildQueue26from lp.buildmaster.interfaces.buildqueue import IBuildQueue
27from lp.buildmaster.interfaces.packagebuild import IPackageBuild27from lp.buildmaster.interfaces.packagebuild import IPackageBuild
28from lp.buildmaster.model.builder import BuilderSlave
29from lp.buildmaster.model.buildqueue import BuildQueue28from lp.buildmaster.model.buildqueue import BuildQueue
30from lp.buildmaster.tests.mock_slaves import WaitingSlave29from lp.buildmaster.tests.mock_slaves import WaitingSlave
31from lp.buildmaster.tests.test_packagebuild import (30from lp.buildmaster.tests.test_packagebuild import (
@@ -54,7 +53,6 @@
54 logout,53 logout,
55 TestCaseWithFactory,54 TestCaseWithFactory,
56 )55 )
57from lp.testing.fakemethod import FakeMethod
5856
5957
60class TestBinaryPackageBuild(TestCaseWithFactory):58class TestBinaryPackageBuild(TestCaseWithFactory):
@@ -524,9 +522,7 @@
524 self.build = gedit_src_hist.createMissingBuilds()[0]522 self.build = gedit_src_hist.createMissingBuilds()[0]
525523
526 self.builder = self.factory.makeBuilder()524 self.builder = self.factory.makeBuilder()
527 self.patch(525 self.builder.setSlaveForTesting(WaitingSlave('BuildStatus.OK'))
528 BuilderSlave, 'makeBuilderSlave',
529 FakeMethod(WaitingSlave('BuildStatus.OK')))
530 self.build.buildqueue_record.markAsBuilding(self.builder)526 self.build.buildqueue_record.markAsBuilding(self.builder)
531527
532 def testDependencies(self):528 def testDependencies(self):
@@ -572,12 +568,9 @@
572568
573569
574class TestHandleStatusForBinaryPackageBuild(570class TestHandleStatusForBinaryPackageBuild(
575 MakeBinaryPackageBuildMixin, TestHandleStatusMixin, TestCaseWithFactory):571 MakeBinaryPackageBuildMixin, TestHandleStatusMixin, TrialTestCase):
576 """IPackageBuild.handleStatus works with binary builds."""572 """IPackageBuild.handleStatus works with binary builds."""
577573
578 layer = LaunchpadZopelessLayer
579 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20)
580
581574
582class TestBinaryPackageBuildWebservice(TestCaseWithFactory):575class TestBinaryPackageBuildWebservice(TestCaseWithFactory):
583 """Test cases for BinaryPackageBuild on the webservice.576 """Test cases for BinaryPackageBuild on the webservice.
584577
=== modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py'
--- lib/lp/translations/model/translationtemplatesbuildbehavior.py 2011-10-20 07:45:52 +0000
+++ lib/lp/translations/model/translationtemplatesbuildbehavior.py 2011-12-19 21:48:31 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010-2011 Canonical Ltd. This software is licensed under the1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""An `IBuildFarmJobBehavior` for `TranslationTemplatesBuildJob`.4"""An `IBuildFarmJobBehavior` for `TranslationTemplatesBuildJob`.
@@ -13,10 +13,9 @@
1313
14import datetime14import datetime
15import os15import os
16import pytz
16import tempfile17import tempfile
1718
18import pytz
19import transaction
20from twisted.internet import defer19from twisted.internet import defer
21from zope.component import getUtility20from zope.component import getUtility
22from zope.interface import implements21from zope.interface import implements
@@ -29,7 +28,6 @@
29 )28 )
30from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase29from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase
31from lp.registry.interfaces.productseries import IProductSeriesSet30from lp.registry.interfaces.productseries import IProductSeriesSet
32from lp.services.database.transaction_policy import DatabaseTransactionPolicy
33from lp.translations.interfaces.translationimportqueue import (31from lp.translations.interfaces.translationimportqueue import (
34 ITranslationImportQueue,32 ITranslationImportQueue,
35 )33 )
@@ -134,16 +132,13 @@
134 def storeBuildInfo(build, queue_item, build_status):132 def storeBuildInfo(build, queue_item, build_status):
135 """See `IPackageBuild`."""133 """See `IPackageBuild`."""
136 def got_log(lfa_id):134 def got_log(lfa_id):
137 transaction.commit()135 build.build.log = lfa_id
138 with DatabaseTransactionPolicy(read_only=False):136 build.build.builder = queue_item.builder
139 build.build.log = lfa_id137 build.build.date_started = queue_item.date_started
140 build.build.builder = queue_item.builder138 # XXX cprov 20060615 bug=120584: Currently buildduration includes
141 build.build.date_started = queue_item.date_started139 # the scanner latency, it should really be asking the slave for
142 # XXX cprov 20060615 bug=120584: Currently buildduration140 # the duration spent building locally.
143 # includes the scanner latency. It should really be141 build.build.date_finished = datetime.datetime.now(pytz.UTC)
144 # asking the slave for the duration spent building locally.
145 build.build.date_finished = datetime.datetime.now(pytz.UTC)
146 transaction.commit()
147142
148 d = build.getLogFromSlave(build, queue_item)143 d = build.getLogFromSlave(build, queue_item)
149 return d.addCallback(got_log)144 return d.addCallback(got_log)