← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/build-fast-cleanup into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/build-fast-cleanup into lp:launchpad.

Commit message:
Send fast_cleanup: True to virtualised builds, since they can safely skip the final cleanup steps.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/build-fast-cleanup/+merge/344958

In the process, I refactored the various common bits of building the extra build arguments dict into BuildFarmJobBehaviourBase, so that I only had to calculate fast_cleanup in one place.

https://code.launchpad.net/~cjwatson/launchpad-buildd/faster-cleanup/+merge/344938 must be in place for this to be useful, but they can harmlessly be deployed in either order.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/build-fast-cleanup into lp:launchpad.
=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py'
--- lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py	2016-03-31 14:58:46 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py	2018-05-02 13:37:24 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2014 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Interface for build farm job behaviours."""
@@ -9,14 +9,41 @@
     'IBuildFarmJobBehaviour',
     ]
 
-from zope.interface import Interface
+from zope.interface import (
+    Attribute,
+    Interface,
+    )
 
 
 class IBuildFarmJobBehaviour(Interface):
 
+    builder_type = Attribute(
+        "The name of the builder type to use for this build, corresponding "
+        "to a launchpad-buildd build manager tag.")
+
+    distro_arch_series = Attribute("The `DistroArchSeries` to build against.")
+
     def setBuilder(builder, slave):
         """Sets the associated builder and slave for this instance."""
 
+    def determineFilesToSend():
+        """Work out which files to send to the builder.
+
+        :return: A dict mapping filenames to dicts as follows::
+            'sha1': SHA-1 of file content
+            'url': URL from which the builder can fetch content
+            'username' (optional): username to authenticate as
+            'password' (optional): password to authenticate with
+        """
+
+    def extraBuildArgs(logger=None):
+        """Return extra arguments required by the builder for this build.
+
+        :param logger: An optional logger.
+        :return: A dict of builder arguments, or a Deferred resulting in the
+            same.
+        """
+
     def composeBuildRequest(logger):
         """Compose parameters for a slave build request.
 

=== modified file 'lib/lp/buildmaster/model/buildfarmjobbehaviour.py'
--- lib/lp/buildmaster/model/buildfarmjobbehaviour.py	2016-10-12 14:02:19 +0000
+++ lib/lp/buildmaster/model/buildfarmjobbehaviour.py	2018-05-02 13:37:24 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Base and idle BuildFarmJobBehaviour classes."""
@@ -32,6 +32,7 @@
 from lp.services.librarian.interfaces import ILibraryFileAliasSet
 from lp.services.librarian.utils import copy_and_close
 from lp.services.utils import sanitise_urls
+from lp.services.webapp import canonical_url
 
 
 SLAVE_LOG_FILENAME = 'buildlog'
@@ -48,11 +49,39 @@
         self.build = build
         self._builder = None
 
+    @property
+    def distro_arch_series(self):
+        if self.build is not None:
+            return self.build.distro_arch_series
+        else:
+            return None
+
     def setBuilder(self, builder, slave):
         """The builder should be set once and not changed."""
         self._builder = builder
         self._slave = slave
 
+    def determineFilesToSend(self):
+        """The default behaviour is to send no files."""
+        return {}
+
+    def extraBuildArgs(self, logger=None):
+        """The default behaviour is to send only common extra arguments."""
+        args = {}
+        args["arch_tag"] = self.distro_arch_series.architecturetag
+        args["archive_private"] = self.build.archive.private
+        args["build_url"] = canonical_url(self.build)
+        args["fast_cleanup"] = self._builder.virtualized
+        args["series"] = self.distro_arch_series.distroseries.name
+        return args
+
+    @defer.inlineCallbacks
+    def composeBuildRequest(self, logger):
+        args = yield self.extraBuildArgs(logger=logger)
+        defer.returnValue(
+            (self.builder_type, self.distro_arch_series,
+             self.determineFilesToSend(), args))
+
     def verifyBuildRequest(self, logger):
         """The default behaviour is a no-op."""
         pass

=== modified file 'lib/lp/buildmaster/tests/test_buildfarmjobbehaviour.py'
--- lib/lp/buildmaster/tests/test_buildfarmjobbehaviour.py	2018-02-14 11:13:47 +0000
+++ lib/lp/buildmaster/tests/test_buildfarmjobbehaviour.py	2018-05-02 13:37:24 +0000
@@ -116,6 +116,20 @@
             '%s-%s' % (now.strftime("%Y%m%d-%H%M%S"), build_cookie),
             upload_leaf)
 
+    def test_extraBuildArgs_virtualized(self):
+        # If the builder is virtualized, extraBuildArgs sends
+        # fast_cleanup: True.
+        behaviour = self._makeBehaviour(self._makeBuild())
+        behaviour.setBuilder(self.factory.makeBuilder(virtualized=True), None)
+        self.assertIs(True, behaviour.extraBuildArgs()["fast_cleanup"])
+
+    def test_extraBuildArgs_non_virtualized(self):
+        # If the builder is non-virtualized, extraBuildArgs sends
+        # fast_cleanup: False.
+        behaviour = self._makeBehaviour(self._makeBuild())
+        behaviour.setBuilder(self.factory.makeBuilder(virtualized=False), None)
+        self.assertIs(False, behaviour.extraBuildArgs()["fast_cleanup"])
+
 
 class TestDispatchBuildToSlave(TestCase):
 

=== modified file 'lib/lp/code/model/recipebuilder.py'
--- lib/lp/code/model/recipebuilder.py	2018-03-01 17:36:31 +0000
+++ lib/lp/code/model/recipebuilder.py	2018-05-02 13:37:24 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2017 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Code to build recipes on the buildfarm."""
@@ -24,7 +24,7 @@
     ISourcePackageRecipeBuild,
     )
 from lp.services.config import config
