← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jml/launchpad/buildd-deferred-fo-sho into lp:launchpad/devel

 

Jonathan Lange has proposed merging lp:~jml/launchpad/buildd-deferred-fo-sho into lp:launchpad/devel with lp:~jml/launchpad/buildd-slavescanner-bustage as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)


Use Deferreds.
-- 
https://code.launchpad.net/~jml/launchpad/buildd-deferred-fo-sho/+merge/36188
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jml/launchpad/buildd-deferred-fo-sho into lp:launchpad/devel.
=== modified file 'lib/lp/buildmaster/manager.py'
--- lib/lp/buildmaster/manager.py	2010-09-20 10:21:32 +0000
+++ lib/lp/buildmaster/manager.py	2010-09-21 18:41:34 +0000
@@ -283,15 +283,15 @@
         """Scan the builder and dispatch to it or deal with failures."""
         self.logger.debug("Scanning builder: %s" % self.builder_name)
 
-        try:
-            slave = self.scan()
+        d = self.scan()
+
+        def got_slave(slave):
             if slave is None:
-                self.scheduleNextScanCycle()
+                return self.scheduleNextScanCycle()
             else:
-                # XXX: Ought to return Deferred.
-                self.resumeAndDispatch(slave)
-        except:
-            error = Failure()
+                return self.resumeAndDispatch(slave)
+
+        def disaster(error):
             self.logger.info("Scanning failed with: %s\n%s" %
                 (error.getErrorMessage(), error.getTraceback()))
 
@@ -307,7 +307,11 @@
             assessFailureCounts(builder, error.getErrorMessage())
             transaction.commit()
 
-            self.scheduleNextScanCycle()
+            return self.scheduleNextScanCycle()
+
+        d.addCallback(got_slave)
+        d.addErrback(disaster)
+        return d
 
     @write_transaction
     def scan(self):
@@ -346,7 +350,7 @@
         if self.builder.manual:
             self.logger.debug(
                 '%s is in manual mode, not dispatching.' % self.builder.name)
-            return None
+            return defer.succeed(None)
 
         # If the builder is marked unavailable, don't dispatch anything.
         # Additionaly, because builders can be removed from the pool at
@@ -362,7 +366,7 @@
                     "job" % self.builder.name)
                 job.reset()
                 transaction.commit()
-            return None
+            return defer.succeed(None)
 
         # See if there is a job we can dispatch to the builder slave.
 
@@ -374,15 +378,17 @@
             self.builder.name, self.builder.url, self.builder.vm_host)
         # XXX: Passing buildd_slave=slave overwrites the 'slave' property of
         # self.builder. Not sure why this is needed yet.
-        self.builder.findAndStartJob(buildd_slave=slave)
-        if self.builder.currentjob is not None:
-            # After a successful dispatch we can reset the
-            # failure_count.
-            self.builder.resetFailureCount()
-            transaction.commit()
-            return slave
-
-        return None
+        d = self.builder.findAndStartJob(buildd_slave=slave)
+        def job_started(candidate):
+            if self.builder.currentjob is not None:
+                # After a successful dispatch we can reset the
+                # failure_count.
+                self.builder.resetFailureCount()
+                transaction.commit()
+                return slave
+            else:
+                return None
+        return d.addCallback(job_started)
 
     def resumeAndDispatch(self, slave):
         """Chain the resume and dispatching Deferreds."""

=== modified file 'lib/lp/buildmaster/model/builder.py'
--- lib/lp/buildmaster/model/builder.py	2010-09-21 18:41:33 +0000
+++ lib/lp/buildmaster/model/builder.py	2010-09-21 18:41:34 +0000
@@ -34,6 +34,7 @@
     Count,
     Sum,
     )
+from twisted.internet import defer
 from zope.component import getUtility
 from zope.interface import implements
 
@@ -166,8 +167,10 @@
         return self._server.status()
 
     def ensurepresent(self, sha1sum, url, username, password):
+        # XXX: Nothing external calls this. Make it private.
         """Attempt to ensure the given file is present."""
-        return self._server.ensurepresent(sha1sum, url, username, password)
+        return defer.succeed(
+            self._server.ensurepresent(sha1sum, url, username, password))
 
     def getFile(self, sha_sum):
         """Construct a file-like object to return the named file."""
