launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06006
lp:~allenap/launchpad/builder-rescue-if-lost-ro-crash-bug-906079 into lp:launchpad
Gavin Panella has proposed merging lp:~allenap/launchpad/builder-rescue-if-lost-ro-crash-bug-906079 into lp:launchpad with lp:~allenap/launchpad/translation-templates-build-behaviour-ro-crash-bug-905855 as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #906079 in Launchpad itself: "Builder.rescueIfLost writes in a read-only transaction"
https://bugs.launchpad.net/launchpad/+bug/906079
For more details, see:
https://code.launchpad.net/~allenap/launchpad/builder-rescue-if-lost-ro-crash-bug-906079/+merge/86803
Uses BuilddManagerTestFixture extensively in the remaining
lp.buildmaster tests to try and ensure that tests are running in the
correct environment.
--
https://code.launchpad.net/~allenap/launchpad/builder-rescue-if-lost-ro-crash-bug-906079/+merge/86803
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/builder-rescue-if-lost-ro-crash-bug-906079 into lp:launchpad.
=== modified file 'lib/lp/buildmaster/manager.py'
--- lib/lp/buildmaster/manager.py 2011-12-23 13:30:34 +0000
+++ lib/lp/buildmaster/manager.py 2011-12-23 13:30:35 +0000
@@ -217,8 +217,9 @@
return defer.succeed(True)
self.logger.info("Cancelling build '%s'" % build.title)
- buildqueue.cancel()
- transaction.commit()
+ with DatabaseTransactionPolicy(read_only=False):
+ buildqueue.cancel()
+ transaction.commit()
d = builder.resumeSlaveHost()
d.addCallback(resume_done)
return d
=== modified file 'lib/lp/buildmaster/model/packagebuild.py'
--- lib/lp/buildmaster/model/packagebuild.py 2011-12-23 13:30:34 +0000
+++ lib/lp/buildmaster/model/packagebuild.py 2011-12-23 13:30:35 +0000
@@ -293,6 +293,7 @@
job=job, processor=processor,
virtualized=specific_job.virtualized)
Store.of(self).add(queue_entry)
+
return queue_entry
def handleStatus(self, status, librarian, slave_status):
=== modified file 'lib/lp/buildmaster/testing.py'
--- lib/lp/buildmaster/testing.py 2011-12-23 13:30:34 +0000
+++ lib/lp/buildmaster/testing.py 2011-12-23 13:30:35 +0000
@@ -9,6 +9,7 @@
]
from contextlib import contextmanager
+from functools import wraps
import fixtures
import transaction
@@ -42,18 +43,38 @@
self.addCleanup(self.policy.__exit__, None, None, None)
@staticmethod
- @contextmanager
- def extraSetUp():
+ def extraSetUp(func=None):
"""Temporarily enter a read-write transaction to do extra setup.
For example:
- with self.extraSetUp():
+ with BuilddManagerTestFixture.extraSetUp():
removeSecurityProxy(self.build).date_finished = None
On exit it will commit the changes and restore the previous
transaction access mode.
+
+ Alternatively it can be used as a decorator:
+
+ @BuilddManagerTestFixture.extraSetUp
+ def makeSomethingOrOther(self):
+ return ...
+
+ Like with the context manager, on return it will commit the changes
+ and restore the previous transaction access mode.
"""
- with DatabaseTransactionPolicy(read_only=False):
- yield
- transaction.commit()
+ if func is None:
+ @contextmanager
+ def context():
+ with DatabaseTransactionPolicy(read_only=False):
+ yield
+ transaction.commit()
+ return context()
+ else:
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ with DatabaseTransactionPolicy(read_only=False):
+ result = func(*args, **kwargs)
+ transaction.commit()
+ return result
+ return wrapper
=== modified file 'lib/lp/buildmaster/tests/test_builder.py'
--- lib/lp/buildmaster/tests/test_builder.py 2011-12-23 13:30:34 +0000
+++ lib/lp/buildmaster/tests/test_builder.py 2011-12-23 13:30:35 +0000
@@ -36,10 +36,7 @@
IStoreSelector,
MAIN_STORE,
)
-from canonical.testing.layers import (
- DatabaseFunctionalLayer,
- LaunchpadZopelessLayer,
- )
+from canonical.testing.layers import LaunchpadZopelessLayer
from lp.buildmaster.enums import BuildStatus
from lp.buildmaster.interfaces.builder import (
CannotFetchFile,
@@ -57,6 +54,7 @@
)
from lp.buildmaster.model.buildfarmjobbehavior import IdleBuildBehavior
from lp.buildmaster.model.buildqueue import BuildQueue
+from lp.buildmaster.testing import BuilddManagerTestFixture
from lp.buildmaster.tests.mock_slaves import (
AbortedSlave,
AbortingSlave,
@@ -73,7 +71,6 @@
WaitingSlave,
)
from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.services.database.transaction_policy import DatabaseTransactionPolicy
from lp.services.job.interfaces.job import JobStatus
from lp.services.log.logger import BufferLogger
from lp.soyuz.enums import (
@@ -94,46 +91,63 @@
class TestBuilderBasics(TestCaseWithFactory):
"""Basic unit tests for `Builder`."""
- layer = DatabaseFunctionalLayer
+ layer = LaunchpadZopelessLayer
+
+ def setUp(self):
+ super(TestBuilderBasics, self).setUp()
+ self.useFixture(BuilddManagerTestFixture())
def test_providesInterface(self):
# Builder provides IBuilder
- builder = self.factory.makeBuilder()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.factory.makeBuilder()
self.assertProvides(builder, IBuilder)
def test_default_values(self):
- builder = self.factory.makeBuilder()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.factory.makeBuilder()
# Make sure the Storm cache gets the values that the database
# initializes.
flush_database_updates()
+ self.useFixture(BuilddManagerTestFixture())
self.assertEqual(0, builder.failure_count)
def test_getCurrentBuildFarmJob(self):
- bq = self.factory.makeSourcePackageRecipeBuildJob(3333)
- builder = self.factory.makeBuilder()
- bq.markAsBuilding(builder)
+ with BuilddManagerTestFixture.extraSetUp():
+ bq = self.factory.makeSourcePackageRecipeBuildJob(3333)
+ builder = self.factory.makeBuilder()
+ bq.markAsBuilding(builder)
+ self.useFixture(BuilddManagerTestFixture())
self.assertEqual(
bq, builder.getCurrentBuildFarmJob().buildqueue_record)
def test_getBuildQueue(self):
- buildqueueset = getUtility(IBuildQueueSet)
- active_jobs = buildqueueset.getActiveBuildJobs()
- [active_job] = active_jobs
- builder = active_job.builder
+ with BuilddManagerTestFixture.extraSetUp():
+ buildqueueset = getUtility(IBuildQueueSet)
+ active_jobs = buildqueueset.getActiveBuildJobs()
+ [active_job] = active_jobs
+ builder = active_job.builder
bq = builder.getBuildQueue()
self.assertEqual(active_job, bq)
- active_job.builder = None
+ with BuilddManagerTestFixture.extraSetUp():
+ active_job.builder = None
+
bq = builder.getBuildQueue()
self.assertIs(None, bq)
def test_setting_builderok_resets_failure_count(self):
- builder = removeSecurityProxy(self.factory.makeBuilder())
- builder.failure_count = 1
- builder.builderok = False
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = removeSecurityProxy(self.factory.makeBuilder())
+ builder.failure_count = 1
+ builder.builderok = False
+
self.assertEqual(1, builder.failure_count)
- builder.builderok = True
+
+ with BuilddManagerTestFixture.extraSetUp():
+ builder.builderok = True
+
self.assertEqual(0, builder.failure_count)
@@ -145,6 +159,7 @@
def setUp(self):
super(TestBuilder, self).setUp()
self.slave_helper = self.useFixture(SlaveTestHelpers())
+ self.useFixture(BuilddManagerTestFixture())
def test_updateStatus_aborts_lost_and_broken_slave(self):
# A slave that's 'lost' should be aborted; when the slave is
@@ -161,12 +176,14 @@
return d.addBoth(check_slave_status)
def test_resumeSlaveHost_nonvirtual(self):
- builder = self.factory.makeBuilder(virtualized=False)
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.factory.makeBuilder(virtualized=False)
d = builder.resumeSlaveHost()
return assert_fails_with(d, CannotResumeHost)
def test_resumeSlaveHost_no_vmhost(self):
- builder = self.factory.makeBuilder(virtualized=True, vm_host=None)
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.factory.makeBuilder(virtualized=True, vm_host=None)
d = builder.resumeSlaveHost()
return assert_fails_with(d, CannotResumeHost)
@@ -177,7 +194,8 @@
config.push('reset', reset_config)
self.addCleanup(config.pop, 'reset')
- builder = self.factory.makeBuilder(virtualized=True, vm_host="pop")
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.factory.makeBuilder(virtualized=True, vm_host="pop")
d = builder.resumeSlaveHost()
def got_resume(output):
self.assertEqual(('parp', ''), output)
@@ -189,7 +207,8 @@
vm_resume_command: /bin/false"""
config.push('reset fail', reset_fail_config)
self.addCleanup(config.pop, 'reset fail')
- builder = self.factory.makeBuilder(virtualized=True, vm_host="pop")
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.factory.makeBuilder(virtualized=True, vm_host="pop")
d = builder.resumeSlaveHost()
return assert_fails_with(d, CannotResumeHost)
@@ -199,11 +218,13 @@
vm_resume_command: /bin/false"""
config.push('reset fail', reset_fail_config)
self.addCleanup(config.pop, 'reset fail')
- builder = self.factory.makeBuilder(virtualized=True, vm_host="pop")
- builder.builderok = True
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.factory.makeBuilder(virtualized=True, vm_host="pop")
+ builder.builderok = True
d = builder.handleTimeout(BufferLogger(), 'blah')
return assert_fails_with(d, CannotResumeHost)
+ @BuilddManagerTestFixture.extraSetUp
def _setupBuilder(self):
processor = self.factory.makeProcessor(name="i386")
builder = self.factory.makeBuilder(
@@ -218,6 +239,7 @@
distroseries.nominatedarchindep = das
return builder, distroseries, das
+ @BuilddManagerTestFixture.extraSetUp
def _setupRecipeBuildAndBuilder(self):
# Helper function to make a builder capable of building a
# recipe, returning both.
@@ -226,6 +248,7 @@
distroseries=distroseries)
return builder, build
+ @BuilddManagerTestFixture.extraSetUp
def _setupBinaryBuildAndBuilder(self):
# Helper function to make a builder capable of building a
# binary package, returning both.
@@ -238,7 +261,8 @@
# findAndStartJob finds the next queued job using _findBuildCandidate.
# We don't care about the type of build at all.
builder, build = self._setupRecipeBuildAndBuilder()
- candidate = build.queueBuild()
+ with BuilddManagerTestFixture.extraSetUp():
+ candidate = build.queueBuild()
# _findBuildCandidate is tested elsewhere, we just make sure that
# findAndStartJob delegates to it.
removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
@@ -251,7 +275,8 @@
# and then starts it.
# We don't care about the type of build at all.
builder, build = self._setupRecipeBuildAndBuilder()
- candidate = build.queueBuild()
+ with BuilddManagerTestFixture.extraSetUp():
+ candidate = build.queueBuild()
removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
result=candidate)
d = builder.findAndStartJob()
@@ -264,7 +289,8 @@
# We need to send a ping to the builder to work around a bug
# where sometimes the first network packet sent is dropped.
builder, build = self._setupBinaryBuildAndBuilder()
- candidate = build.queueBuild()
+ with BuilddManagerTestFixture.extraSetUp():
+ candidate = build.queueBuild()
removeSecurityProxy(builder)._findBuildCandidate = FakeMethod(
result=candidate)
d = builder.findAndStartJob()
@@ -277,7 +303,8 @@
# Builder.slave is a BuilderSlave that points at the actual Builder.
# The Builder is only ever used in scripts that run outside of the
# security context.
- builder = removeSecurityProxy(self.factory.makeBuilder())
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = removeSecurityProxy(self.factory.makeBuilder())
self.assertEqual(builder.url, builder.slave.url)
def test_recovery_of_aborted_virtual_slave(self):
@@ -347,7 +374,8 @@
# rescueIfLost does not attempt to abort or clean a builder that is
# BUILDING.
building_slave = BuildingSlave()
- builder = MockBuilder("mock_builder", building_slave, TrivialBehavior())
+ builder = MockBuilder(
+ "mock_builder", building_slave, TrivialBehavior())
d = builder.rescueIfLost()
def check_slave_calls(ignored):
self.assertNotIn('abort', building_slave.call_log)
@@ -371,10 +399,11 @@
# too so that we don't traceback when the wrong behaviour tries
# to access a non-existent job.
builder, build = self._setupBinaryBuildAndBuilder()
- candidate = build.queueBuild()
- building_slave = BuildingSlave()
- builder.setSlaveForTesting(building_slave)
- candidate.markAsBuilding(builder)
+ with BuilddManagerTestFixture.extraSetUp():
+ candidate = build.queueBuild()
+ building_slave = BuildingSlave()
+ builder.setSlaveForTesting(building_slave)
+ candidate.markAsBuilding(builder)
# At this point we should see a valid behaviour on the builder:
self.assertFalse(
@@ -382,8 +411,8 @@
builder.current_build_behavior, IdleBuildBehavior))
# Now reset the job and try to rescue the builder.
- candidate.destroySelf()
- self.layer.txn.commit()
+ with BuilddManagerTestFixture.extraSetUp():
+ candidate.destroySelf()
builder = getUtility(IBuilderSet)[builder.name]
d = builder.rescueIfLost()
def check_builder(ignored):
@@ -403,13 +432,14 @@
def setUp(self):
super(TestBuilderSlaveStatus, self).setUp()
self.slave_helper = self.useFixture(SlaveTestHelpers())
+ self.builder = self.factory.makeBuilder()
+ self.useFixture(BuilddManagerTestFixture())
def assertStatus(self, slave, builder_status=None,
build_status=None, logtail=False, filemap=None,
dependencies=None):
- builder = self.factory.makeBuilder()
- builder.setSlaveForTesting(slave)
- d = builder.slaveStatus()
+ self.builder.setSlaveForTesting(slave)
+ d = self.builder.slaveStatus()
def got_status(status_dict):
expected = {}
@@ -454,21 +484,19 @@
def test_isAvailable_with_not_builderok(self):
# isAvailable() is a wrapper around slaveStatusSentence()
- builder = self.factory.makeBuilder()
- builder.builderok = False
- d = builder.isAvailable()
+ with BuilddManagerTestFixture.extraSetUp():
+ self.builder.builderok = False
+ d = self.builder.isAvailable()
return d.addCallback(self.assertFalse)
def test_isAvailable_with_slave_fault(self):
- builder = self.factory.makeBuilder()
- builder.setSlaveForTesting(BrokenSlave())
- d = builder.isAvailable()
+ self.builder.setSlaveForTesting(BrokenSlave())
+ d = self.builder.isAvailable()
return d.addCallback(self.assertFalse)
def test_isAvailable_with_slave_idle(self):
- builder = self.factory.makeBuilder()
- builder.setSlaveForTesting(OkSlave())
- d = builder.isAvailable()
+ self.builder.setSlaveForTesting(OkSlave())
+ d = self.builder.isAvailable()
return d.addCallback(self.assertTrue)
@@ -502,6 +530,8 @@
builder.builderok = True
builder.manual = False
+ self.useFixture(BuilddManagerTestFixture())
+
class TestFindBuildCandidateGeneralCases(TestFindBuildCandidateBase):
# Test usage of findBuildCandidate not specific to any archive type.
@@ -510,10 +540,11 @@
# IBuilder._findBuildCandidate identifies if there are builds
# for superseded source package releases in the queue and marks
# the corresponding build record as SUPERSEDED.
- archive = self.factory.makeArchive()
- self.publisher.getPubSource(
- sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
- archive=archive).createMissingBuilds()
+ with BuilddManagerTestFixture.extraSetUp():
+ archive = self.factory.makeArchive()
+ self.publisher.getPubSource(
+ sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
+ archive=archive).createMissingBuilds()
old_candidate = removeSecurityProxy(
self.frog_builder)._findBuildCandidate()
@@ -524,7 +555,8 @@
# Now supersede the source package:
publication = build.current_source_publication
- publication.status = PackagePublishingStatus.SUPERSEDED
+ with BuilddManagerTestFixture.extraSetUp():
+ publication.status = PackagePublishingStatus.SUPERSEDED
# The candidate returned is now a different one:
new_candidate = removeSecurityProxy(
@@ -542,25 +574,24 @@
# PackageBuildJob.postprocessCandidate will attempt to delete
# security builds.
- pub = self.publisher.getPubSource(
- sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
- archive=self.factory.makeArchive(),
- pocket=PackagePublishingPocket.SECURITY)
- pub.createMissingBuilds()
+ with BuilddManagerTestFixture.extraSetUp():
+ pub = self.publisher.getPubSource(
+ sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.factory.makeArchive(),
+ pocket=PackagePublishingPocket.SECURITY)
+ pub.createMissingBuilds()
+ removeSecurityProxy(self.frog_builder)._findBuildCandidate()
+ # Passes without a "transaction is read-only" error...
transaction.commit()
- with DatabaseTransactionPolicy(read_only=True):
- removeSecurityProxy(self.frog_builder)._findBuildCandidate()
- # The test is that this passes without a "transaction is
- # read-only" error.
- transaction.commit()
def test_acquireBuildCandidate_marks_building(self):
# acquireBuildCandidate() should call _findBuildCandidate and
# mark the build as building.
- archive = self.factory.makeArchive()
- self.publisher.getPubSource(
- sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
- archive=archive).createMissingBuilds()
+ with BuilddManagerTestFixture.extraSetUp():
+ archive = self.factory.makeArchive()
+ self.publisher.getPubSource(
+ sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
+ archive=archive).createMissingBuilds()
candidate = removeSecurityProxy(
self.frog_builder).acquireBuildCandidate()
self.assertEqual(JobStatus.RUNNING, candidate.job.status)
@@ -590,6 +621,8 @@
sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
archive=self.ppa_joe).createMissingBuilds()
+ self.useFixture(BuilddManagerTestFixture())
+
def test_findBuildCandidate_first_build_started(self):
# The allocation rule for PPA dispatching doesn't apply when
# there's only one builder available.
@@ -602,8 +635,9 @@
# If bob is in a failed state the joesppa build is still
# returned.
- self.bob_builder.builderok = False
- self.bob_builder.manual = False
+ with BuilddManagerTestFixture.extraSetUp():
+ self.bob_builder.builderok = False
+ self.bob_builder.manual = False
next_job = removeSecurityProxy(
self.frog_builder)._findBuildCandidate()
build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(next_job)
@@ -615,6 +649,7 @@
ppa_joe_private = False
ppa_jim_private = False
+ @BuilddManagerTestFixture.extraSetUp
def _setBuildsBuildingForArch(self, builds_list, num_builds,
archtag="i386"):
"""Helper function.
@@ -632,7 +667,10 @@
def setUp(self):
"""Publish some builds for the test archive."""
super(TestFindBuildCandidatePPABase, self).setUp()
+ self.extraSetUp()
+ @BuilddManagerTestFixture.extraSetUp
+ def extraSetUp(self):
# Create two PPAs and add some builds to each.
self.ppa_joe = self.factory.makeArchive(
name="joesppa", private=self.ppa_joe_private)
@@ -693,7 +731,8 @@
def test_findBuildCandidate_first_build_finished(self):
# When joe's first ppa build finishes, his fourth i386 build
# will be the next build candidate.
- self.joe_builds[0].status = BuildStatus.FAILEDTOBUILD
+ with BuilddManagerTestFixture.extraSetUp():
+ self.joe_builds[0].status = BuildStatus.FAILEDTOBUILD
next_job = removeSecurityProxy(self.builder4)._findBuildCandidate()
build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(next_job)
self.failUnlessEqual('joesppa', build.archive.name)
@@ -702,9 +741,10 @@
# Disabled archives should not be considered for dispatching
# builds.
disabled_job = removeSecurityProxy(self.builder4)._findBuildCandidate()
- build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(
- disabled_job)
- build.archive.disable()
+ with BuilddManagerTestFixture.extraSetUp():
+ build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(
+ disabled_job)
+ build.archive.disable()
next_job = removeSecurityProxy(self.builder4)._findBuildCandidate()
self.assertNotEqual(disabled_job, next_job)
@@ -724,7 +764,8 @@
# dispatched because the builder has to fetch the source files
# from the (password protected) repo area, not the librarian.
pub = build.current_source_publication
- pub.status = PackagePublishingStatus.PENDING
+ with BuilddManagerTestFixture.extraSetUp():
+ pub.status = PackagePublishingStatus.PENDING
candidate = removeSecurityProxy(self.builder4)._findBuildCandidate()
self.assertNotEqual(next_job.id, candidate.id)
@@ -734,6 +775,10 @@
def setUp(self):
"""Publish some builds for the test archive."""
super(TestFindBuildCandidateDistroArchive, self).setUp()
+ self.extraSetUp()
+
+ @BuilddManagerTestFixture.extraSetUp
+ def extraSetUp(self):
# Create a primary archive and publish some builds for the
# queue.
self.non_ppa = self.factory.makeArchive(
@@ -749,7 +794,6 @@
def test_findBuildCandidate_for_non_ppa(self):
# Normal archives are not restricted to serial builds per
# arch.
-
next_job = removeSecurityProxy(
self.frog_builder)._findBuildCandidate()
build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(next_job)
@@ -758,8 +802,9 @@
# Now even if we set the build building, we'll still get the
# second non-ppa build for the same archive as the next candidate.
- build.status = BuildStatus.BUILDING
- build.builder = self.frog_builder
+ with BuilddManagerTestFixture.extraSetUp():
+ build.status = BuildStatus.BUILDING
+ build.builder = self.frog_builder
next_job = removeSecurityProxy(
self.frog_builder)._findBuildCandidate()
build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(next_job)
@@ -776,7 +821,9 @@
self.assertEqual(self.gedit_build.buildqueue_record.lastscore, 2505)
self.assertEqual(self.firefox_build.buildqueue_record.lastscore, 2505)
- recipe_build_job = self.factory.makeSourcePackageRecipeBuildJob(9999)
+ with BuilddManagerTestFixture.extraSetUp():
+ recipe_build_job = (
+ self.factory.makeSourcePackageRecipeBuildJob(9999))
self.assertEqual(recipe_build_job.lastscore, 9999)
@@ -790,6 +837,7 @@
# These tests operate in a "recipe builds only" setting.
# Please see also bug #507782.
+ @BuilddManagerTestFixture.extraSetUp
def clearBuildQueue(self):
"""Delete all `BuildQueue`, XXXJOb and `Job` instances."""
store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
@@ -799,6 +847,10 @@
def setUp(self):
"""Publish some builds for the test archive."""
super(TestFindRecipeBuildCandidates, self).setUp()
+ self.extraSetUp()
+
+ @BuilddManagerTestFixture.extraSetUp
+ def extraSetUp(self):
# Create a primary archive and publish some builds for the
# queue.
self.non_ppa = self.factory.makeArchive(
@@ -844,6 +896,8 @@
self.buildfarmjob = self.build.buildqueue_record.specific_job
+ self.useFixture(BuilddManagerTestFixture())
+
def test_idle_behavior_when_no_current_build(self):
"""We return an idle behavior when there is no behavior specified
nor a current build.
@@ -864,7 +918,8 @@
"""The current behavior is set automatically from the current job."""
# Set the builder attribute on the buildqueue record so that our
# builder will think it has a current build.
- self.build.buildqueue_record.builder = self.builder
+ with BuilddManagerTestFixture.extraSetUp():
+ self.build.buildqueue_record.builder = self.builder
self.assertIsInstance(
self.builder.current_build_behavior, BinaryPackageBuildBehavior)
@@ -1157,15 +1212,17 @@
super(TestSlaveWithLibrarian, self).setUp()
self.slave_helper = self.useFixture(SlaveTestHelpers())
+ self.useFixture(BuilddManagerTestFixture())
+
def test_ensurepresent_librarian(self):
# ensurepresent, when given an http URL for a file will download the
# file from that URL and report that the file is present, and it was
# downloaded.
# Use the Librarian because it's a "convenient" web server.
- lf = self.factory.makeLibraryFileAlias(
- 'HelloWorld.txt', content="Hello World")
- self.layer.txn.commit()
+ with BuilddManagerTestFixture.extraSetUp():
+ lf = self.factory.makeLibraryFileAlias(
+ 'HelloWorld.txt', content="Hello World")
self.slave_helper.getServerSlave()
slave = self.slave_helper.getClientSlave()
d = slave.ensurepresent(
@@ -1178,9 +1235,9 @@
# filename made from the sha1 of the content underneath the
# 'filecache' directory.
content = "Hello World"
- lf = self.factory.makeLibraryFileAlias(
- 'HelloWorld.txt', content=content)
- self.layer.txn.commit()
+ with BuilddManagerTestFixture.extraSetUp():
+ lf = self.factory.makeLibraryFileAlias(
+ 'HelloWorld.txt', content=content)
expected_url = '%s/filecache/%s' % (
self.slave_helper.BASE_URL, lf.content.sha1)
self.slave_helper.getServerSlave()
@@ -1206,7 +1263,6 @@
def got_files(ignored):
# Called back when getFiles finishes. Make sure all the
# content is as expected.
- got_contents = []
for sha1 in filemap:
local_file = filemap[sha1]
file = open(local_file)
@@ -1222,11 +1278,12 @@
dl = []
for content in contents:
filename = content + '.txt'
- lf = self.factory.makeLibraryFileAlias(filename, content=content)
+ with BuilddManagerTestFixture.extraSetUp():
+ lf = self.factory.makeLibraryFileAlias(
+ filename, content=content)
content_map[lf.content.sha1] = content
fd, filemap[lf.content.sha1] = tempfile.mkstemp()
self.addCleanup(os.remove, filemap[lf.content.sha1])
- self.layer.txn.commit()
d = slave.ensurepresent(lf.content.sha1, lf.http_url, "", "")
dl.append(d)
=== modified file 'lib/lp/buildmaster/tests/test_manager.py'
--- lib/lp/buildmaster/tests/test_manager.py 2011-12-23 13:30:34 +0000
+++ lib/lp/buildmaster/tests/test_manager.py 2011-12-23 13:30:35 +0000
@@ -14,7 +14,6 @@
assert_fails_with,
AsynchronousDeferredRunTest,
)
-import transaction
from twisted.internet import (
defer,
reactor,
@@ -47,6 +46,7 @@
)
from lp.buildmaster.model.builder import Builder
from lp.buildmaster.model.packagebuild import PackageBuild
+from lp.buildmaster.testing import BuilddManagerTestFixture
from lp.buildmaster.tests.harness import BuilddManagerTestSetup
from lp.buildmaster.tests.mock_slaves import (
BrokenSlave,
@@ -56,7 +56,6 @@
WaitingSlave,
)
from lp.registry.interfaces.distribution import IDistributionSet
-from lp.services.database.transaction_policy import DatabaseTransactionPolicy
from lp.services.log.logger import BufferLogger
from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
from lp.testing import (
@@ -86,8 +85,6 @@
'bob' builder.
"""
super(TestSlaveScannerScan, self).setUp()
- self.read_only = DatabaseTransactionPolicy(read_only=True)
-
# Creating the required chroots needed for dispatching.
test_publisher = make_publisher()
ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
@@ -95,25 +92,15 @@
test_publisher.setUpDefaultDistroSeries(hoary)
test_publisher.addFakeChroots()
- def _enterReadOnly(self):
- """Go into read-only transaction policy."""
- self.read_only.__enter__()
- self.addCleanup(self._exitReadOnly)
-
- def _exitReadOnly(self):
- """Leave read-only transaction policy."""
- self.read_only.__exit__(None, None, None)
+ self.useFixture(BuilddManagerTestFixture())
def _resetBuilder(self, builder):
"""Reset the given builder and its job."""
-
builder.builderok = True
job = builder.currentjob
if job is not None:
job.reset()
- transaction.commit()
-
def getFreshBuilder(self, slave=None, name=BOB_THE_BUILDER_NAME,
failure_count=0):
"""Return a builder.
@@ -171,12 +158,11 @@
# Obtain a builder. Initialize failure count to 1 so that
# _checkDispatch can make sure that a successful dispatch resets
# the count to 0.
- builder = self.getFreshBuilder(failure_count=1)
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.getFreshBuilder(failure_count=1)
# Run 'scan' and check its result.
- self.layer.txn.commit()
self.layer.switchDbUser(config.builddmaster.dbuser)
- self._enterReadOnly()
scanner = self._getScanner()
d = defer.maybeDeferred(scanner.scan)
d.addCallback(self._checkDispatch, builder)
@@ -199,21 +185,19 @@
# When a required chroot is not present the `scan` method
# should not return any `RecordingSlaves` to be processed
# and the builder used should remain active and IDLE.
-
- builder = self.getFreshBuilder()
-
- # Remove hoary/i386 chroot.
- login('foo.bar@xxxxxxxxxxxxx')
- ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
- hoary = ubuntu.getSeries('hoary')
- pocket_chroot = hoary.getDistroArchSeries('i386').getPocketChroot()
- removeSecurityProxy(pocket_chroot).chroot = None
- transaction.commit()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.getFreshBuilder()
+ # Remove hoary/i386 chroot.
+ login('foo.bar@xxxxxxxxxxxxx')
+ ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
+ hoary = ubuntu.getSeries('hoary')
+ pocket_chroot = hoary.getDistroArchSeries('i386').getPocketChroot()
+ removeSecurityProxy(pocket_chroot).chroot = None
+
login(ANONYMOUS)
# Run 'scan' and check its result.
self.layer.switchDbUser(config.builddmaster.dbuser)
- self._enterReadOnly()
scanner = self._getScanner()
d = defer.maybeDeferred(scanner.singleCycle)
d.addCallback(self._checkNoDispatch, builder)
@@ -249,13 +233,12 @@
# Disable the sampledata builder
login('foo.bar@xxxxxxxxxxxxx')
- builder.builderok = False
- transaction.commit()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder.builderok = False
login(ANONYMOUS)
# Run 'scan' and check its result.
self.layer.switchDbUser(config.builddmaster.dbuser)
- self._enterReadOnly()
scanner = self._getScanner()
d = defer.maybeDeferred(scanner.scan)
d.addCallback(self._checkJobRescued, builder, job)
@@ -281,9 +264,9 @@
builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME]
login('foo.bar@xxxxxxxxxxxxx')
- builder.builderok = True
- builder.setSlaveForTesting(BuildingSlave(build_id='8-1'))
- transaction.commit()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder.builderok = True
+ builder.setSlaveForTesting(BuildingSlave(build_id='8-1'))
login(ANONYMOUS)
job = builder.currentjob
@@ -291,27 +274,24 @@
# Run 'scan' and check its result.
self.layer.switchDbUser(config.builddmaster.dbuser)
- self._enterReadOnly()
scanner = self._getScanner()
d = defer.maybeDeferred(scanner.scan)
d.addCallback(self._checkJobUpdated, builder, job)
return d
def test_scan_with_nothing_to_dispatch(self):
- builder = self.factory.makeBuilder()
- builder.setSlaveForTesting(OkSlave())
- transaction.commit()
- self._enterReadOnly()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.factory.makeBuilder()
+ builder.setSlaveForTesting(OkSlave())
scanner = self._getScanner(builder_name=builder.name)
d = scanner.scan()
return d.addCallback(self._checkNoDispatch, builder)
def test_scan_with_manual_builder(self):
# Reset sampledata builder.
- builder = self.getFreshBuilder()
- builder.manual = True
- transaction.commit()
- self._enterReadOnly()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.getFreshBuilder()
+ builder.manual = True
scanner = self._getScanner()
d = scanner.scan()
d.addCallback(self._checkNoDispatch, builder)
@@ -319,10 +299,9 @@
def test_scan_with_not_ok_builder(self):
# Reset sampledata builder.
- builder = self.getFreshBuilder()
- builder.builderok = False
- transaction.commit()
- self._enterReadOnly()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.getFreshBuilder()
+ builder.builderok = False
scanner = self._getScanner()
d = scanner.scan()
# Because the builder is not ok, we can't use _checkNoDispatch.
@@ -331,9 +310,8 @@
return d
def test_scan_of_broken_slave(self):
- builder = self.getFreshBuilder(slave=BrokenSlave())
- transaction.commit()
- self._enterReadOnly()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder = self.getFreshBuilder(slave=BrokenSlave())
scanner = self._getScanner(builder_name=builder.name)
d = scanner.scan()
return assert_fails_with(d, xmlrpclib.Fault)
@@ -350,16 +328,17 @@
def failing_scan():
return defer.fail(Exception("fake exception"))
- scanner = self._getScanner()
- scanner.scan = failing_scan
- self.patch(manager_module, 'assessFailureCounts', FakeMethod())
- builder = getUtility(IBuilderSet)[scanner.builder_name]
+ with BuilddManagerTestFixture.extraSetUp():
+ scanner = self._getScanner()
+ scanner.scan = failing_scan
+ self.patch(manager_module, 'assessFailureCounts', FakeMethod())
+ builder = getUtility(IBuilderSet)[scanner.builder_name]
- builder.failure_count = builder_count
- builder.currentjob.specific_job.build.failure_count = job_count
- # The _scanFailed() calls abort, so make sure our existing
- # failure counts are persisted.
- self.layer.txn.commit()
+ builder.failure_count = builder_count
+ builder.currentjob.specific_job.build.failure_count = job_count
+ # The _scanFailed() calls abort, so make sure our existing failure
+ # counts are persisted by exiting the extraSetUp() context (which
+ # commits).
# singleCycle() calls scan() which is our fake one that throws an
# exception.
@@ -405,9 +384,9 @@
scanner = self._getScanner()
scanner.scan = failing_scan
builder = getUtility(IBuilderSet)[scanner.builder_name]
- builder.failure_count = Builder.FAILURE_THRESHOLD
- builder.currentjob.reset()
- self.layer.txn.commit()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder.failure_count = Builder.FAILURE_THRESHOLD
+ builder.currentjob.reset()
d = scanner.singleCycle()
@@ -428,17 +407,19 @@
# Reset sampledata builder.
builder = removeSecurityProxy(
getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME])
- self._resetBuilder(builder)
- self.assertEqual(0, builder.failure_count)
- builder.setSlaveForTesting(slave)
- builder.vm_host = "fake_vm_host"
+ with BuilddManagerTestFixture.extraSetUp():
+ self._resetBuilder(builder)
+ self.assertEqual(0, builder.failure_count)
+ builder.setSlaveForTesting(slave)
+ builder.vm_host = "fake_vm_host"
scanner = self._getScanner()
# Get the next job that will be dispatched.
job = removeSecurityProxy(builder._findBuildCandidate())
- job.virtualized = True
- builder.virtualized = True
+ with BuilddManagerTestFixture.extraSetUp():
+ job.virtualized = True
+ builder.virtualized = True
d = scanner.singleCycle()
def check(ignored):
@@ -470,19 +451,20 @@
# Set the sample data builder building with the slave from above.
builder = getUtility(IBuilderSet)[BOB_THE_BUILDER_NAME]
login('foo.bar@xxxxxxxxxxxxx')
- builder.builderok = True
- # For now, we can only cancel virtual builds.
- builder.virtualized = True
- builder.vm_host = "fake_vm_host"
- builder.setSlaveForTesting(slave)
- transaction.commit()
+ with BuilddManagerTestFixture.extraSetUp():
+ builder.builderok = True
+ # For now, we can only cancel virtual builds.
+ builder.virtualized = True
+ builder.vm_host = "fake_vm_host"
+ builder.setSlaveForTesting(slave)
login(ANONYMOUS)
buildqueue = builder.currentjob
self.assertBuildingJob(buildqueue, builder)
# Now set the build to CANCELLING.
build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(buildqueue)
- build.status = BuildStatus.CANCELLING
+ with BuilddManagerTestFixture.extraSetUp():
+ build.status = BuildStatus.CANCELLING
# Run 'scan' and check its results.
self.layer.switchDbUser(config.builddmaster.dbuser)
@@ -513,19 +495,22 @@
# not affect the functioning builder.
clock = task.Clock()
- broken_builder = self.getFreshBuilder(
- slave=BrokenSlave(), name=BOB_THE_BUILDER_NAME)
- broken_scanner = self._getScanner(builder_name=broken_builder.name)
- good_builder = self.getFreshBuilder(
- slave=WaitingSlave(), name=FROG_THE_BUILDER_NAME)
- good_build = self.factory.makeBinaryPackageBuild(
- distroarchseries=self.factory.makeDistroArchSeries())
-
- # The good build is being handled by the good builder.
- buildqueue = good_build.queueBuild()
- buildqueue.builder = good_builder
-
- removeSecurityProxy(good_build.build_farm_job).date_started = UTC_NOW
+ with BuilddManagerTestFixture.extraSetUp():
+ broken_builder = self.getFreshBuilder(
+ slave=BrokenSlave(), name=BOB_THE_BUILDER_NAME)
+ broken_scanner = self._getScanner(
+ builder_name=broken_builder.name)
+ good_builder = self.getFreshBuilder(
+ slave=WaitingSlave(), name=FROG_THE_BUILDER_NAME)
+ good_build = self.factory.makeBinaryPackageBuild(
+ distroarchseries=self.factory.makeDistroArchSeries())
+
+ # The good build is being handled by the good builder.
+ buildqueue = good_build.queueBuild()
+ buildqueue.builder = good_builder
+
+ removeSecurityProxy(
+ good_build.build_farm_job).date_started = UTC_NOW
# The good builder requests information from a successful build,
# and up receiving it, updates the build's metadata.
@@ -569,9 +554,12 @@
self.scanner.builder = self.builder
self.scanner.logger.name = 'slave-scanner'
+ self.useFixture(BuilddManagerTestFixture())
+
def test_ignores_nonvirtual(self):
# If the builder is nonvirtual make sure we return False.
- self.builder.virtualized = False
+ with BuilddManagerTestFixture.extraSetUp():
+ self.builder.virtualized = False
d = self.scanner.checkCancellation(self.builder)
return d.addCallback(self.assertFalse)
@@ -579,7 +567,8 @@
# If the builder has no buildqueue associated,
# make sure we return False.
buildqueue = self.builder.currentjob
- buildqueue.reset()
+ with BuilddManagerTestFixture.extraSetUp():
+ buildqueue.reset()
d = self.scanner.checkCancellation(self.builder)
return d.addCallback(self.assertFalse)
@@ -587,7 +576,8 @@
# If the active build is not in a CANCELLING state, ignore it.
buildqueue = self.builder.currentjob
build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(buildqueue)
- build.status = BuildStatus.BUILDING
+ with BuilddManagerTestFixture.extraSetUp():
+ build.status = BuildStatus.BUILDING
d = self.scanner.checkCancellation(self.builder)
return d.addCallback(self.assertFalse)
@@ -601,11 +591,13 @@
return defer.succeed((None, None, 0))
slave = OkSlave()
slave.resume = fake_resume
- self.builder.vm_host = "fake_vm_host"
- self.builder.setSlaveForTesting(slave)
- buildqueue = self.builder.currentjob
- build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(buildqueue)
- build.status = BuildStatus.CANCELLING
+
+ with BuilddManagerTestFixture.extraSetUp():
+ self.builder.vm_host = "fake_vm_host"
+ self.builder.setSlaveForTesting(slave)
+ buildqueue = self.builder.currentjob
+ build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(buildqueue)
+ build.status = BuildStatus.CANCELLING
def check(result):
self.assertEqual(1, call_counter.call_count)
=== modified file 'lib/lp/services/database/tests/test_transaction_policy.py'
--- lib/lp/services/database/tests/test_transaction_policy.py 2011-12-23 13:30:34 +0000
+++ lib/lp/services/database/tests/test_transaction_policy.py 2011-12-23 13:30:35 +0000
@@ -41,6 +41,14 @@
self.factory.makePerson(name=name)
return name
+ def writeToDatabaseAndCommit(self):
+ """Write an object to the database and commit.
+
+ :return: A token that `hasDatabaseBeenWrittenTo` can look for.
+ """
+ self.writeToDatabase()
+ transaction.commit()
+
def hasDatabaseBeenWrittenTo(self, test_token):
"""Is the object made by `writeToDatabase` present in the database?
@@ -216,28 +224,36 @@
def test_policy_can_span_transactions(self):
# It's okay to commit within a policy; the policy will still
# apply to the next transaction inside the same policy.
- def write_and_commit():
- self.writeToDatabase()
- transaction.commit()
-
test_token = self.writeToDatabase()
transaction.commit()
with DatabaseTransactionPolicy(read_only=True):
self.hasDatabaseBeenWrittenTo(test_token)
transaction.commit()
- self.assertRaises(InternalError, write_and_commit)
+ self.assertRaises(InternalError, self.writeToDatabaseAndCommit)
transaction.abort()
def test_policy_survives_abort(self):
# Even after aborting the initial transaction, a transaction
# policy still applies.
- def write_and_commit():
- self.writeToDatabase()
- transaction.commit()
-
with DatabaseTransactionPolicy(read_only=True):
self.readFromDatabase()
transaction.abort()
- self.assertRaises(InternalError, write_and_commit)
+ self.assertRaises(InternalError, self.writeToDatabaseAndCommit)
transaction.abort()
+
+ def test_readOnly(self):
+ # DatabaseTransactionPolicy.readOnly() is a function decorator that
+ # applies a read-only policy for the duration of the function call.
+ writeToDatabaseAndCommit = DatabaseTransactionPolicy.readOnly(
+ self.writeToDatabaseAndCommit)
+ with DatabaseTransactionPolicy(read_only=False):
+ self.assertRaises(InternalError, writeToDatabaseAndCommit)
+
+ def test_readWrite(self):
+ # DatabaseTransactionPolicy.readWrite() is a function decorator that
+ # applies a read-write policy for the duration of the function call.
+ writeToDatabaseAndCommit = DatabaseTransactionPolicy.readWrite(
+ self.writeToDatabaseAndCommit)
+ with DatabaseTransactionPolicy(read_only=True):
+ writeToDatabaseAndCommit() # No exception.
=== modified file 'lib/lp/services/database/transaction_policy.py'
--- lib/lp/services/database/transaction_policy.py 2011-12-23 13:30:34 +0000
+++ lib/lp/services/database/transaction_policy.py 2011-12-23 13:30:35 +0000
@@ -8,6 +8,8 @@
'DatabaseTransactionPolicy',
]
+from functools import wraps
+
from psycopg2.extensions import TRANSACTION_STATUS_IDLE
import transaction
from zope.component import getUtility
@@ -195,3 +197,19 @@
"""
self.store.execute(
"SET %s TO %s" % (self.db_switch, quote(read_only)))
+
+ @classmethod
+ def readOnly(cls, func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ with cls(read_only=True):
+ return func(*args, **kwargs)
+ return wrapper
+
+ @classmethod
+ def readWrite(cls, func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ with cls(read_only=False):
+ return func(*args, **kwargs)
+ return wrapper