-from lp.services.webapp import canonical_url
+from lp.services.propertycache import cachedproperty
 from lp.soyuz.adapters.archivedependencies import (
     get_primary_current_component,
     get_sources_list_for_building,
@@ -36,22 +36,37 @@
 class RecipeBuildBehaviour(BuildFarmJobBehaviourBase):
     """How to build a recipe on the build farm."""
 
+    builder_type = "sourcepackagerecipe"
+
     # The list of build status values for which email notifications are
     # allowed to be sent. It is up to each callback as to whether it will
     # consider sending a notification but it won't do so if the status is not
     # in this list.
     ALLOWED_STATUS_NOTIFICATIONS = ['PACKAGEFAIL', 'DEPFAIL', 'CHROOTFAIL']
 
+    @cachedproperty
+    def distro_arch_series(self):
+        if self.build is not None and self._builder is not None:
+            return self.build.distroseries.getDistroArchSeriesByProcessor(
+                self._builder.processor)
+        else:
+            return None
+
     @defer.inlineCallbacks
-    def _extraBuildArgs(self, distroarchseries, logger=None):
+    def extraBuildArgs(self, logger=None):
         """
         Return the extra arguments required by the slave for the given build.
         """
+        if self.distro_arch_series is None:
+            raise CannotBuild(
+                "Unable to find distroarchseries for %s in %s" %
+                (self._builder.processor.name,
+                 self.build.distroseries.displayname))
+
         # Build extra arguments.
-        args = {}
-        args['series'] = self.build.distroseries.name
+        args = yield super(RecipeBuildBehaviour, self).extraBuildArgs(
+            logger=logger)
         args['suite'] = self.build.distroseries.getSuite(self.build.pocket)
-        args['arch_tag'] = distroarchseries.architecturetag
         requester = self.build.requester
         if requester.preferredemail is None:
             # Use a constant, known, name and email.
@@ -71,11 +86,9 @@
             None).name
         args['archives'], args['trusted_keys'] = (
             yield get_sources_list_for_building(
-                self.build, distroarchseries, None,
+                self.build, self.distro_arch_series, None,
                 tools_source=config.builddmaster.bzr_builder_sources_list,
                 logger=logger))
-        args['archive_private'] = self.build.archive.private
-        args['build_url'] = canonical_url(self.build)
         # XXX cjwatson 2017-07-26: This duplicates "series", which is common
         # to all build types; this name for it is deprecated and should be
         # removed once launchpad-buildd no longer requires it.
@@ -84,18 +97,6 @@
             args['git'] = True
         defer.returnValue(args)
 
-    @defer.inlineCallbacks
-    def composeBuildRequest(self, logger):
-        das = self.build.distroseries.getDistroArchSeriesByProcessor(
-            self._builder.processor)
-        if das is None:
-            raise CannotBuild(
-                "Unable to find distroarchseries for %s in %s" %
-                (self._builder.processor.name,
-                 self.build.distroseries.displayname))
-        args = yield self._extraBuildArgs(das, logger=logger)
-        defer.returnValue(("sourcepackagerecipe", das, {}, args))
-
     def verifyBuildRequest(self, logger):
         """Assert some pre-build checks.
 

=== modified file 'lib/lp/code/model/tests/test_recipebuilder.py'
--- lib/lp/code/model/tests/test_recipebuilder.py	2018-03-01 17:36:31 +0000
+++ lib/lp/code/model/tests/test_recipebuilder.py	2018-05-02 13:37:24 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2017 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test RecipeBuildBehaviour."""
@@ -70,7 +70,7 @@
     layer = LaunchpadZopelessLayer
 
     def makeJob(self, recipe_registrant=None, recipe_owner=None,
-                archive=None, git=False):
+                archive=None, git=False, with_builder=False):
         """Create a sample `ISourcePackageRecipeBuild`."""
         spn = self.factory.makeSourcePackageName("apackage")
         if archive is None:
@@ -105,6 +105,10 @@
             sourcepackage=sourcepackage, archive=archive,
             recipe=recipe, requester=recipe_owner, distroseries=distroseries)
         job = IBuildFarmJobBehaviour(spb)
+        if with_builder:
+            builder = MockBuilder()
+            builder.processor = processor
+            job.setBuilder(builder, None)
         return job
 
 
@@ -171,16 +175,16 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs(self):
-        # _extraBuildArgs will return a sane set of additional arguments
+        # extraBuildArgs will return a sane set of additional arguments.
         self._setBuilderConfig()
-        job = self.makeJob()
+        job = self.makeJob(with_builder=True)
         distroarchseries = job.build.distroseries.architectures[0]
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, distroarchseries, None))
         expected_archives.insert(
             0, "deb http://foo %s main" % job.build.distroseries.name)
