def setUp(self): super(TestHandleStatusMixin, self).setUp() self.factory = LaunchpadObjectFactory() self.build = self.makeBuild() # For the moment, we require a builder for the build so that # handleStatus_OK can get a reference to the slave. self.builder = self.factory.makeBuilder() self.build.buildqueue_record.markAsBuilding(self.builder) self.slave = WaitingSlave('BuildStatus.OK') self.slave.valid_file_hashes.append('test_file_hash') self.interactor = BuilderInteractor() self.behaviour = self.interactor.getBuildBehaviour( self.build.buildqueue_record, self.builder, self.slave) # We overwrite the buildmaster root to use a temp directory. tempdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tempdir) self.upload_root = tempdir tmp_builddmaster_root = """ [builddmaster] root: %s """ % self.upload_root config.push('tmp_builddmaster_root', tmp_builddmaster_root) # We stub out our builds getUploaderCommand() method so # we can check whether it was called as well as # verifySuccessfulUpload(). removeSecurityProxy( self.build).verifySuccessfulUpload = FakeMethod(result=True)
def test_virtual_ppa_dispatch(self): # Make sure the builder slave gets reset before a build is # dispatched to it. archive = self.factory.makeArchive(virtualized=True) slave = OkSlave() builder = self.factory.makeBuilder( virtualized=True, vm_host="foohost") vitals = extract_vitals_from_db(builder) build = self.factory.makeBinaryPackageBuild( builder=builder, archive=archive) lf = self.factory.makeLibraryFileAlias() transaction.commit() build.distro_arch_series.addOrUpdateChroot(lf) bq = build.queueBuild() bq.markAsBuilding(builder) interactor = BuilderInteractor() d = interactor._startBuild( bq, vitals, builder, slave, interactor.getBuildBehavior(bq, builder, slave), BufferLogger()) def check_build(ignored): # We expect the first call to the slave to be a resume call, # followed by the rest of the usual calls we expect. expected_resume_call = slave.call_log.pop(0) self.assertEqual('resume', expected_resume_call) self.assertExpectedInteraction( ignored, slave.call_log, builder, build, lf, archive, ArchivePurpose.PPA) return d.addCallback(check_build)
def test_non_virtual_ppa_dispatch_with_primary_ancestry(self): # If there is a primary component override, it is honoured for # non-virtual PPA builds too. archive = self.factory.makeArchive(virtualized=False) slave = OkSlave() builder = self.factory.makeBuilder(virtualized=False) builder.setCleanStatus(BuilderCleanStatus.CLEAN) vitals = extract_vitals_from_db(builder) build = self.factory.makeBinaryPackageBuild(builder=builder, archive=archive) self.factory.makeSourcePackagePublishingHistory( distroseries=build.distro_series, archive=archive.distribution.main_archive, sourcepackagename=build.source_package_release.sourcepackagename, component='main') lf = self.factory.makeLibraryFileAlias() transaction.commit() build.distro_arch_series.addOrUpdateChroot(lf) bq = build.queueBuild() bq.markAsBuilding(builder) interactor = BuilderInteractor() yield interactor._startBuild( bq, vitals, builder, slave, interactor.getBuildBehaviour(bq, builder, slave), BufferLogger()) yield self.assertExpectedInteraction(slave.call_log, builder, build, lf, archive, ArchivePurpose.PRIMARY, 'main')
def test_virtual_ppa_dispatch(self): # Make sure the builder slave gets reset before a build is # dispatched to it. archive = self.factory.makeArchive(virtualized=True) slave = OkSlave() builder = self.factory.makeBuilder(virtualized=True, vm_host="foohost") vitals = extract_vitals_from_db(builder) build = self.factory.makeBinaryPackageBuild(builder=builder, archive=archive) lf = self.factory.makeLibraryFileAlias() transaction.commit() build.distro_arch_series.addOrUpdateChroot(lf) bq = build.queueBuild() bq.markAsBuilding(builder) interactor = BuilderInteractor() d = interactor._startBuild( bq, vitals, builder, slave, interactor.getBuildBehavior(bq, builder, slave), BufferLogger()) def check_build(ignored): # We expect the first call to the slave to be a resume call, # followed by the rest of the usual calls we expect. expected_resume_call = slave.call_log.pop(0) self.assertEqual('resume', expected_resume_call) self.assertExpectedInteraction(ignored, slave.call_log, builder, build, lf, archive, ArchivePurpose.PPA) return d.addCallback(check_build)
def test_non_virtual_ppa_dispatch(self): # When the BinaryPackageBuildBehaviour dispatches PPA builds to # non-virtual builders, it stores the chroot on the server and # requests a binary package build, lying to say that the archive # purpose is "PRIMARY" because this ensures that the package mangling # tools will run over the built packages. archive = self.factory.makeArchive(virtualized=False) slave = OkSlave() builder = self.factory.makeBuilder(virtualized=False) builder.setCleanStatus(BuilderCleanStatus.CLEAN) vitals = extract_vitals_from_db(builder) build = self.factory.makeBinaryPackageBuild(builder=builder, archive=archive) lf = self.factory.makeLibraryFileAlias() transaction.commit() build.distro_arch_series.addOrUpdateChroot(lf) bq = build.queueBuild() bq.markAsBuilding(builder) interactor = BuilderInteractor() yield interactor._startBuild( bq, vitals, builder, slave, interactor.getBuildBehaviour(bq, builder, slave), BufferLogger()) yield self.assertExpectedInteraction(slave.call_log, builder, build, lf, archive, ArchivePurpose.PRIMARY, 'universe')
def test_scan_aborts_lost_slave_with_job(self): # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort # slaves that don't have the expected job. slave = BuildingSlave('nontrivial') bq = FakeBuildQueue() # Instrument updateBuild. interactor = BuilderInteractor() interactor.updateBuild = FakeMethod() scanner = SlaveScanner('mock', MockBuilderFactory(MockBuilder(), bq), BufferLogger(), interactor_factory=FakeMethod(interactor), slave_factory=FakeMethod(slave), behavior_factory=FakeMethod(TrivialBehavior())) # XXX: checkCancellation needs more than a FakeBuildQueue. scanner.checkCancellation = FakeMethod(defer.succeed(False)) # A single scan will call status(), notice that the slave is # lost, abort() the slave, then reset() the job without calling # updateBuild(). yield scanner.scan() self.assertEqual(['status', 'abort'], slave.call_log) self.assertEqual(0, interactor.updateBuild.call_count) self.assertEqual(1, bq.reset.call_count)
def test_private_source_dispatch(self): archive = self.factory.makeArchive(private=True) slave = OkSlave() builder = self.factory.makeBuilder() builder.setCleanStatus(BuilderCleanStatus.CLEAN) vitals = extract_vitals_from_db(builder) build = self.factory.makeBinaryPackageBuild(builder=builder, archive=archive) sprf = build.source_package_release.addFile( self.factory.makeLibraryFileAlias(db_only=True), filetype=SourcePackageFileType.ORIG_TARBALL) sprf_url = ( 'http://private-ppa.launchpad.dev/%s/%s/ubuntu/pool/%s/%s' % (archive.owner.name, archive.name, poolify(build.source_package_release.sourcepackagename.name, 'main'), sprf.libraryfile.filename)) lf = self.factory.makeLibraryFileAlias() transaction.commit() build.distro_arch_series.addOrUpdateChroot(lf) bq = build.queueBuild() bq.markAsBuilding(builder) interactor = BuilderInteractor() yield interactor._startBuild( bq, vitals, builder, slave, interactor.getBuildBehaviour(bq, builder, slave), BufferLogger()) yield self.assertExpectedInteraction( slave.call_log, builder, build, lf, archive, ArchivePurpose.PPA, extra_uploads=[(sprf_url, 'buildd', 'sekrit')], filemap_names=[sprf.libraryfile.filename])
def test_non_virtual_ppa_dispatch(self): # When the BinaryPackageBuildBehavior dispatches PPA builds to # non-virtual builders, it stores the chroot on the server and # requests a binary package build, lying to say that the archive # purpose is "PRIMARY" because this ensures that the package mangling # tools will run over the built packages. archive = self.factory.makeArchive(virtualized=False) slave = OkSlave() builder = self.factory.makeBuilder(virtualized=False) vitals = extract_vitals_from_db(builder) build = self.factory.makeBinaryPackageBuild( builder=builder, archive=archive) lf = self.factory.makeLibraryFileAlias() transaction.commit() build.distro_arch_series.addOrUpdateChroot(lf) bq = build.queueBuild() bq.markAsBuilding(builder) interactor = BuilderInteractor() d = interactor._startBuild( bq, vitals, builder, slave, interactor.getBuildBehavior(bq, builder, slave), BufferLogger()) d.addCallback( self.assertExpectedInteraction, slave.call_log, builder, build, lf, archive, ArchivePurpose.PRIMARY, 'universe') return d
def test_scan_aborts_lost_slave_with_job(self): # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort # slaves that don't have the expected job. slave = BuildingSlave('nontrivial') bq = FakeBuildQueue() # Instrument updateBuild. interactor = BuilderInteractor() interactor.updateBuild = FakeMethod() scanner = SlaveScanner( 'mock', MockBuilderFactory(MockBuilder(), bq), BufferLogger(), interactor_factory=FakeMethod(interactor), slave_factory=FakeMethod(slave), behavior_factory=FakeMethod(TrivialBehavior())) # XXX: checkCancellation needs more than a FakeBuildQueue. scanner.checkCancellation = FakeMethod(defer.succeed(False)) # A single scan will call status(), notice that the slave is # lost, abort() the slave, then reset() the job without calling # updateBuild(). yield scanner.scan() self.assertEqual(['status', 'abort'], slave.call_log) self.assertEqual(0, interactor.updateBuild.call_count) self.assertEqual(1, bq.reset.call_count)
def test_resetOrFail_resume_failure(self): reset_fail_config = """ [builddmaster] vm_resume_command: /bin/false""" config.push('reset fail', reset_fail_config) self.addCleanup(config.pop, 'reset fail') builder = MockBuilder(virtualized=True, vm_host="pop", builderok=True) vitals = extract_vitals_from_db(builder) d = BuilderInteractor.resetOrFail( vitals, BuilderInteractor.makeSlaveFromVitals(vitals), builder, DevNullLogger(), Exception()) return assert_fails_with(d, CannotResumeHost)
def test_makeSlaveFromVitals(self): # 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 = MockBuilder(virtualized=False) vitals = extract_vitals_from_db(builder) slave = BuilderInteractor.makeSlaveFromVitals(vitals) self.assertEqual(builder.url, slave.url) self.assertEqual(10, slave.timeout) builder = MockBuilder(virtualized=True) vitals = extract_vitals_from_db(builder) slave = BuilderInteractor.makeSlaveFromVitals(vitals) self.assertEqual(5, slave.timeout)
def test_recover_ok_slave(self): # An idle slave that's meant to be idle is not rescued. slave = OkSlave() lost = yield BuilderInteractor.rescueIfLost( extract_vitals_from_db(MockBuilder()), slave, None) self.assertFalse(lost) self.assertEqual([], slave.call_log)
def setUp(self): super(TestBinaryBuildPackageBehaviourBuildCollection, self).setUp() switch_dbuser('testadmin') self.builder = self.factory.makeBuilder() self.interactor = BuilderInteractor() self.build = self.factory.makeBinaryPackageBuild( builder=self.builder, pocket=PackagePublishingPocket.RELEASE) lf = self.factory.makeLibraryFileAlias() transaction.commit() self.build.distro_arch_series.addOrUpdateChroot(lf) self.candidate = self.build.queueBuild() self.candidate.markAsBuilding(self.builder) # This is required so that uploaded files from the buildd don't # hang around between test runs. self.addCleanup(self._cleanup)
def setUp(self): super(TestHandleStatusMixin, self).setUp() self.factory = LaunchpadObjectFactory() self.build = self.makeBuild() # For the moment, we require a builder for the build so that # handleStatus_OK can get a reference to the slave. self.builder = self.factory.makeBuilder() self.build.buildqueue_record.markAsBuilding(self.builder) self.slave = WaitingSlave('BuildStatus.OK') self.slave.valid_file_hashes.append('test_file_hash') self.interactor = BuilderInteractor() self.behavior = self.interactor.getBuildBehavior( self.build.buildqueue_record, self.builder, self.slave) # We overwrite the buildmaster root to use a temp directory. tempdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tempdir) self.upload_root = tempdir tmp_builddmaster_root = """ [builddmaster] root: %s """ % self.upload_root config.push('tmp_builddmaster_root', tmp_builddmaster_root) # We stub out our builds getUploaderCommand() method so # we can check whether it was called as well as # verifySuccessfulUpload(). removeSecurityProxy(self.build).verifySuccessfulUpload = FakeMethod( result=True)
def test_recover_building_slave_with_good_id(self): # rescueIfLost does not attempt to abort or clean a builder that is # BUILDING. building_slave = BuildingSlave(build_id='trivial') lost = yield BuilderInteractor.rescueIfLost( extract_vitals_from_db(MockBuilder()), building_slave, 'trivial') self.assertFalse(lost) self.assertEqual(['status'], building_slave.call_log)
def test_recover_building_slave_with_bad_id(self): # If a slave is BUILDING with a build id we don't recognize, then we # abort the build, thus stopping it in its tracks. building_slave = BuildingSlave(build_id='non-trivial') lost = yield BuilderInteractor.rescueIfLost( extract_vitals_from_db(MockBuilder()), building_slave, 'trivial') self.assertTrue(lost) self.assertEqual(['status', 'abort'], building_slave.call_log)
def test_recover_idle_slave(self): # An idle slave is not rescued, even if it's not meant to be # idle. SlaveScanner.scan() will clean up the DB side, because # we still report that it's lost. slave = OkSlave() lost = yield BuilderInteractor.rescueIfLost( extract_vitals_from_db(MockBuilder()), slave, 'trivial') self.assertTrue(lost) self.assertEqual([], slave.call_log)
def test_findAndStartJob_dirties_slave(self): # findAndStartJob marks its builder DIRTY before dispatching. builder, build = self._setupBinaryBuildAndBuilder() candidate = build.queueBuild() removeSecurityProxy(builder)._findBuildCandidate = FakeMethod( result=candidate) vitals = extract_vitals_from_db(builder) yield BuilderInteractor.findAndStartJob(vitals, builder, OkSlave()) self.assertEqual(BuilderCleanStatus.DIRTY, builder.clean_status)
def test_virtual_ppa_dispatch(self): archive = self.factory.makeArchive(virtualized=True) slave = OkSlave() builder = self.factory.makeBuilder(virtualized=True, vm_host="foohost") builder.setCleanStatus(BuilderCleanStatus.CLEAN) vitals = extract_vitals_from_db(builder) build = self.factory.makeBinaryPackageBuild(builder=builder, archive=archive) lf = self.factory.makeLibraryFileAlias() transaction.commit() build.distro_arch_series.addOrUpdateChroot(lf) bq = build.queueBuild() bq.markAsBuilding(builder) interactor = BuilderInteractor() yield interactor._startBuild( bq, vitals, builder, slave, interactor.getBuildBehaviour(bq, builder, slave), BufferLogger()) yield self.assertExpectedInteraction(slave.call_log, builder, build, lf, archive, ArchivePurpose.PPA)
def got_status(status): slave_status = { 'builder_status': status[0], 'build_status': status[1], 'build_id': status[2], } behavior.updateSlaveStatus(status, slave_status) return behavior.handleStatus( queue_item, BuilderInteractor.extractBuildStatus(slave_status), slave_status),
def test_partner_dispatch_no_publishing_history(self): archive = self.factory.makeArchive(virtualized=False, purpose=ArchivePurpose.PARTNER) slave = OkSlave() builder = self.factory.makeBuilder(virtualized=False) vitals = extract_vitals_from_db(builder) build = self.factory.makeBinaryPackageBuild(builder=builder, archive=archive) lf = self.factory.makeLibraryFileAlias() transaction.commit() build.distro_arch_series.addOrUpdateChroot(lf) bq = build.queueBuild() bq.markAsBuilding(builder) interactor = BuilderInteractor() d = interactor._startBuild( bq, vitals, builder, slave, interactor.getBuildBehavior(bq, builder, slave), BufferLogger()) d.addCallback(self.assertExpectedInteraction, slave.call_log, builder, build, lf, archive, ArchivePurpose.PARTNER) return d
def test_recover_waiting_slave_with_bad_id(self): # If a slave is WAITING with a build for us to get, and the build # cookie cannot be verified, which means we don't recognize the build, # then rescueBuilderIfLost should attempt to abort it, so that the # builder is reset for a new build, and the corrupt build is # discarded. waiting_slave = WaitingSlave(build_id='non-trivial') lost = yield BuilderInteractor.rescueIfLost( extract_vitals_from_db(MockBuilder()), waiting_slave, 'trivial') self.assertTrue(lost) self.assertEqual(['status', 'clean'], waiting_slave.call_log)
def test_findAndStartJob_requires_clean_slave(self): # findAndStartJob ensures that its slave starts CLEAN. builder, build = self._setupBinaryBuildAndBuilder() builder.setCleanStatus(BuilderCleanStatus.DIRTY) candidate = build.queueBuild() removeSecurityProxy(builder)._findBuildCandidate = FakeMethod( result=candidate) vitals = extract_vitals_from_db(builder) with ExpectedException(BuildDaemonIsolationError, "Attempted to start build on a dirty slave."): yield BuilderInteractor.findAndStartJob(vitals, builder, OkSlave())
def got_status(status): slave_call_log = slave.call_log slave_status = { 'builder_status': status[0], 'build_status': status[1], 'filemap': { 'translation-templates.tar.gz': 'foo' }, } return (behavior.handleStatus( queue_item, BuilderInteractor.extractBuildStatus(slave_status), slave_status), slave_call_log)
def test_scan_aborts_lost_slave_when_idle(self): # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort # slaves that aren't meant to have a job. slave = BuildingSlave() # Instrument updateBuild. interactor = BuilderInteractor() interactor.updateBuild = FakeMethod() scanner = SlaveScanner( 'mock', MockBuilderFactory(MockBuilder(), None), BufferLogger(), interactor_factory=FakeMethod(interactor), slave_factory=FakeMethod(slave), behavior_factory=FakeMethod(None)) # A single scan will call status(), notice that the slave is # lost, abort() the slave, then reset() the job without calling # updateBuild(). yield scanner.scan() self.assertEqual(['status', 'abort'], slave.call_log) self.assertEqual(0, interactor.updateBuild.call_count)
def test_partner_dispatch_no_publishing_history(self): archive = self.factory.makeArchive( virtualized=False, purpose=ArchivePurpose.PARTNER) slave = OkSlave() builder = self.factory.makeBuilder(virtualized=False) vitals = extract_vitals_from_db(builder) build = self.factory.makeBinaryPackageBuild( builder=builder, archive=archive) lf = self.factory.makeLibraryFileAlias() transaction.commit() build.distro_arch_series.addOrUpdateChroot(lf) bq = build.queueBuild() bq.markAsBuilding(builder) interactor = BuilderInteractor() d = interactor._startBuild( bq, vitals, builder, slave, interactor.getBuildBehavior(bq, builder, slave), BufferLogger()) d.addCallback( self.assertExpectedInteraction, slave.call_log, builder, build, lf, archive, ArchivePurpose.PARTNER) return d
def test_scan_with_job(self): # SlaveScanner.scan calls updateBuild() when a job is building. slave = BuildingSlave('trivial') bq = FakeBuildQueue() # Instrument updateBuild. interactor = BuilderInteractor() interactor.updateBuild = FakeMethod() scanner = SlaveScanner( 'mock', MockBuilderFactory(MockBuilder(), bq), BufferLogger(), interactor_factory=FakeMethod(interactor), slave_factory=FakeMethod(slave), behavior_factory=FakeMethod(TrivialBehavior())) # XXX: checkCancellation needs more than a FakeBuildQueue. scanner.checkCancellation = FakeMethod(defer.succeed(False)) yield scanner.scan() self.assertEqual(['status'], slave.call_log) self.assertEqual(1, interactor.updateBuild.call_count) self.assertEqual(0, bq.reset.call_count)
def test_findAndStartJob_returns_candidate(self): # 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() # _findBuildCandidate is tested elsewhere, we just make sure that # findAndStartJob delegates to it. removeSecurityProxy(builder)._findBuildCandidate = FakeMethod( result=candidate) vitals = extract_vitals_from_db(builder) d = BuilderInteractor.findAndStartJob(vitals, builder, OkSlave()) return d.addCallback(self.assertEqual, candidate)
def test_virtual_no_protocol(self): # Virtual builders fail to clean unless vm_reset_protocol is # set. builder = MockBuilder(virtualized=True, clean_status=BuilderCleanStatus.DIRTY, vm_host='lol') builder.vm_reset_protocol = None with ExpectedException(CannotResumeHost, "Invalid vm_reset_protocol: None"): yield BuilderInteractor.cleanSlave( extract_vitals_from_db(builder), OkSlave(), MockBuilderFactory(builder, None))
def test_scan_aborts_lost_slave_when_idle(self): # SlaveScanner.scan uses BuilderInteractor.rescueIfLost to abort # slaves that aren't meant to have a job. slave = BuildingSlave() # Instrument updateBuild. interactor = BuilderInteractor() interactor.updateBuild = FakeMethod() scanner = SlaveScanner('mock', MockBuilderFactory(MockBuilder(), None), BufferLogger(), interactor_factory=FakeMethod(interactor), slave_factory=FakeMethod(slave), behavior_factory=FakeMethod(None)) # A single scan will call status(), notice that the slave is # lost, abort() the slave, then reset() the job without calling # updateBuild(). yield scanner.scan() self.assertEqual(['status', 'abort'], slave.call_log) self.assertEqual(0, interactor.updateBuild.call_count)
def test_scan_with_job(self): # SlaveScanner.scan calls updateBuild() when a job is building. slave = BuildingSlave('trivial') bq = FakeBuildQueue() # Instrument updateBuild. interactor = BuilderInteractor() interactor.updateBuild = FakeMethod() scanner = SlaveScanner('mock', MockBuilderFactory(MockBuilder(), bq), BufferLogger(), interactor_factory=FakeMethod(interactor), slave_factory=FakeMethod(slave), behavior_factory=FakeMethod(TrivialBehavior())) # XXX: checkCancellation needs more than a FakeBuildQueue. scanner.checkCancellation = FakeMethod(defer.succeed(False)) yield scanner.scan() self.assertEqual(['status'], slave.call_log) self.assertEqual(1, interactor.updateBuild.call_count) self.assertEqual(0, bq.reset.call_count)
def test_getBuildBehaviour_building(self): """The current behaviour 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. builder = self.factory.makeBuilder(name='builder') slave = BuildingSlave() build = self.factory.makeBinaryPackageBuild() bq = build.queueBuild() bq.markAsBuilding(builder) behaviour = BuilderInteractor.getBuildBehaviour(bq, builder, slave) self.assertIsInstance(behaviour, BinaryPackageBuildBehaviour) self.assertEqual(behaviour._builder, builder) self.assertEqual(behaviour._slave, slave)
def got_status(status): slave_call_log = slave.call_log slave_status = { 'builder_status': status[0], 'build_status': status[1], 'filemap': {'translation-templates.tar.gz': 'foo'}, } return ( behavior.handleStatus( queue_item, BuilderInteractor.extractBuildStatus(slave_status), slave_status), slave_call_log)
def test_getBuildBehavior_building(self): """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. builder = self.factory.makeBuilder(name='builder') slave = BuildingSlave() build = self.factory.makeBinaryPackageBuild() bq = build.queueBuild() bq.markAsBuilding(builder) behavior = BuilderInteractor.getBuildBehavior(bq, builder, slave) self.assertIsInstance(behavior, BinaryPackageBuildBehavior) self.assertEqual(behavior._builder, builder) self.assertEqual(behavior._slave, slave)
def test_nonvirtual_broken(self): # A broken non-virtual builder is probably unrecoverable, so the # method just crashes. builder = MockBuilder(virtualized=False, clean_status=BuilderCleanStatus.DIRTY) vitals = extract_vitals_from_db(builder) slave = LostBuildingBrokenSlave() try: yield BuilderInteractor.cleanSlave( vitals, slave, MockBuilderFactory(builder, None)) except xmlrpclib.Fault: self.assertEqual(['status', 'abort'], slave.call_log) else: self.fail("abort() should crash.")
def test_rescueIfLost_aborts_lost_and_broken_slave(self): # A slave that's 'lost' should be aborted; when the slave is # broken then abort() should also throw a fault. slave = LostBuildingBrokenSlave() d = BuilderInteractor.rescueIfLost( extract_vitals_from_db(MockBuilder()), slave, 'trivial') def check_slave_status(failure): self.assertIn('abort', slave.call_log) # 'Fault' comes from the LostBuildingBrokenSlave, this is # just testing that the value is passed through. self.assertIsInstance(failure.value, xmlrpclib.Fault) return d.addBoth(check_slave_status)
def test_virtual_job_dispatch_pings_before_building(self): # 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() removeSecurityProxy(builder)._findBuildCandidate = FakeMethod( result=candidate) vitals = extract_vitals_from_db(builder) slave = OkSlave() d = BuilderInteractor.findAndStartJob(vitals, builder, slave) def check_build_started(candidate): self.assertIn(('echo', 'ping'), slave.call_log) return d.addCallback(check_build_started)
def got_status(status): raw_status = ( 'BuilderStatus.WAITING', 'BuildStatus.OK', status[2], ) slave_status = { 'builder_status': raw_status[0], 'build_status': raw_status[1], } behavior.updateSlaveStatus(raw_status, slave_status) self.assertFalse('filemap' in slave_status) return behavior.handleStatus( queue_item, BuilderInteractor.extractBuildStatus(slave_status), slave_status),
def test_findAndStartJob_starts_job(self): # findAndStartJob finds the next queued job using _findBuildCandidate # and then starts it. # We don't care about the type of build at all. builder, build = self._setupRecipeBuildAndBuilder() candidate = build.queueBuild() removeSecurityProxy(builder)._findBuildCandidate = FakeMethod( result=candidate) vitals = extract_vitals_from_db(builder) d = BuilderInteractor.findAndStartJob(vitals, builder, OkSlave()) def check_build_started(candidate): self.assertEqual(candidate.builder, builder) self.assertEqual(BuildStatus.BUILDING, build.status) return d.addCallback(check_build_started)
def setUp(self): super(TestBinaryBuildPackageBehaviorBuildCollection, self).setUp() switch_dbuser('testadmin') self.builder = self.factory.makeBuilder() self.interactor = BuilderInteractor() self.build = self.factory.makeBinaryPackageBuild( builder=self.builder, pocket=PackagePublishingPocket.RELEASE) lf = self.factory.makeLibraryFileAlias() transaction.commit() self.build.distro_arch_series.addOrUpdateChroot(lf) self.candidate = self.build.queueBuild() self.candidate.markAsBuilding(self.builder) # This is required so that uploaded files from the buildd don't # hang around between test runs. self.addCleanup(self._cleanup)
def assertStatus(self, slave, builder_status=None, build_status=None, logtail=False, filemap=None, dependencies=None): statuses = yield BuilderInteractor.slaveStatus(slave) status_dict = statuses[1] expected = {} if builder_status is not None: expected["builder_status"] = builder_status if build_status is not None: expected["build_status"] = build_status if dependencies is not None: expected["dependencies"] = dependencies # We don't care so much about the content of the logtail, # just that it's there. if logtail: tail = status_dict.pop("logtail") self.assertIsInstance(tail, xmlrpclib.Binary) self.assertEqual(expected, status_dict)
def prepareBehavior(self, fake_successful_upload=False): self.queue_record = self.factory.makeSourcePackageRecipeBuildJob() build = self.queue_record.specific_job.build build.updateStatus(BuildStatus.FULLYBUILT) if fake_successful_upload: removeSecurityProxy(build).verifySuccessfulUpload = FakeMethod(result=True) # We overwrite the buildmaster root to use a temp directory. tempdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tempdir) self.upload_root = tempdir tmp_builddmaster_root = ( """ [builddmaster] root: %s """ % self.upload_root ) config.push("tmp_builddmaster_root", tmp_builddmaster_root) self.addCleanup(config.pop, "tmp_builddmaster_root") self.queue_record.builder = self.factory.makeBuilder() slave = WaitingSlave("BuildStatus.OK") return BuilderInteractor.getBuildBehavior(self.queue_record, self.queue_record.builder, slave)
def resumeSlaveHost(self, builder): vitals = extract_vitals_from_db(builder) return BuilderInteractor.resumeSlaveHost( vitals, BuilderInteractor.makeSlaveFromVitals(vitals))
class TestBinaryBuildPackageBehaviorBuildCollection(TestCaseWithFactory): """Tests for the BinaryPackageBuildBehavior. Using various mock slaves, we check how updateBuild() behaves in various scenarios. """ # XXX: These tests replace part of the old buildd-slavescanner.txt # It was checking that each call to updateBuild was sending 3 (!) # emails but this behaviour is so ill-defined and dependent on the # sample data that I've not replicated that here. We need to # examine that behaviour separately somehow, but the old tests gave # NO clue as to what, exactly, they were testing. layer = LaunchpadZopelessLayer run_tests_with = AsynchronousDeferredRunTest def _cleanup(self): if os.path.exists(config.builddmaster.root): shutil.rmtree(config.builddmaster.root) def setUp(self): super(TestBinaryBuildPackageBehaviorBuildCollection, self).setUp() switch_dbuser('testadmin') self.builder = self.factory.makeBuilder() self.interactor = BuilderInteractor() self.build = self.factory.makeBinaryPackageBuild( builder=self.builder, pocket=PackagePublishingPocket.RELEASE) lf = self.factory.makeLibraryFileAlias() transaction.commit() self.build.distro_arch_series.addOrUpdateChroot(lf) self.candidate = self.build.queueBuild() self.candidate.markAsBuilding(self.builder) # This is required so that uploaded files from the buildd don't # hang around between test runs. self.addCleanup(self._cleanup) def updateBuild(self, candidate, slave): bf = MockBuilderFactory(self.builder, candidate) return self.interactor.updateBuild( bf.getVitals('foo'), slave, bf, self.interactor.getBuildBehavior) def assertBuildProperties(self, build): """Check that a build happened by making sure some of its properties are set.""" self.assertIsNot(None, build.builder) self.assertIsNot(None, build.date_finished) self.assertIsNot(None, build.duration) self.assertIsNot(None, build.log) def test_packagefail_collection(self): # When a package fails to build, make sure the builder notes are # stored and the build status is set as failed. def got_update(ignored): self.assertBuildProperties(self.build) self.assertEqual(BuildStatus.FAILEDTOBUILD, self.build.status) d = self.updateBuild( self.candidate, WaitingSlave('BuildStatus.PACKAGEFAIL')) return d.addCallback(got_update) def test_depwait_collection(self): # Package build was left in dependency wait. DEPENDENCIES = 'baz (>= 1.0.1)' def got_update(ignored): self.assertBuildProperties(self.build) self.assertEqual(BuildStatus.MANUALDEPWAIT, self.build.status) self.assertEqual(DEPENDENCIES, self.build.dependencies) d = self.updateBuild( self.candidate, WaitingSlave('BuildStatus.DEPFAIL', DEPENDENCIES)) return d.addCallback(got_update) def test_chrootfail_collection(self): # There was a chroot problem for this build. def got_update(ignored): self.assertBuildProperties(self.build) self.assertEqual(BuildStatus.CHROOTWAIT, self.build.status) d = self.updateBuild( self.candidate, WaitingSlave('BuildStatus.CHROOTFAIL')) return d.addCallback(got_update) def test_builderfail_collection(self): # The builder failed after we dispatched the build. def got_update(ignored): self.assertEqual( "Builder returned BUILDERFAIL when asked for its status", self.builder.failnotes) self.assertIs(None, self.candidate.builder) self.assertEqual(BuildStatus.NEEDSBUILD, self.build.status) job = self.candidate.specific_job.job self.assertEqual(JobStatus.WAITING, job.status) d = self.updateBuild( self.candidate, WaitingSlave('BuildStatus.BUILDERFAIL')) return d.addCallback(got_update) def test_building_collection(self): # The builder is still building the package. def got_update(ignored): # The fake log is returned from the BuildingSlave() mock. self.assertEqual("This is a build log", self.candidate.logtail) d = self.updateBuild(self.candidate, BuildingSlave()) return d.addCallback(got_update) def test_aborting_collection(self): # The builder is in the process of aborting. def got_update(ignored): self.assertEqual( "Waiting for slave process to be terminated", self.candidate.logtail) d = self.updateBuild(self.candidate, AbortingSlave()) return d.addCallback(got_update) def test_collection_for_deleted_source(self): # If we collected a build for a superseded/deleted source then # the build should get marked superseded as the build results # get discarded. spr = removeSecurityProxy(self.build.source_package_release) pub = self.build.current_source_publication pub.requestDeletion(spr.creator) def got_update(ignored): self.assertEqual( BuildStatus.SUPERSEDED, self.build.status) d = self.updateBuild(self.candidate, WaitingSlave('BuildStatus.OK')) return d.addCallback(got_update) def test_uploading_collection(self): # After a successful build, the status should be UPLOADING. def got_update(ignored): self.assertEqual(self.build.status, BuildStatus.UPLOADING) # We do not store any upload log information when the binary # upload processing succeeded. self.assertIs(None, self.build.upload_log) d = self.updateBuild(self.candidate, WaitingSlave('BuildStatus.OK')) return d.addCallback(got_update) def test_givenback_collection(self): score = self.candidate.lastscore def got_update(ignored): self.assertIs(None, self.candidate.builder) self.assertIs(None, self.candidate.date_started) self.assertEqual(score, self.candidate.lastscore) self.assertEqual(BuildStatus.NEEDSBUILD, self.build.status) job = self.candidate.specific_job.job self.assertEqual(JobStatus.WAITING, job.status) d = self.updateBuild( self.candidate, WaitingSlave('BuildStatus.GIVENBACK')) return d.addCallback(got_update) def test_log_file_collection(self): self.build.updateStatus(BuildStatus.FULLYBUILT) old_tmps = sorted(os.listdir('/tmp')) slave = WaitingSlave('BuildStatus.OK') def got_log(logfile_lfa_id): # Grabbing logs should not leave new files in /tmp (bug #172798) logfile_lfa = getUtility(ILibraryFileAliasSet)[logfile_lfa_id] new_tmps = sorted(os.listdir('/tmp')) self.assertEqual(old_tmps, new_tmps) # The new librarian file is stored compressed with a .gz # extension and text/plain file type for easy viewing in # browsers, as it decompresses and displays the file inline. self.assertTrue( logfile_lfa.filename.endswith('_FULLYBUILT.txt.gz')) self.assertEqual('text/plain', logfile_lfa.mimetype) self.layer.txn.commit() # LibrarianFileAlias does not implement tell() or seek(), which # are required by gzip.open(), so we need to read the file out # of the librarian first. fd, fname = tempfile.mkstemp() self.addCleanup(os.remove, fname) tmp = os.fdopen(fd, 'wb') tmp.write(logfile_lfa.read()) tmp.close() uncompressed_file = gzip.open(fname).read() # Now make a temp filename that getFile() can write to. fd, tmp_orig_file_name = tempfile.mkstemp() self.addCleanup(os.remove, tmp_orig_file_name) # Check that the original file from the slave matches the # uncompressed file in the librarian. def got_orig_log(ignored): orig_file_content = open(tmp_orig_file_name).read() self.assertEqual(orig_file_content, uncompressed_file) d = removeSecurityProxy(slave).getFile( 'buildlog', tmp_orig_file_name) return d.addCallback(got_orig_log) behavior = IBuildFarmJobBehavior(self.candidate.specific_job) behavior.setBuilder(self.builder, slave) d = behavior.getLogFromSlave(self.build.buildqueue_record) return d.addCallback(got_log) def test_private_build_log_storage(self): # Builds in private archives should have their log uploaded to # the restricted librarian. # Go behind Storm's back since the field validator on # Archive.private prevents us from setting it to True with # existing published sources. Store.of(self.build).execute(""" UPDATE archive SET private=True,buildd_secret='foo' WHERE archive.id = %s""" % self.build.archive.id) Store.of(self.build).invalidate() def got_update(ignored): # Librarian needs a commit. :( self.layer.txn.commit() self.assertTrue(self.build.log.restricted) d = self.updateBuild(self.candidate, WaitingSlave('BuildStatus.OK')) return d.addCallback(got_update)
def test_extractBuildStatus_baseline(self): # extractBuildStatus picks the name of the build status out of a # dict describing the slave's status. slave_status = {'build_status': 'BuildStatus.BUILDING'} self.assertEqual( 'BUILDING', BuilderInteractor.extractBuildStatus(slave_status))
def test_getBuildBehavior_idle(self): """An idle builder has no build behavior.""" self.assertIs( None, BuilderInteractor.getBuildBehavior(None, MockBuilder(), None))
class TestHandleStatusMixin: """Tests for `IPackageBuild`s handleStatus method. This should be run with a Trial TestCase. """ layer = LaunchpadZopelessLayer def makeBuild(self): """Allow classes to override the build with which the test runs.""" raise NotImplementedError def setUp(self): super(TestHandleStatusMixin, self).setUp() self.factory = LaunchpadObjectFactory() self.build = self.makeBuild() # For the moment, we require a builder for the build so that # handleStatus_OK can get a reference to the slave. self.builder = self.factory.makeBuilder() self.build.buildqueue_record.markAsBuilding(self.builder) self.slave = WaitingSlave('BuildStatus.OK') self.slave.valid_file_hashes.append('test_file_hash') self.interactor = BuilderInteractor() self.behavior = self.interactor.getBuildBehavior( self.build.buildqueue_record, self.builder, self.slave) # We overwrite the buildmaster root to use a temp directory. tempdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tempdir) self.upload_root = tempdir tmp_builddmaster_root = """ [builddmaster] root: %s """ % self.upload_root config.push('tmp_builddmaster_root', tmp_builddmaster_root) # We stub out our builds getUploaderCommand() method so # we can check whether it was called as well as # verifySuccessfulUpload(). removeSecurityProxy(self.build).verifySuccessfulUpload = FakeMethod( result=True) def assertResultCount(self, count, result): self.assertEquals( 1, len(os.listdir(os.path.join(self.upload_root, result)))) def test_handleStatus_OK_normal_file(self): # A filemap with plain filenames should not cause a problem. # The call to handleStatus will attempt to get the file from # the slave resulting in a URL error in this test case. def got_status(ignored): self.assertEqual(BuildStatus.UPLOADING, self.build.status) self.assertResultCount(1, "incoming") d = self.behavior.handleStatus( self.build.buildqueue_record, 'OK', {'filemap': {'myfile.py': 'test_file_hash'}}) return d.addCallback(got_status) def test_handleStatus_OK_absolute_filepath(self): # A filemap that tries to write to files outside of # the upload directory will result in a failed upload. def got_status(ignored): self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status) self.assertResultCount(0, "failed") self.assertIdentical(None, self.build.buildqueue_record) d = self.behavior.handleStatus( self.build.buildqueue_record, 'OK', {'filemap': {'/tmp/myfile.py': 'test_file_hash'}}) return d.addCallback(got_status) def test_handleStatus_OK_relative_filepath(self): # A filemap that tries to write to files outside of # the upload directory will result in a failed upload. def got_status(ignored): self.assertEqual(BuildStatus.FAILEDTOUPLOAD, self.build.status) self.assertResultCount(0, "failed") d = self.behavior.handleStatus( self.build.buildqueue_record, 'OK', {'filemap': {'../myfile.py': 'test_file_hash'}}) return d.addCallback(got_status) def test_handleStatus_OK_sets_build_log(self): # The build log is set during handleStatus. self.assertEqual(None, self.build.log) d = self.behavior.handleStatus( self.build.buildqueue_record, 'OK', {'filemap': {'myfile.py': 'test_file_hash'}}) def got_status(ignored): self.assertNotEqual(None, self.build.log) return d.addCallback(got_status) def _test_handleStatus_notifies(self, status): # An email notification is sent for a given build status if # notifications are allowed for that status. expected_notification = ( status in self.behavior.ALLOWED_STATUS_NOTIFICATIONS) def got_status(ignored): if expected_notification: self.failIf( len(pop_notifications()) == 0, "No notifications received") else: self.failIf( len(pop_notifications()) > 0, "Notifications received") d = self.behavior.handleStatus( self.build.buildqueue_record, status, {}) return d.addCallback(got_status) def test_handleStatus_DEPFAIL_notifies(self): return self._test_handleStatus_notifies("DEPFAIL") def test_handleStatus_CHROOTFAIL_notifies(self): return self._test_handleStatus_notifies("CHROOTFAIL") def test_handleStatus_PACKAGEFAIL_notifies(self): return self._test_handleStatus_notifies("PACKAGEFAIL") def test_handleStatus_ABORTED_cancels_cancelling(self): self.build.updateStatus(BuildStatus.CANCELLING) def got_status(ignored): self.assertEqual( 0, len(pop_notifications()), "Notifications received") self.assertEqual(BuildStatus.CANCELLED, self.build.status) d = self.behavior.handleStatus( self.build.buildqueue_record, "ABORTED", {}) return d.addCallback(got_status) def test_handleStatus_ABORTED_recovers_building(self): self.builder.vm_host = "fake_vm_host" self.behavior = self.interactor.getBuildBehavior( self.build.buildqueue_record, self.builder, self.slave) self.build.updateStatus(BuildStatus.BUILDING) def got_status(ignored): self.assertEqual( 0, len(pop_notifications()), "Notifications received") self.assertEqual(BuildStatus.NEEDSBUILD, self.build.status) self.assertEqual(1, self.builder.failure_count) self.assertEqual(1, self.build.failure_count) self.assertIn("clean", self.slave.call_log) d = self.behavior.handleStatus( self.build.buildqueue_record, "ABORTED", {}) return d.addCallback(got_status) @defer.inlineCallbacks def test_handleStatus_ABORTED_cancelling_sets_build_log(self): # If a build is intentionally cancelled, the build log is set. self.assertEqual(None, self.build.log) self.build.updateStatus(BuildStatus.CANCELLING) yield self.behavior.handleStatus( self.build.buildqueue_record, "ABORTED", {}) self.assertNotEqual(None, self.build.log) def test_date_finished_set(self): # The date finished is updated during handleStatus_OK. self.assertEqual(None, self.build.date_finished) d = self.behavior.handleStatus( self.build.buildqueue_record, 'OK', {'filemap': {'myfile.py': 'test_file_hash'}}) def got_status(ignored): self.assertNotEqual(None, self.build.date_finished) return d.addCallback(got_status)