@@ -206,13 +209,15 @@
         logger.debug("Asking builder on %s to ensure it has file %s "
                      "(%s, %s)" % (self.urlbase, libraryfilealias.filename,
                                    url, libraryfilealias.content.sha1))
-        self.sendFileToSlave(libraryfilealias.content.sha1, url)
+        return self.sendFileToSlave(libraryfilealias.content.sha1, url)
 
     def sendFileToSlave(self, sha1, url, username="", password=""):
         """Helper to send the file at 'url' with 'sha1' to this builder."""
-        present, info = self.ensurepresent(sha1, url, username, password)
-        if not present:
-            raise CannotFetchFile(url, info)
+        d = self.ensurepresent(sha1, url, username, password)
+        def check_present((present, info)):
+            if not present:
+                raise CannotFetchFile(url, info)
+        return d.addCallback(check_present)
 
     def build(self, buildid, builder_type, chroot_sha1, filemap, args):
         """Build a thing on this build slave.
@@ -469,14 +474,19 @@
 
         # Do it.
         build_queue_item.markAsBuilding(self)
-        try:
-            self.current_build_behavior.dispatchBuildToSlave(
-                build_queue_item.id, logger)
-        except BuildSlaveFailure, e:
-            logger.debug("Disabling builder: %s" % self.url, exc_info=1)
+
+        d = self.current_build_behavior.dispatchBuildToSlave(
+            build_queue_item.id, logger)
+
+        def eb_slave_failure(failure):
+            failure.trap(BuildSlaveFailure)
+            e = failure.value
             self.failBuilder(
                 "Exception (%s) when setting up to new job" % (e,))
-        except CannotFetchFile, e:
+
+        def eb_cannot_fetch_file(failure):
+            failure.trap(CannotFetchFile)
+            e = failure.value
             message = """Slave '%s' (%s) was unable to fetch file.
             ****** URL ********
             %s
@@ -485,11 +495,19 @@
             *******************
             """ % (self.name, self.url, e.file_url, e.error_information)
             raise BuildDaemonError(message)
-        except socket.error, e:
+
+        def eb_socket_error(failure):
+            failure.trap(socket.error)
+            e = failure.value
             error_message = "Exception (%s) when setting up new job" % (e,)
             self.handleTimeout(logger, error_message)
             raise BuildSlaveFailure
 
+        d.addErrback(eb_slave_failure)
+        d.addErrback(eb_cannot_fetch_file)
+        d.addErrback(eb_socket_error)
+        return d
+
     def failBuilder(self, reason):
         """See IBuilder"""
         # XXX cprov 2007-04-17: ideally we should be able to notify the
@@ -682,10 +700,13 @@
         :param candidate: The job to dispatch.
         """
         logger = self._getSlaveScannerLogger()
-        try:
-            self.startBuild(candidate, logger)
-        except (BuildSlaveFailure, CannotBuild, BuildBehaviorMismatch), err:
+        d = self.startBuild(candidate, logger)
+        def warn_on_error(failure):
+            failure.trap(
+                BuildSlaveFailure, CannotBuild, BuildBehaviorMismatch)
+            err = failure.value
             logger.warn('Could not build: %s' % err)
+        return d.addErrback(warn_on_error)
 
     def handleTimeout(self, logger, error_message):
         """See IBuilder."""
@@ -726,8 +747,8 @@
         if buildd_slave is not None:
             self.setSlaveForTesting(buildd_slave)
 
-        self._dispatchBuildCandidate(candidate)
-        return candidate
+        d = self._dispatchBuildCandidate(candidate)
+        return d.addCallback(lambda ignored: candidate)
 
     def getBuildQueue(self):
         """See `IBuilder`."""

=== modified file 'lib/lp/buildmaster/tests/test_builder.py'
--- lib/lp/buildmaster/tests/test_builder.py	2010-09-21 16:23:35 +0000
+++ lib/lp/buildmaster/tests/test_builder.py	2010-09-21 18:41:34 +0000
@@ -8,8 +8,10 @@
 import socket
 import xmlrpclib
 
-from testtools.content import Content
-from testtools.content_type import UTF8_TEXT
+import fixtures
+
+from twisted.trial.unittest import TestCase as TrialTestCase
+from twisted.web.client import getPage
 
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
@@ -24,10 +26,16 @@
     )
 from canonical.testing.layers import (
     DatabaseFunctionalLayer,
-    LaunchpadZopelessLayer
+    LaunchpadZopelessLayer,
+    TwistedLaunchpadZopelessLayer,
+    TwistedLayer,
     )
 from lp.buildmaster.enums import BuildStatus