-        args = yield job._extraBuildArgs(distroarchseries)
+        args = yield job.extraBuildArgs()
         self.assertEqual({
             'arch_tag': 'i386',
             'archive_private': False,
@@ -190,6 +194,7 @@
             'author_name': 'Joe User',
             'build_url': canonical_url(job.build),
             'distroseries_name': job.build.distroseries.name,
+            'fast_cleanup': True,
             'ogrecomponent': 'universe',
             'recipe_text':
                 '# bzr-builder format 0.3 '
@@ -207,11 +212,9 @@
         # build logs.
         self._setBuilderConfig()
         archive = self.factory.makeArchive(private=True)
-        job = self.makeJob(archive=archive)
-        distroarchseries = job.build.distroseries.architectures[0]
-        extra_args = yield job._extraBuildArgs(distroarchseries)
-        self.assertEqual(
-            True, extra_args['archive_private'])
+        job = self.makeJob(archive=archive, with_builder=True)
+        extra_args = yield job.extraBuildArgs()
+        self.assertEqual(True, extra_args['archive_private'])
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_team_owner_no_email(self):
@@ -224,9 +227,8 @@
         recipe_owner = self.factory.makeTeam(
             name='vikings', members=[recipe_registrant])
 
-        job = self.makeJob(recipe_registrant, recipe_owner)
-        distroarchseries = job.build.distroseries.architectures[0]
-        extra_args = yield job._extraBuildArgs(distroarchseries)
+        job = self.makeJob(recipe_registrant, recipe_owner, with_builder=True)
+        extra_args = yield job.extraBuildArgs()
         self.assertEqual(
             "Launchpad Package Builder", extra_args['author_name'])
         self.assertEqual("noreply@xxxxxxxxxxxxx", extra_args['author_email'])
@@ -241,9 +243,8 @@
             name='vikings', email='everyone@xxxxxxxxxxxx',
             members=[recipe_registrant])
 
-        job = self.makeJob(recipe_registrant, recipe_owner)
-        distroarchseries = job.build.distroseries.architectures[0]
-        extra_args = yield job._extraBuildArgs(distroarchseries)
+        job = self.makeJob(recipe_registrant, recipe_owner, with_builder=True)
+        extra_args = yield job.extraBuildArgs()
         self.assertEqual("Vikings", extra_args['author_name'])
         self.assertEqual("everyone@xxxxxxxxxxxx", extra_args['author_email'])
 
@@ -254,28 +255,27 @@
         owner = self.factory.makePerson()
         with person_logged_in(owner):
             owner.deactivate(comment='deactivating')
-        job = self.makeJob(owner)
-        distroarchseries = job.build.distroseries.architectures[0]
-        extra_args = yield job._extraBuildArgs(distroarchseries)
+        job = self.makeJob(owner, with_builder=True)
+        extra_args = yield job.extraBuildArgs()
         self.assertEqual(
             "Launchpad Package Builder", extra_args['author_name'])
         self.assertEqual("noreply@xxxxxxxxxxxxx", extra_args['author_email'])
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_withBadConfigForBzrBuilderPPA(self):
-        # Ensure _extraBuildArgs doesn't blow up with a badly formatted
+        # Ensure extraBuildArgs doesn't blow up with a badly formatted
         # bzr_builder_sources_list in the config.
         self.pushConfig(
             "builddmaster",
             bzr_builder_sources_list="deb http://foo %(series) main")
         # (note the missing 's' in %(series)
-        job = self.makeJob()
+        job = self.makeJob(with_builder=True)
         distroarchseries = job.build.distroseries.architectures[0]
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, distroarchseries, None))
         logger = BufferLogger()
-        extra_args = yield job._extraBuildArgs(distroarchseries, logger)
+        extra_args = yield job.extraBuildArgs(logger)
         self.assertEqual({
             'arch_tag': 'i386',
             'archive_private': False,
@@ -285,6 +285,7 @@
             'author_name': 'Joe User',
             'build_url': canonical_url(job.build),
             'distroseries_name': job.build.distroseries.name,
+            'fast_cleanup': True,
             'ogrecomponent': 'universe',
             'recipe_text':
                 '# bzr-builder format 0.3 '
@@ -299,12 +300,12 @@
             logger.getLogBuffer())
 
     @defer.inlineCallbacks
-    def test_extraBuildArgs_withNoBZrBuilderConfigSet(self):
-        # Ensure _extraBuildArgs doesn't blow up when
+    def test_extraBuildArgs_withNoBzrBuilderConfigSet(self):
+        # Ensure extraBuildArgs doesn't blow up when
         # bzr_builder_sources_list isn't set.
-        job = self.makeJob()
+        job = self.makeJob(with_builder=True)
         distroarchseries = job.build.distroseries.architectures[0]
-        args = yield job._extraBuildArgs(distroarchseries)
+        args = yield job.extraBuildArgs()
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, distroarchseries, None))
@@ -313,12 +314,12 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_git(self):
-        job = self.makeJob(git=True)
+        job = self.makeJob(git=True, with_builder=True)
         distroarchseries = job.build.distroseries.architectures[0]
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, distroarchseries, None))
-        extra_args = yield job._extraBuildArgs(distroarchseries)
+        extra_args = yield job.extraBuildArgs()
         self.assertEqual({
             'arch_tag': 'i386',
             'archive_private': False,
@@ -328,6 +329,7 @@
             'author_name': 'Joe User',
             'build_url': canonical_url(job.build),
             'distroseries_name': job.build.distroseries.name,
+            'fast_cleanup': True,
             'git': True,
             'ogrecomponent': 'universe',
             'recipe_text':
@@ -341,33 +343,30 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_archive_trusted_keys(self):
-        # If the archive has a signing key, _extraBuildArgs sends it.
+        # If the archive has a signing key, extraBuildArgs sends it.
         yield self.useFixture(InProcessKeyServerFixture()).start()
         archive = self.factory.makeArchive()
         key_path = os.path.join(gpgkeysdir, "ppa-sample@xxxxxxxxxxxxxxxxx")
         yield IArchiveSigningKey(archive).setSigningKey(
             key_path, async_keyserver=True)
-        job = self.makeJob(archive=archive)
+        job = self.makeJob(archive=archive, with_builder=True)
         distroarchseries = job.build.distroseries.architectures[0]
         self.factory.makeBinaryPackagePublishingHistory(
             distroarchseries=distroarchseries, pocket=job.build.pocket,
             archive=archive, status=PackagePublishingStatus.PUBLISHED)
-        args = yield job._extraBuildArgs(distroarchseries)
+        args = yield job.extraBuildArgs()
         self.assertThat(args["trusted_keys"], MatchesListwise([
             Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
             ]))
 
     @defer.inlineCallbacks
     def test_composeBuildRequest(self):
-        job = self.makeJob()
+        job = self.makeJob(with_builder=True)
         test_publisher = SoyuzTestPublisher()
         test_publisher.addFakeChroots(job.build.distroseries)
         das = job.build.distroseries.nominatedarchindep
-        builder = MockBuilder("bob-de-bouwer")
-        builder.processor = das.processor
-        job.setBuilder(builder, None)
         build_request = yield job.composeBuildRequest(None)
-        extra_args = yield job._extraBuildArgs(das)
+        extra_args = yield job.extraBuildArgs()
         self.assertEqual(
             ('sourcepackagerecipe', das, {}, extra_args), build_request)
 

=== modified file 'lib/lp/snappy/model/snapbuildbehaviour.py'
--- lib/lp/snappy/model/snapbuildbehaviour.py	2018-04-23 11:14:38 +0000
+++ lib/lp/snappy/model/snapbuildbehaviour.py	2018-05-02 13:37:24 +0000
@@ -30,7 +30,6 @@
     )
 from lp.registry.interfaces.series import SeriesStatus
 from lp.services.config import config
-from lp.services.webapp import canonical_url
 from lp.snappy.interfaces.snap import SnapBuildArchiveOwnerMismatch
 from lp.snappy.interfaces.snapbuild import ISnapBuild
 from lp.soyuz.adapters.archivedependencies import (
@@ -44,6 +43,8 @@
 class SnapBuildBehaviour(BuildFarmJobBehaviourBase):
     """Dispatches `SnapBuild` jobs to slaves."""
 
+    builder_type = "snap"
+
     def getLogFileName(self):
         das = self.build.distro_arch_series
 
@@ -79,12 +80,13 @@
                 "Missing chroot for %s" % build.distro_arch_series.displayname)
 
     @defer.inlineCallbacks
-    def _extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None):
         """
         Return the extra arguments required by the slave for the given build.
         """
         build = self.build