-from lp.buildmaster.interfaces.builder import IBuilder, IBuilderSet
+from lp.buildmaster.interfaces.builder import (
+    CannotFetchFile,
+    IBuilder,
+    IBuilderSet,
+    )
 from lp.buildmaster.interfaces.buildfarmjobbehavior import (
     IBuildFarmJobBehavior,
     )
@@ -49,9 +57,12 @@
     )
 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
 from lp.testing import (
-    TestCase,
+    ANONYMOUS,
+    login_as,
+    logout,
     TestCaseWithFactory,
     )
+from lp.testing.factory import LaunchpadObjectFactory
 from lp.testing.fakemethod import FakeMethod
 
 
@@ -467,19 +478,11 @@
             self.builder.current_build_behavior, BinaryPackageBuildBehavior)
 
 
-class TestSlave(TestCase):
-    """
-    Integration tests for BuilderSlave that verify how it works against a
-    real slave server.
-    """
-
-    # XXX: JonathanLange 2010-09-20 bug=643521: There are also tests for
-    # BuilderSlave in buildd-slave.txt and in other places. The tests here
-    # ought to become the canonical tests for BuilderSlave vs running buildd
-    # XML-RPC server interaction.
+class SlaveTestHelpers(fixtures.Fixture):
 
     # The URL for the XML-RPC service set up by `BuilddSlaveTestSetup`.
-    TEST_URL = 'http://localhost:8221/rpc/'
+    BASE_URL = 'http://localhost:8221'
+    TEST_URL = '%s/rpc/' % (BASE_URL,)
 
     def getServerSlave(self):
         """Set up a test build slave server.
@@ -488,11 +491,14 @@
         """
         tachandler = BuilddSlaveTestSetup()
         tachandler.setUp()
-        def addLogFile(exc_info):
-            self.addDetail(
-                'xmlrpc-log-file',
-                Content(UTF8_TEXT, lambda: open(tachandler.logfile, 'r').read()))
-        self.addOnException(addLogFile)
+        # Basically impossible to do this w/ TrialTestCase. But it would be
+        # really nice to keep it.
+        #
+        # def addLogFile(exc_info):
+        #     self.addDetail(
+        #         'xmlrpc-log-file',
+        #         Content(UTF8_TEXT, lambda: open(tachandler.logfile, 'r').read()))
+        # self.addOnException(addLogFile)
         self.addCleanup(tachandler.tearDown)
         return tachandler
 