-        args = {}
+        args = yield super(SnapBuildBehaviour, self).extraBuildArgs(
+            logger=logger)
         if config.snappy.builder_proxy_host and build.snap.allow_internet:
             token = yield self._requestProxyToken()
             args["proxy_url"] = (
@@ -98,8 +100,6 @@
                     endpoint=config.snappy.builder_proxy_auth_api_endpoint,
                     token=token['username']))
         args["name"] = build.snap.store_name or build.snap.name
-        args["series"] = build.distro_series.name
-        args["arch_tag"] = build.distro_arch_series.architecturetag
         # XXX cjwatson 2015-08-03: Allow tools_source to be overridden at
         # some more fine-grained level.
         args["archives"], args["trusted_keys"] = (
@@ -108,8 +108,6 @@
                 tools_source=config.snappy.tools_source,
                 tools_fingerprint=config.snappy.tools_fingerprint,
                 logger=logger))
-        args["archive_private"] = build.archive.private
-        args["build_url"] = canonical_url(build)
         if build.channels is not None:
             # We have to remove the security proxy that Zope applies to this
             # dict, since otherwise we'll be unable to serialise it to
@@ -168,11 +166,6 @@
         token = json.loads(result)
         defer.returnValue(token)
 
-    @defer.inlineCallbacks
-    def composeBuildRequest(self, logger):
-        args = yield self._extraBuildArgs(logger=logger)
-        defer.returnValue(("snap", self.build.distro_arch_series, {}, args))
-
     def verifySuccessfulBuild(self):
         """See `IBuildFarmJobBehaviour`."""
         # The implementation in BuildFarmJobBehaviourBase checks whether the

=== modified file 'lib/lp/snappy/tests/test_snapbuildbehaviour.py'
--- lib/lp/snappy/tests/test_snapbuildbehaviour.py	2018-04-23 11:14:38 +0000
+++ lib/lp/snappy/tests/test_snapbuildbehaviour.py	2018-05-02 13:37:24 +0000
@@ -80,7 +80,7 @@
         self.pushConfig("snappy", tools_source=None, tools_fingerprint=None)
 
     def makeJob(self, archive=None, pocket=PackagePublishingPocket.UPDATES,
-                **kwargs):
+                with_builder=False, **kwargs):
         """Create a sample `ISnapBuildBehaviour`."""
         if archive is None:
             distribution = self.factory.makeDistribution(name="distro")
@@ -95,7 +95,12 @@
         build = self.factory.makeSnapBuild(
             archive=archive, distroarchseries=distroarchseries, pocket=pocket,
             name="test-snap", **kwargs)
-        return IBuildFarmJobBehaviour(build)
+        job = IBuildFarmJobBehaviour(build)
+        if with_builder:
+            builder = MockBuilder()
+            builder.processor = processor
+            job.setBuilder(builder, None)
+        return job
 
 
 class TestSnapBuildBehaviour(TestSnapBuildBehaviourBase):
@@ -222,7 +227,7 @@
 
     @defer.inlineCallbacks
     def test_composeBuildRequest(self):
-        job = self.makeJob()
+        job = self.makeJob(with_builder=True)
         lfa = self.factory.makeLibraryFileAlias(db_only=True)
         job.build.distro_arch_series.addOrUpdateChroot(lfa)
         build_request = yield job.composeBuildRequest(None)
@@ -233,17 +238,17 @@
     def test_requestProxyToken_unconfigured(self):
         self.pushConfig("snappy", builder_proxy_auth_api_admin_secret=None)
         branch = self.factory.makeBranch()
-        job = self.makeJob(branch=branch)
+        job = self.makeJob(branch=branch, with_builder=True)
         expected_exception_msg = (
             "builder_proxy_auth_api_admin_secret is not configured.")
         with ExpectedException(CannotBuild, expected_exception_msg):
-            yield job._extraBuildArgs()
+            yield job.extraBuildArgs()
 
     @defer.inlineCallbacks
     def test_requestProxyToken(self):
         branch = self.factory.makeBranch()