@@ -527,7 +533,7 @@
         :return: The build id returned by the slave.
         """
         if build_id is None:
-            build_id = self.getUniqueString()
+            build_id = 'random-build-id'
         tachandler = self.getServerSlave()
         chroot_file = 'fake-chroot'
         dsc_file = 'thing'
@@ -537,10 +543,30 @@
             build_id, 'debian', chroot_file, {'.dsc': dsc_file},
             {'ogrecomponent': 'main'})
 
+
+class TestSlave(TrialTestCase):
+    """
+    Integration tests for BuilderSlave that verify how it works against a
+    real slave server.
+    """
+
+    layer = TwistedLayer
+
+    def setUp(self):
+        super(TestSlave, self).setUp()
+        self.slave_helper = SlaveTestHelpers()
+        self.slave_helper.setUp()
+        self.addCleanup(self.slave_helper.cleanUp)
+
+    # XXX: JonathanLange 2010-09-20 bug=643521: There are also tests for
+    # BuilderSlave in buildd-slave.txt and in other places. The tests here
+    # ought to become the canonical tests for BuilderSlave vs running buildd
+    # XML-RPC server interaction.
+
     def test_abort(self):
-        slave = self.getClientSlave()
+        slave = self.slave_helper.getClientSlave()
         # We need to be in a BUILDING state before we can abort.
-        self.triggerGoodBuild(slave)
+        self.slave_helper.triggerGoodBuild(slave)
         result = slave.abort()
         self.assertEqual(result, BuilderStatus.ABORTING)
 
@@ -549,8 +575,8 @@
         # valid chroot & filemaps works and returns a BuilderStatus of
         # BUILDING.
         build_id = 'some-id'
-        slave = self.getClientSlave()
-        result = self.triggerGoodBuild(slave, build_id)
+        slave = self.slave_helper.getClientSlave()
+        result = self.slave_helper.triggerGoodBuild(slave, build_id)
         self.assertEqual([BuilderStatus.BUILDING, build_id], result)
 
     def test_clean(self):
@@ -564,15 +590,15 @@
     def test_echo(self):
         # Calling 'echo' contacts the server which returns the arguments we
         # gave it.
-        self.getServerSlave()
-        slave = self.getClientSlave()
+        self.slave_helper.getServerSlave()
+        slave = self.slave_helper.getClientSlave()
         result = slave.echo('foo', 'bar', 42)
         self.assertEqual(['foo', 'bar', 42], result)
 
     def test_info(self):
         # Calling 'info' gets some information about the slave.
-        self.getServerSlave()
-        slave = self.getClientSlave()
+        self.slave_helper.getServerSlave()
+        slave = self.slave_helper.getClientSlave()
         result = slave.info()
         # We're testing the hard-coded values, since the version is hard-coded
         # into the remote slave, the supported build managers are hard-coded
@@ -588,17 +614,17 @@
     def test_initial_status(self):
         # Calling 'status' returns the current status of the slave. The
         # initial status is IDLE.
-        self.getServerSlave()
-        slave = self.getClientSlave()
+        self.slave_helper.getServerSlave()
+        slave = self.slave_helper.getClientSlave()
         status = slave.status()
         self.assertEqual([BuilderStatus.IDLE, ''], status)
 
     def test_status_after_build(self):
         # Calling 'status' returns the current status of the slave. After a
         # build has been triggered, the status is BUILDING.
-        slave = self.getClientSlave()
+        slave = self.slave_helper.getClientSlave()
         build_id = 'status-build-id'
-        self.triggerGoodBuild(slave, build_id)
+        self.slave_helper.triggerGoodBuild(slave, build_id)
         status = slave.status()
         self.assertEqual([BuilderStatus.BUILDING, build_id], status[:2])
         [log_file] = status[2:]
@@ -606,15 +632,84 @@
 
     def test_ensurepresent_not_there(self):
         # ensurepresent checks to see if a file is there.
-        self.getServerSlave()
-        slave = self.getClientSlave()
-        result = slave.ensurepresent('blahblah', None, None, None)
-        self.assertEqual([False, 'No URL'], result)
+        self.slave_helper.getServerSlave()
+        slave = self.slave_helper.getClientSlave()
+        d = slave.ensurepresent('blahblah', None, None, None)
+        d.addCallback(self.assertEqual, [False, 'No URL'])
+        return d
 
     def test_ensurepresent_actually_there(self):
         # ensurepresent checks to see if a file is there.
-        tachandler = self.getServerSlave()
-        slave = self.getClientSlave()
-        self.makeCacheFile(tachandler, 'blahblah')
-        result = slave.ensurepresent('blahblah', None, None, None)
-        self.assertEqual([True, 'No URL'], result)
+        tachandler = self.slave_helper.getServerSlave()
+        slave = self.slave_helper.getClientSlave()
+        self.slave_helper.makeCacheFile(tachandler, 'blahblah')
+        d = slave.ensurepresent('blahblah', None, None, None)
+        d.addCallback(self.assertEqual, [True, 'No URL'])
+        return d
+
+    def test_sendFileToSlave_not_there(self):
+        self.slave_helper.getServerSlave()
+        slave = self.slave_helper.getClientSlave()
+        d = slave.sendFileToSlave('blahblah', None, None, None)
+        return self.assertFailure(d, CannotFetchFile)
+
+    def test_sendFileToSlave_actually_there(self):
+        tachandler = self.slave_helper.getServerSlave()
+        slave = self.slave_helper.getClientSlave()
+        self.slave_helper.makeCacheFile(tachandler, 'blahblah')
+        d = slave.sendFileToSlave('blahblah', None, None, None)
+        def check_present(ignored):
+            d = slave.ensurepresent('blahblah', None, None, None)
+            return d.addCallback(self.assertEqual, [True, 'No URL'])
+        d.addCallback(check_present)
+        return d
+
+
+class TestSlaveWithLibrarian(TrialTestCase):
+    """Tests that need more of Launchpad to run."""
+
+    layer = TwistedLaunchpadZopelessLayer
+
+    def setUp(self):
+        super(TestSlaveWithLibrarian, self)
+        self.slave_helper = SlaveTestHelpers()
+        self.slave_helper.setUp()
+        self.addCleanup(self.slave_helper.cleanUp)
+        self.factory = LaunchpadObjectFactory()
+        login_as(ANONYMOUS)
+        self.addCleanup(logout)
+
+    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()
+        self.slave_helper.getServerSlave()
+        slave = self.slave_helper.getClientSlave()
+        d = slave.ensurepresent(
+            lf.content.sha1, lf.http_url, "", "")
+        d.addCallback(self.assertEqual, [True, 'Download'])
+        return d
+
+    def test_retrieve_files_from_filecache(self):
+        # Files that are present on the slave can be downloaded with a
+        # 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()
+        expected_url = '%s/filecache/%s' % (
+            self.slave_helper.BASE_URL, lf.content.sha1)
+        self.slave_helper.getServerSlave()
+        slave = self.slave_helper.getClientSlave()
+        d = slave.ensurepresent(
+            lf.content.sha1, lf.http_url, "", "")
+        def check_file(ignored):
+            d = getPage(expected_url.encode('utf8'))
+            return d.addCallback(self.assertEqual, content)
+        return d.addCallback(check_file)

=== modified file 'lib/lp/code/model/recipebuilder.py'
--- lib/lp/code/model/recipebuilder.py	2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/recipebuilder.py	2010-09-21 18:41:34 +0000
@@ -122,33 +122,36 @@
         if chroot is None:
             raise CannotBuild("Unable to find a chroot for %s" %
                               distroarchseries.displayname)
-        self._builder.slave.cacheFile(logger, chroot)
-
-        # Generate a string which can be used to cross-check when obtaining
-        # results so we know we are referring to the right database object in
-        # subsequent runs.
-        buildid = "%s-%s" % (self.build.id, build_queue_id)
-        cookie = self.buildfarmjob.generateSlaveBuildCookie()
-        chroot_sha1 = chroot.content.sha1
-        logger.debug(
-            "Initiating build %s on %s" % (buildid, self._builder.url))
-
-        args = self._extraBuildArgs(distroarchseries, logger)
-        status, info = self._builder.slave.build(
-            cookie, "sourcepackagerecipe", chroot_sha1, {}, args)
-        message = """%s (%s):
-        ***** RESULT *****
-        %s
-        %s: %s
-        ******************
-        """ % (
-            self._builder.name,
-            self._builder.url,
-            args,
-            status,
-            info,
-            )
-        logger.info(message)
+        d = self._builder.slave.cacheFile(logger, chroot)
+
+        def got_cache_file(ignored):
+            # Generate a string which can be used to cross-check when obtaining
+            # results so we know we are referring to the right database object in
+            # subsequent runs.
+            buildid = "%s-%s" % (self.build.id, build_queue_id)
+            cookie = self.buildfarmjob.generateSlaveBuildCookie()
+            chroot_sha1 = chroot.content.sha1
+            logger.debug(
+                "Initiating build %s on %s" % (buildid, self._builder.url))
+
+            args = self._extraBuildArgs(distroarchseries, logger)
+            # XXX: Soon to be async
+            status, info = self._builder.slave.build(
+                cookie, "sourcepackagerecipe", chroot_sha1, {}, args)
+            message = """%s (%s):
+            ***** RESULT *****
+            %s
+            %s: %s
+            ******************
+            """ % (
+                self._builder.name,
+                self._builder.url,
+                args,
+                status,
+                info,
+                )
+            logger.info(message)
+        return d.addCallback(got_cache_file)
 
     def verifyBuildRequest(self, logger):
         """Assert some pre-build checks.