-        job = self.makeJob(branch=branch)
-        yield job._extraBuildArgs()
+        job = self.makeJob(branch=branch, with_builder=True)
+        yield job.extraBuildArgs()
         self.assertThat(self.mock_proxy_api.calls, MatchesListwise([
             MatchesListwise([
                 MatchesListwise([
@@ -265,14 +270,14 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_bzr(self):
-        # _extraBuildArgs returns appropriate arguments if asked to build a
+        # extraBuildArgs returns appropriate arguments if asked to build a
         # job for a Bazaar branch.
         branch = self.factory.makeBranch()
-        job = self.makeJob(branch=branch)
+        job = self.makeJob(branch=branch, with_builder=True)
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, job.build.distro_arch_series, None))
-        args = yield job._extraBuildArgs()
+        args = yield job.extraBuildArgs()
         self.assertEqual({
             "archive_private": False,
             "archives": expected_archives,
@@ -280,6 +285,7 @@
             "branch": branch.bzr_identity,
             "build_source_tarball": False,
             "build_url": canonical_url(job.build),
+            "fast_cleanup": True,
             "name": "test-snap",
             "proxy_url": self.proxy_url,
             "revocation_endpoint": self.revocation_endpoint,
@@ -289,20 +295,21 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_git(self):
-        # _extraBuildArgs returns appropriate arguments if asked to build a
+        # extraBuildArgs returns appropriate arguments if asked to build a
         # job for a Git branch.
         [ref] = self.factory.makeGitRefs()
-        job = self.makeJob(git_ref=ref)
+        job = self.makeJob(git_ref=ref, with_builder=True)
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, job.build.distro_arch_series, None))
-        args = yield job._extraBuildArgs()
+        args = yield job.extraBuildArgs()
         self.assertEqual({
             "archive_private": False,
             "archives": expected_archives,
             "arch_tag": "i386",
             "build_source_tarball": False,
             "build_url": canonical_url(job.build),
+            "fast_cleanup": True,
             "git_repository": ref.repository.git_https_url,
             "git_path": ref.name,
             "name": "test-snap",
@@ -314,21 +321,23 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_git_HEAD(self):
-        # _extraBuildArgs returns appropriate arguments if asked to build a
+        # extraBuildArgs returns appropriate arguments if asked to build a
         # job for the default branch in a Launchpad-hosted Git repository.
         [ref] = self.factory.makeGitRefs()
         removeSecurityProxy(ref.repository)._default_branch = ref.path
-        job = self.makeJob(git_ref=ref.repository.getRefByPath("HEAD"))
+        job = self.makeJob(
+            git_ref=ref.repository.getRefByPath("HEAD"), with_builder=True)
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, job.build.distro_arch_series, None))
-        args = yield job._extraBuildArgs()
+        args = yield job.extraBuildArgs()
         self.assertEqual({
             "archive_private": False,
             "archives": expected_archives,
             "arch_tag": "i386",
             "build_source_tarball": False,
             "build_url": canonical_url(job.build),
+            "fast_cleanup": True,
             "git_repository": ref.repository.git_https_url,
             "name": "test-snap",
             "proxy_url": self.proxy_url,
@@ -339,22 +348,23 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_git_url(self):
-        # _extraBuildArgs returns appropriate arguments if asked to build a
+        # extraBuildArgs returns appropriate arguments if asked to build a
         # job for a Git branch backed by a URL for an external repository.
         url = "https://git.example.org/foo";
         ref = self.factory.makeGitRefRemote(
             repository_url=url, path="refs/heads/master")
-        job = self.makeJob(git_ref=ref)
+        job = self.makeJob(git_ref=ref, with_builder=True)
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, job.build.distro_arch_series, None))
-        args = yield job._extraBuildArgs()
+        args = yield job.extraBuildArgs()
         self.assertEqual({
             "archive_private": False,
             "archives": expected_archives,
             "arch_tag": "i386",
             "build_source_tarball": False,
             "build_url": canonical_url(job.build),
+            "fast_cleanup": True,
             "git_repository": url,
             "git_path": "master",
             "name": "test-snap",
@@ -366,21 +376,22 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_git_url_HEAD(self):
-        # _extraBuildArgs returns appropriate arguments if asked to build a
+        # extraBuildArgs returns appropriate arguments if asked to build a
         # job for the default branch in an external Git repository.
         url = "https://git.example.org/foo";
         ref = self.factory.makeGitRefRemote(repository_url=url, path="HEAD")
-        job = self.makeJob(git_ref=ref)
+        job = self.makeJob(git_ref=ref, with_builder=True)
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, job.build.distro_arch_series, None))
-        args = yield job._extraBuildArgs()
+        args = yield job.extraBuildArgs()
         self.assertEqual({
             "archive_private": False,
             "archives": expected_archives,
             "arch_tag": "i386",
             "build_source_tarball": False,
             "build_url": canonical_url(job.build),
+            "fast_cleanup": True,
             "git_repository": url,
             "name": "test-snap",
             "proxy_url": self.proxy_url,
@@ -391,62 +402,61 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_prefers_store_name(self):
-        # For the "name" argument, _extraBuildArgs prefers Snap.store_name
+        # For the "name" argument, extraBuildArgs prefers Snap.store_name
         # over Snap.name if the former is set.
-        job = self.makeJob(store_name="something-else")
-        args = yield job._extraBuildArgs()
+        job = self.makeJob(store_name="something-else", with_builder=True)
+        args = yield job.extraBuildArgs()
         self.assertEqual("something-else", args["name"])
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_archive_trusted_keys(self):
-        # If the archive has a signing key, _extraBuildArgs sends it.
+        # If the archive has a signing key, extraBuildArgs sends it.
         yield self.useFixture(InProcessKeyServerFixture()).start()
         archive = self.factory.makeArchive()
         key_path = os.path.join(gpgkeysdir, "ppa-sample@xxxxxxxxxxxxxxxxx")
         yield IArchiveSigningKey(archive).setSigningKey(
             key_path, async_keyserver=True)
-        job = self.makeJob(archive=archive)
+        job = self.makeJob(archive=archive, with_builder=True)
         self.factory.makeBinaryPackagePublishingHistory(
             distroarchseries=job.build.distro_arch_series,
             pocket=job.build.pocket, archive=archive,
             status=PackagePublishingStatus.PUBLISHED)
-        args = yield job._extraBuildArgs()
+        args = yield job.extraBuildArgs()
         self.assertThat(args["trusted_keys"], MatchesListwise([
             Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
             ]))
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_channels(self):
-        # If the build needs particular channels, _extraBuildArgs sends
-        # them.
-        job = self.makeJob(channels={"snapcraft": "edge"})
+        # If the build needs particular channels, extraBuildArgs sends them.
+        job = self.makeJob(channels={"snapcraft": "edge"}, with_builder=True)
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, job.build.distro_arch_series, None))
-        args = yield job._extraBuildArgs()
+        args = yield job.extraBuildArgs()
         self.assertFalse(isProxy(args["channels"]))
         self.assertEqual({"snapcraft": "edge"}, args["channels"])
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_disallow_internet(self):
         # If external network access is not allowed for the snap,
-        # _extraBuildArgs does not dispatch a proxy token.
-        job = self.makeJob(allow_internet=False)
-        args = yield job._extraBuildArgs()
+        # extraBuildArgs does not dispatch a proxy token.
+        job = self.makeJob(allow_internet=False, with_builder=True)
+        args = yield job.extraBuildArgs()
         self.assertNotIn("proxy_url", args)
         self.assertNotIn("revocation_endpoint", args)
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_build_source_tarball(self):
-        # If the snap requests building of a source tarball, _extraBuildArgs
+        # If the snap requests building of a source tarball, extraBuildArgs
         # sends the appropriate arguments.
-        job = self.makeJob(build_source_tarball=True)
-        args = yield job._extraBuildArgs()
+        job = self.makeJob(build_source_tarball=True, with_builder=True)
+        args = yield job.extraBuildArgs()
         self.assertTrue(args["build_source_tarball"])
 
     @defer.inlineCallbacks
     def test_composeBuildRequest_proxy_url_set(self):
-        job = self.makeJob()
+        job = self.makeJob(with_builder=True)
         build_request = yield job.composeBuildRequest(None)
         proxy_url = ("http://{username}:{password}";
                      "@{host}:{port}".format(
@@ -462,7 +472,8 @@
         # composeBuildRequest raises CannotBuild.
         branch = self.factory.makeBranch()
         owner = self.factory.makePerson(name="snap-owner")
-        job = self.makeJob(registrant=owner, owner=owner, branch=branch)
+        job = self.makeJob(
+            registrant=owner, owner=owner, branch=branch, with_builder=True)
         branch.destroySelf(break_references=True)
         self.assertIsNone(job.build.snap.branch)
         self.assertIsNone(job.build.snap.git_repository)
@@ -478,7 +489,8 @@
         repository = self.factory.makeGitRepository()
         [ref] = self.factory.makeGitRefs(repository=repository)
         owner = self.factory.makePerson(name="snap-owner")
-        job = self.makeJob(registrant=owner, owner=owner, git_ref=ref)
+        job = self.makeJob(
+            registrant=owner, owner=owner, git_ref=ref, with_builder=True)
         repository.removeRefs([ref.path])
         self.assertIsNone(job.build.snap.git_ref)
         expected_exception_msg = ("Source branch/repository for "

=== modified file 'lib/lp/soyuz/model/binarypackagebuildbehaviour.py'
--- lib/lp/soyuz/model/binarypackagebuildbehaviour.py	2018-03-01 17:36:31 +0000
+++ lib/lp/soyuz/model/binarypackagebuildbehaviour.py	2018-05-02 13:37:24 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Builder behaviour for binary package builds."""
@@ -20,10 +20,7 @@
     BuildFarmJobBehaviourBase,
     )
 from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.services.webapp import (
-    canonical_url,
-    urlappend,
-    )
+from lp.services.webapp import urlappend
 from lp.soyuz.adapters.archivedependencies import (
     get_primary_current_component,
     get_sources_list_for_building,
@@ -36,6 +33,8 @@
 class BinaryPackageBuildBehaviour(BuildFarmJobBehaviourBase):
     """Define the behaviour of binary package builds."""
 
+    builder_type = "binarypackage"
+
     def getLogFileName(self):
         """See `IBuildPackageJob`."""
         sourcename = self.build.source_package_release.name
@@ -60,6 +59,7 @@
             state))
 
     def determineFilesToSend(self):
+        """See `IBuildFarmJobBehaviour`."""
         # Build filemap structure with the files required in this build
         # and send them to the slave.
         if self.build.archive.private:
@@ -85,13 +85,6 @@
                     'password': self.build.archive.buildd_secret}
         return filemap
 
-    @defer.inlineCallbacks
-    def composeBuildRequest(self, logger):
-        args = yield self._extraBuildArgs(self.build, logger=logger)
-        defer.returnValue(
-            ("binarypackage", self.build.distro_arch_series,
-             self.determineFilesToSend(), args))
-
     def verifyBuildRequest(self, logger):
         """Assert some pre-build checks.
 
@@ -137,19 +130,19 @@
                      build.distro_series.name))
 
     @defer.inlineCallbacks
-    def _extraBuildArgs(self, build, logger=None):
+    def extraBuildArgs(self, logger=None):
         """
         Return the extra arguments required by the slave for the given build.
         """
+        build = self.build
         das = build.distro_arch_series
 
         # Build extra arguments.
-        args = {}
+        args = yield super(BinaryPackageBuildBehaviour, self).extraBuildArgs(
+            logger=logger)
         args['arch_indep'] = build.arch_indep
         args['distribution'] = das.distroseries.distribution.name
-        args['series'] = das.distroseries.name
         args['suite'] = das.distroseries.getSuite(build.pocket)
-        args['arch_tag'] = das.architecturetag
 
         archive_purpose = build.archive.purpose
         if (archive_purpose == ArchivePurpose.PPA and
@@ -171,8 +164,6 @@
         args['archives'], args['trusted_keys'] = (
             yield get_sources_list_for_building(
                 build, das, build.source_package_release.name, logger=logger))
-        args['archive_private'] = build.archive.private
-        args['build_url'] = canonical_url(build)
         args['build_debug_symbols'] = build.archive.build_debug_symbols
 
         defer.returnValue(args)

=== modified file 'lib/lp/soyuz/model/livefsbuildbehaviour.py'
--- lib/lp/soyuz/model/livefsbuildbehaviour.py	2018-03-01 17:36:31 +0000
+++ lib/lp/soyuz/model/livefsbuildbehaviour.py	2018-05-02 13:37:24 +0000
@@ -1,4 +1,4 @@
-# Copyright 2014-2017 Canonical Ltd.  This software is licensed under the
+# Copyright 2014-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """An `IBuildFarmJobBehaviour` for `LiveFSBuild`.
@@ -24,7 +24,6 @@
     BuildFarmJobBehaviourBase,
     )
 from lp.registry.interfaces.series import SeriesStatus
-from lp.services.webapp import canonical_url
 from lp.soyuz.adapters.archivedependencies import (
     get_sources_list_for_building,
     )
@@ -38,6 +37,8 @@
 class LiveFSBuildBehaviour(BuildFarmJobBehaviourBase):
     """Dispatches `LiveFSBuild` jobs to slaves."""
 
+    builder_type = "livefs"
+
     def getLogFileName(self):
         das = self.build.distro_arch_series
         archname = das.architecturetag
@@ -76,33 +77,26 @@
                 "Missing chroot for %s" % build.distro_arch_series.displayname)
 
     @defer.inlineCallbacks
-    def _extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None):
         """
         Return the extra arguments required by the slave for the given build.
         """
         build = self.build
+        args = yield super(LiveFSBuildBehaviour, self).extraBuildArgs(
+            logger=logger)
         # Non-trivial metadata values may have been security-wrapped, which
         # is pointless here and just gets in the way of xmlrpclib
         # serialisation.
-        args = dict(removeSecurityProxy(build.livefs.metadata))
+        args.update(removeSecurityProxy(build.livefs.metadata))
         if build.metadata_override is not None:
             args.update(removeSecurityProxy(build.metadata_override))
-        args["series"] = build.distro_series.name
         args["pocket"] = build.pocket.name.lower()
-        args["arch_tag"] = build.distro_arch_series.architecturetag
         args["datestamp"] = build.version
         args["archives"], args["trusted_keys"] = (
             yield get_sources_list_for_building(
                 build, build.distro_arch_series, None, logger=logger))
-        args["archive_private"] = build.archive.private
-        args["build_url"] = canonical_url(build)
         defer.returnValue(args)
 
-    @defer.inlineCallbacks
-    def composeBuildRequest(self, logger):
-        args = yield self._extraBuildArgs(logger=logger)
-        defer.returnValue(("livefs", self.build.distro_arch_series, {}, args))
-
     def verifySuccessfulBuild(self):
         """See `IBuildFarmJobBehaviour`."""
         # The implementation in BuildFarmJobBehaviourBase checks whether the

=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuildbehaviour.py'
--- lib/lp/soyuz/tests/test_binarypackagebuildbehaviour.py	2018-03-01 17:36:31 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuildbehaviour.py	2018-05-02 13:37:24 +0000
@@ -143,6 +143,7 @@
             'build_debug_symbols': archive.build_debug_symbols,
             'build_url': canonical_url(build),
             'distribution': das.distroseries.distribution.name,
+            'fast_cleanup': builder.virtualized,
             'ogrecomponent': component,
             'series': ds_name,
             'suite': suite,
@@ -333,18 +334,24 @@
     @defer.inlineCallbacks
     def test_arch_indep(self):
         # BinaryPackageBuild.arch_indep is passed through to the slave.
+        builder = self.factory.makeBuilder()
         build = self.factory.makeBinaryPackageBuild(arch_indep=False)
-        extra_args = yield IBuildFarmJobBehaviour(build)._extraBuildArgs(build)
+        behaviour = IBuildFarmJobBehaviour(build)
+        behaviour.setBuilder(builder, None)
+        extra_args = yield behaviour.extraBuildArgs()
         self.assertFalse(extra_args['arch_indep'])
         build = self.factory.makeBinaryPackageBuild(arch_indep=True)
-        extra_args = yield IBuildFarmJobBehaviour(build)._extraBuildArgs(build)
+        behaviour = IBuildFarmJobBehaviour(build)
+        behaviour.setBuilder(builder, None)
+        extra_args = yield behaviour.extraBuildArgs()
         self.assertTrue(extra_args['arch_indep'])
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_archive_trusted_keys(self):
-        # If the archive has a signing key, _extraBuildArgs sends it.
+        # If the archive has a signing key, extraBuildArgs sends it.
         yield self.useFixture(InProcessKeyServerFixture()).start()
         archive = self.factory.makeArchive()
+        builder = self.factory.makeBuilder()
         key_path = os.path.join(gpgkeysdir, "ppa-sample@xxxxxxxxxxxxxxxxx")
         yield IArchiveSigningKey(archive).setSigningKey(
             key_path, async_keyserver=True)
@@ -352,7 +359,9 @@
         self.factory.makeBinaryPackagePublishingHistory(
             distroarchseries=build.distro_arch_series, pocket=build.pocket,
             archive=archive, status=PackagePublishingStatus.PUBLISHED)
-        args = yield IBuildFarmJobBehaviour(build)._extraBuildArgs(build)
+        behaviour = IBuildFarmJobBehaviour(build)
+        behaviour.setBuilder(builder, None)
+        args = yield behaviour.extraBuildArgs()
         self.assertThat(args["trusted_keys"], MatchesListwise([
             Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
             ]))

=== modified file 'lib/lp/soyuz/tests/test_livefsbuildbehaviour.py'
--- lib/lp/soyuz/tests/test_livefsbuildbehaviour.py	2018-03-01 17:36:31 +0000
+++ lib/lp/soyuz/tests/test_livefsbuildbehaviour.py	2018-05-02 13:37:24 +0000
@@ -69,7 +69,7 @@
         self.useFixture(FeatureFixture({LIVEFS_FEATURE_FLAG: "on"}))
 
     def makeJob(self, archive=None, pocket=PackagePublishingPocket.RELEASE,
-                **kwargs):
+                with_builder=False, **kwargs):
         """Create a sample `ILiveFSBuildBehaviour`."""
         if archive is None:
             distribution = self.factory.makeDistribution(name="distro")
@@ -84,7 +84,12 @@
         build = self.factory.makeLiveFSBuild(
             archive=archive, distroarchseries=distroarchseries, pocket=pocket,
             name="test-livefs", **kwargs)
-        return IBuildFarmJobBehaviour(build)
+        job = IBuildFarmJobBehaviour(build)
+        if with_builder:
+            builder = MockBuilder()
+            builder.processor = processor
+            job.setBuilder(builder, None)
+        return job
 
 
 class TestLiveFSBuildBehaviour(TestLiveFSBuildBehaviourBase):
@@ -185,20 +190,22 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs(self):
-        # _extraBuildArgs returns a reasonable set of additional arguments.
+        # extraBuildArgs returns a reasonable set of additional arguments.
         job = self.makeJob(
             date_created=datetime(2014, 4, 25, 10, 38, 0, tzinfo=pytz.UTC),
-            metadata={"project": "distro", "subproject": "special"})
+            metadata={"project": "distro", "subproject": "special"},
+            with_builder=True)
         expected_archives, expected_trusted_keys = (
             yield get_sources_list_for_building(
                 job.build, job.build.distro_arch_series, None))
-        extra_args = yield job._extraBuildArgs()
+        extra_args = yield job.extraBuildArgs()
         self.assertEqual({
             "archive_private": False,
             "archives": expected_archives,
             "arch_tag": "i386",
             "build_url": canonical_url(job.build),
             "datestamp": "20140425-103800",
+            "fast_cleanup": True,
             "pocket": "release",
             "project": "distro",
             "subproject": "special",
@@ -208,50 +215,51 @@
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_proposed(self):
-        # _extraBuildArgs returns appropriate arguments if asked to build a
+        # extraBuildArgs returns appropriate arguments if asked to build a
         # job for -proposed.
         job = self.makeJob(
             pocket=PackagePublishingPocket.PROPOSED,
-            metadata={"project": "distro"})
-        args = yield job._extraBuildArgs()
+            metadata={"project": "distro"}, with_builder=True)
+        args = yield job.extraBuildArgs()
         self.assertEqual("unstable", args["series"])
         self.assertEqual("proposed", args["pocket"])
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_no_security_proxy(self):
-        # _extraBuildArgs returns an object without security wrapping, even
+        # extraBuildArgs returns an object without security wrapping, even
         # if values in the metadata are (say) lists and hence get proxied by
         # Zope.
-        job = self.makeJob(metadata={"lb_args": ["--option=value"]})
-        args = yield job._extraBuildArgs()
+        job = self.makeJob(
+            metadata={"lb_args": ["--option=value"]}, with_builder=True)
+        args = yield job.extraBuildArgs()
         self.assertEqual(["--option=value"], args["lb_args"])
         self.assertIsNot(Proxy, type(args["lb_args"]))
 
     @defer.inlineCallbacks
     def test_extraBuildArgs_archive_trusted_keys(self):
-        # If the archive has a signing key, _extraBuildArgs sends it.
+        # If the archive has a signing key, extraBuildArgs sends it.
         yield self.useFixture(InProcessKeyServerFixture()).start()
         archive = self.factory.makeArchive()
         key_path = os.path.join(gpgkeysdir, "ppa-sample@xxxxxxxxxxxxxxxxx")
         yield IArchiveSigningKey(archive).setSigningKey(
             key_path, async_keyserver=True)
-        job = self.makeJob(archive=archive)
+        job = self.makeJob(archive=archive, with_builder=True)
         self.factory.makeBinaryPackagePublishingHistory(
             distroarchseries=job.build.distro_arch_series,
             pocket=job.build.pocket, archive=archive,
             status=PackagePublishingStatus.PUBLISHED)
-        args = yield job._extraBuildArgs()
+        args = yield job.extraBuildArgs()
         self.assertThat(args["trusted_keys"], MatchesListwise([
             Base64KeyMatches("0D57E99656BEFB0897606EE9A022DD1F5001B46D"),
             ]))
 
     @defer.inlineCallbacks
     def test_composeBuildRequest(self):
-        job = self.makeJob()
+        job = self.makeJob(with_builder=True)
         lfa = self.factory.makeLibraryFileAlias(db_only=True)
         job.build.distro_arch_series.addOrUpdateChroot(lfa)
         build_request = yield job.composeBuildRequest(None)
-        args = yield job._extraBuildArgs()
+        args = yield job.extraBuildArgs()
         self.assertEqual(
             ('livefs', job.build.distro_arch_series, {}, args), build_request)
 


Follow ups