=== modified file 'lib/lp/soyuz/model/binarypackagebuildbehavior.py'
--- lib/lp/soyuz/model/binarypackagebuildbehavior.py	2010-09-21 18:41:33 +0000
+++ lib/lp/soyuz/model/binarypackagebuildbehavior.py	2010-09-21 18:41:34 +0000
@@ -11,6 +11,7 @@
     'BinaryPackageBuildBehavior',
     ]
 
+from twisted.internet import defer
 from zope.interface import implements
 
 from canonical.launchpad.webapp import urlappend
@@ -38,56 +39,66 @@
         logger.info("startBuild(%s, %s, %s, %s)", self._builder.url,
                     spr.name, spr.version, self.build.pocket.title)
 
-    def dispatchBuildToSlave(self, build_queue_id, logger):
-        """See `IBuildFarmJobBehavior`."""
-
-        # Start the binary package build on the slave builder. First
-        # we send the chroot.
-        chroot = self.build.distro_arch_series.getChroot()
-        self._builder.slave.cacheFile(logger, chroot)
-
+    def _buildFilemapStructure(self, ignored, logger):
         # Build filemap structure with the files required in this build
         # and send them to the slave.
         # If the build is private we tell the slave to get the files from the
         # archive instead of the librarian because the slaves cannot
         # access the restricted librarian.
+        dl = []
         private = self.build.archive.private
         if private:
-            self._cachePrivateSourceOnSlave(logger)
+            dl.extend(self._cachePrivateSourceOnSlave(logger))
         filemap = {}
         for source_file in self.build.source_package_release.files:
             lfa = source_file.libraryfile
             filemap[lfa.filename] = lfa.content.sha1
             if not private:
-                self._builder.slave.cacheFile(logger, source_file.libraryfile)
-
-        # Generate a string which can be used to cross-check when obtaining
-        # results so we know we are referring to the right database object in
-        # subsequent runs.
-        buildid = "%s-%s" % (self.build.id, build_queue_id)
-        cookie = self.buildfarmjob.generateSlaveBuildCookie()
-        chroot_sha1 = chroot.content.sha1
-        logger.debug(
-            "Initiating build %s on %s" % (buildid, self._builder.url))
-
-        args = self._extraBuildArgs(self.build)
-        status, info = self._builder.slave.build(
-            cookie, "binarypackage", chroot_sha1, filemap, args)
-        message = """%s (%s):
-        ***** RESULT *****
-        %s
-        %s
-        %s: %s
-        ******************
-        """ % (
-            self._builder.name,
-            self._builder.url,
-            filemap,
-            args,
-            status,
-            info,
-            )
-        logger.info(message)
+                dl.append(
+                    self._builder.slave.cacheFile(
+                        logger, source_file.libraryfile))
+        d = defer.gatherResults(dl)
+        return d.addCallback(lambda ignored: filemap)
+
+    def dispatchBuildToSlave(self, build_queue_id, logger):
+        """See `IBuildFarmJobBehavior`."""
+
+        # Start the binary package build on the slave builder. First
+        # we send the chroot.
+        chroot = self.build.distro_arch_series.getChroot()
+        d = self._builder.slave.cacheFile(logger, chroot)
+        d.addCallback(self._buildFilemapStructure, logger)
+
+        def got_filemap(filemap):
+            # Generate a string which can be used to cross-check when obtaining
+            # results so we know we are referring to the right database object in
+            # subsequent runs.
+            buildid = "%s-%s" % (self.build.id, build_queue_id)
+            cookie = self.buildfarmjob.generateSlaveBuildCookie()
+            chroot_sha1 = chroot.content.sha1
+            logger.debug(
+                "Initiating build %s on %s" % (buildid, self._builder.url))
+
+            args = self._extraBuildArgs(self.build)
+            status, info = self._builder.slave.build(
+                cookie, "binarypackage", chroot_sha1, filemap, args)
+            message = """%s (%s):
+            ***** RESULT *****
+            %s
+            %s
+            %s: %s
+            ******************
+            """ % (
+                self._builder.name,
+                self._builder.url,
+                filemap,
+                args,
+                status,
+                info,
+                )
+            logger.info(message)
+
+        return d.addCallback(got_filemap)
 
     def verifyBuildRequest(self, logger):
         """Assert some pre-build checks.
@@ -154,6 +165,8 @@
         """Ask the slave to download source files for a private build.
 
         :param logger: A logger used for providing debug information.
+        :return: A list of Deferreds, each of which represents a request
+            to cache a file.
         """
         # The URL to the file in the archive consists of these parts:
         # archive_url / makePoolPath() / filename
@@ -165,6 +178,7 @@
         archive = self.build.archive
         archive_url = archive.archive_url
         component_name = self.build.current_component.name
+        dl = []
         for source_file in self.build.source_package_release.files:
             file_name = source_file.libraryfile.filename
             sha1 = source_file.libraryfile.content.sha1
@@ -175,8 +189,10 @@
             logger.debug("Asking builder on %s to ensure it has file %s "
                          "(%s, %s)" % (
                             self._builder.url, file_name, url, sha1))
-            self._builder.slave.sendFileToSlave(
-                sha1, url, "buildd", archive.buildd_secret)
+            dl.append(
+                self._builder.slave.sendFileToSlave(
+                    sha1, url, "buildd", archive.buildd_secret))
+        return dl
 
     def _extraBuildArgs(self, build):
         """

=== modified file 'lib/lp/soyuz/tests/soyuzbuilddhelpers.py'
--- lib/lp/soyuz/tests/soyuzbuilddhelpers.py	2010-09-21 18:41:33 +0000
+++ lib/lp/soyuz/tests/soyuzbuilddhelpers.py	2010-09-21 18:41:34 +0000
@@ -19,6 +19,8 @@
 from StringIO import StringIO
 import xmlrpclib
 
+from twisted.internet import defer
+
 from lp.buildmaster.interfaces.builder import CannotFetchFile
 from lp.buildmaster.model.builder import (
     rescueBuilderIfLost,
@@ -101,7 +103,7 @@
 
     def ensurepresent(self, sha1, url, user=None, password=None):
         self.call_log.append(('ensurepresent', url, user, password))
-        return True, None
+        return defer.succeed((True, None))
 
     def build(self, buildid, buildtype, chroot, filemap, args):
         self.call_log.append(
@@ -125,9 +127,11 @@
 
     def sendFileToSlave(self, sha1, url, username="", password=""):
         self.call_log.append('sendFileToSlave')
-        present, info = self.ensurepresent(sha1, url, username, password)
-        if not present:
-            raise CannotFetchFile(url, info)
+        d = self.ensurepresent(sha1, url, username, password)
+        def check_present((present, info)):
+            if not present:
+                raise CannotFetchFile(url, info)
+        return d.addCallback(check_present)
 
     def cacheFile(self, logger, libraryfilealias):
         self.call_log.append('cacheFile')

=== modified file 'lib/lp/testing/tests/test_factory.py'
--- lib/lp/testing/tests/test_factory.py	2010-09-21 18:41:33 +0000
+++ lib/lp/testing/tests/test_factory.py	2010-09-21 18:41:34 +0000
@@ -113,14 +113,6 @@
             status=BuildStatus.FULLYBUILT)
         self.assertEqual(BuildStatus.FULLYBUILT, bpb.status)
 
-<<<<<<< TREE
-    def test_makeBinaryPackageBuild_can_be_queued(self):
-        build = self.factory.makeBinaryPackageBuild()
-        # Just check that makeBinaryPackageBuild returns a build that can be
-        # queued.
-        build.queueBuild()
-
-=======
     def test_makeBinaryPackageBuild_uses_pocket(self):
         bpb = self.factory.makeBinaryPackageBuild(
             pocket=PackagePublishingPocket.UPDATES)
@@ -132,7 +124,6 @@
         # queued.
         build.queueBuild()
 
->>>>>>> MERGE-SOURCE
     # makeBinaryPackageName
     def test_makeBinaryPackageName_returns_proxied_IBinaryPackageName(self):
         binarypackagename = self.factory.makeBinaryPackageName()

=== modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py'
--- lib/lp/translations/model/translationtemplatesbuildbehavior.py	2010-08-20 20:31:18 +0000
+++ lib/lp/translations/model/translationtemplatesbuildbehavior.py	2010-09-21 18:41:34 +0000
@@ -41,16 +41,18 @@
         """See `IBuildFarmJobBehavior`."""
         chroot = self._getChroot()
         chroot_sha1 = chroot.content.sha1
-        self._builder.slave.cacheFile(logger, chroot)
-        cookie = self.buildfarmjob.generateSlaveBuildCookie()
-
-        args = {'arch_tag': self._getDistroArchSeries().architecturetag}
-        args.update(self.buildfarmjob.metadata)
-
-        filemap = {}
-
-        self._builder.slave.build(
-            cookie, self.build_type, chroot_sha1, filemap, args)
+        d = self._builder.slave.cacheFile(logger, chroot)
+        def got_cache_file(ignored):
+            cookie = self.buildfarmjob.generateSlaveBuildCookie()
+
+            args = {'arch_tag': self._getDistroArchSeries().architecturetag}
+            args.update(self.buildfarmjob.metadata)
+
+            filemap = {}
+
+            return self._builder.slave.build(
+                cookie, self.build_type, chroot_sha1, filemap, args)
+        return d.addCallback(got_cache_file)
 
     def _getChroot(self):
         return self._getDistroArchSeries().getChroot()


Follow ups