← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:snap-build-record-snap-base into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:snap-build-record-snap-base into launchpad:master.

Commit message:
Record the snap base used for each snap build

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/400348

This will be needed in order to dispatch snap base archive dependencies.

DB MP: https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/400347
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:snap-build-record-snap-base into launchpad:master.
diff --git a/lib/lp/snappy/interfaces/snap.py b/lib/lp/snappy/interfaces/snap.py
index 35cf6f6..7448659 100644
--- a/lib/lp/snappy/interfaces/snap.py
+++ b/lib/lp/snappy/interfaces/snap.py
@@ -110,6 +110,7 @@ from lp.services.fields import (
     URIField,
     )
 from lp.services.webhooks.interfaces import IWebhookTarget
+from lp.snappy.interfaces.snapbase import ISnapBase
 from lp.snappy.interfaces.snappyseries import (
     ISnappyDistroSeries,
     ISnappySeries,
@@ -408,6 +409,7 @@ class ISnapView(Interface):
         archive=Reference(schema=IArchive),
         distro_arch_series=Reference(schema=IDistroArchSeries),
         pocket=Choice(vocabulary=PackagePublishingPocket),
+        snap_base=Reference(schema=ISnapBase),
         channels=Dict(
             title=_("Source snap channels to use for this build."),
             description=_(
@@ -419,13 +421,14 @@ class ISnapView(Interface):
     @export_factory_operation(Interface, [])
     @operation_for_version("devel")
     def requestBuild(requester, archive, distro_arch_series, pocket,
-                     channels=None, build_request=None):
+                     snap_base=None, channels=None, build_request=None):
         """Request that the snap package be built.
 
         :param requester: The person requesting the build.
         :param archive: The IArchive to associate the build with.
         :param distro_arch_series: The architecture to build for.
         :param pocket: The pocket that should be targeted.
+        :param snap_base: The `ISnapBase` to use for this build.
         :param channels: A dictionary mapping snap names to channels to use
             for this build.
         :param build_request: The `ISnapBuildRequest` job being processed,
diff --git a/lib/lp/snappy/interfaces/snapbuild.py b/lib/lp/snappy/interfaces/snapbuild.py
index e812f46..7072731 100644
--- a/lib/lp/snappy/interfaces/snapbuild.py
+++ b/lib/lp/snappy/interfaces/snapbuild.py
@@ -1,4 +1,4 @@
-# Copyright 2015-2020 Canonical Ltd.  This software is licensed under the
+# Copyright 2015-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Snap package build interfaces."""
@@ -60,6 +60,7 @@ from lp.snappy.interfaces.snap import (
     ISnap,
     ISnapBuildRequest,
     )
+from lp.snappy.interfaces.snapbase import ISnapBase
 from lp.soyuz.interfaces.archive import IArchive
 from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
 
@@ -164,6 +165,11 @@ class ISnapBuildView(IPackageBuild):
         title=_("The pocket for which to build."),
         vocabulary=PackagePublishingPocket, required=True, readonly=True))
 
+    snap_base = exported(Reference(
+        ISnapBase,
+        title=_("The snap base to use for this build."),
+        required=False, readonly=True))
+
     channels = exported(Dict(
         title=_("Source snap channels to use for this build."),
         description=_(
@@ -355,7 +361,8 @@ class ISnapBuildSet(ISpecificBuildFarmJobSource):
     """Utility for `ISnapBuild`."""
 
     def new(requester, snap, archive, distro_arch_series, pocket,
-            date_created=DEFAULT):
+            snap_base=None, channels=None, date_created=DEFAULT,
+            store_upload_metadata=None, build_request=None):
         """Create an `ISnapBuild`."""
 
     def preloadBuildsData(builds):
diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py
index 2eac649..523ae68 100644
--- a/lib/lp/snappy/model/snap.py
+++ b/lib/lp/snappy/model/snap.py
@@ -776,7 +776,7 @@ class Snap(Storm, WebhookTargetMixin):
             raise SnapBuildArchiveOwnerMismatch()
 
     def requestBuild(self, requester, archive, distro_arch_series, pocket,
-                     channels=None, build_request=None):
+                     snap_base=None, channels=None, build_request=None):
         """See `ISnap`."""
         self._checkRequestBuild(requester, archive)
         if not self._isArchitectureAllowed(distro_arch_series, pocket):
@@ -800,7 +800,8 @@ class Snap(Storm, WebhookTargetMixin):
 
         build = getUtility(ISnapBuildSet).new(
             requester, self, archive, distro_arch_series, pocket,
-            channels=channels, build_request=build_request)
+            snap_base=snap_base, channels=channels,
+            build_request=build_request)
         build.queueBuild()
         notify(ObjectCreatedEvent(build, user=requester))
         return build
@@ -916,7 +917,8 @@ class Snap(Storm, WebhookTargetMixin):
             try:
                 build = self.requestBuild(
                     requester, archive, supported_arches[arch], pocket,
-                    channels, build_request=build_request)
+                    snap_base=snap_base, channels=channels,
+                    build_request=build_request)
                 if logger is not None:
                     logger.debug(
                         " - %s/%s/%s: Build requested.",
diff --git a/lib/lp/snappy/model/snapbuild.py b/lib/lp/snappy/model/snapbuild.py
index 3a9475b..2b533b7 100644
--- a/lib/lp/snappy/model/snapbuild.py
+++ b/lib/lp/snappy/model/snapbuild.py
@@ -1,4 +1,4 @@
-# Copyright 2015-2020 Canonical Ltd.  This software is licensed under the
+# Copyright 2015-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 from __future__ import absolute_import, print_function, unicode_literals
@@ -157,6 +157,8 @@ class SnapBuild(PackageBuildMixin, Storm):
 
     pocket = DBEnum(enum=PackagePublishingPocket, allow_none=False)
 
+    snap_base_id = Int(name='snap_base', allow_none=True)
+    snap_base = Reference(snap_base_id, 'SnapBase.id')
     channels = JSON('channels', allow_none=True)
 
     processor_id = Int(name='processor', allow_none=False)
@@ -190,8 +192,9 @@ class SnapBuild(PackageBuildMixin, Storm):
     store_upload_metadata = JSON('store_upload_json_data', allow_none=True)
 
     def __init__(self, build_farm_job, requester, snap, archive,
-                 distro_arch_series, pocket, channels, processor, virtualized,
-                 date_created, store_upload_metadata=None, build_request=None):
+                 distro_arch_series, pocket, snap_base, channels,
+                 processor, virtualized, date_created,
+                 store_upload_metadata=None, build_request=None):
         """Construct a `SnapBuild`."""
         super(SnapBuild, self).__init__()
         self.build_farm_job = build_farm_job
@@ -200,6 +203,7 @@ class SnapBuild(PackageBuildMixin, Storm):
         self.archive = archive
         self.distro_arch_series = distro_arch_series
         self.pocket = pocket
+        self.snap_base = snap_base
         self.channels = channels
         self.processor = processor
         self.virtualized = virtualized
@@ -560,7 +564,7 @@ class SnapBuild(PackageBuildMixin, Storm):
 class SnapBuildSet(SpecificBuildFarmJobSourceMixin):
 
     def new(self, requester, snap, archive, distro_arch_series, pocket,
-            channels=None, date_created=DEFAULT,
+            snap_base=None, channels=None, date_created=DEFAULT,
             store_upload_metadata=None, build_request=None):
         """See `ISnapBuildSet`."""
         store = IMasterStore(SnapBuild)
@@ -569,7 +573,7 @@ class SnapBuildSet(SpecificBuildFarmJobSourceMixin):
             archive)
         snapbuild = SnapBuild(
             build_farm_job, requester, snap, archive, distro_arch_series,
-            pocket, channels, distro_arch_series.processor,
+            pocket, snap_base, channels, distro_arch_series.processor,
             not distro_arch_series.processor.supports_nonvirtualized
             or snap.require_virtualized or archive.require_virtualized,
             date_created, store_upload_metadata=store_upload_metadata,
diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py
index e5ec2c7..f827970 100644
--- a/lib/lp/snappy/tests/test_snap.py
+++ b/lib/lp/snappy/tests/test_snap.py
@@ -243,12 +243,15 @@ class TestSnap(TestCaseWithFactory):
             snap.owner, snap.distro_series.main_archive, distroarchseries,
             PackagePublishingPocket.UPDATES)
         self.assertTrue(ISnapBuild.providedBy(build))
-        self.assertEqual(snap.owner, build.requester)
-        self.assertEqual(snap.distro_series.main_archive, build.archive)
-        self.assertEqual(distroarchseries, build.distro_arch_series)
-        self.assertEqual(PackagePublishingPocket.UPDATES, build.pocket)
-        self.assertIsNone(build.channels)
-        self.assertEqual(BuildStatus.NEEDSBUILD, build.status)
+        self.assertThat(build, MatchesStructure(
+            requester=Equals(snap.owner),
+            archive=Equals(snap.distro_series.main_archive),
+            distro_arch_series=Equals(distroarchseries),
+            pocket=Equals(PackagePublishingPocket.UPDATES),
+            snap_base=Is(None),
+            channels=Is(None),
+            status=Equals(BuildStatus.NEEDSBUILD),
+            ))
         store = Store.of(build)
         store.flush()
         build_queue = store.find(
@@ -292,6 +295,20 @@ class TestSnap(TestCaseWithFactory):
         queue_record.score()
         self.assertEqual(2610, queue_record.lastscore)
 
+    def test_requestBuild_snap_base(self):
+        # requestBuild can select a snap base.
+        processor = self.factory.makeProcessor(supports_virtualized=True)
+        distroarchseries = self.makeBuildableDistroArchSeries(
+            processor=processor)
+        snap = self.factory.makeSnap(
+            distroseries=distroarchseries.distroseries, processors=[processor])
+        with admin_logged_in():
+            snap_base = self.factory.makeSnapBase()
+        build = snap.requestBuild(
+            snap.owner, snap.distro_series.main_archive, distroarchseries,
+            PackagePublishingPocket.UPDATES, snap_base=snap_base)
+        self.assertEqual(snap_base, build.snap_base)
+
     def test_requestBuild_channels(self):
         # requestBuild can select non-default channels.
         processor = self.factory.makeProcessor(supports_virtualized=True)
@@ -655,8 +672,8 @@ class TestSnap(TestCaseWithFactory):
             snap, snap.owner.teamowner, distro.main_archive,
             PackagePublishingPocket.RELEASE, {"snapcraft": "edge"})
 
-    def assertRequestedBuildsMatch(self, builds, job, arch_tags, channels,
-                                   distro_series=None):
+    def assertRequestedBuildsMatch(self, builds, job, arch_tags, snap_base,
+                                   channels, distro_series=None):
         if distro_series is None:
             distro_series = job.snap.distro_series
         self.assertThat(builds, MatchesSetwise(
@@ -666,6 +683,7 @@ class TestSnap(TestCaseWithFactory):
                 archive=Equals(job.archive),
                 distro_arch_series=Equals(distro_series[arch_tag]),
                 pocket=Equals(job.pocket),
+                snap_base=Equals(snap_base),
                 channels=Equals(channels))
               for arch_tag in arch_tags)))
 
@@ -687,7 +705,8 @@ class TestSnap(TestCaseWithFactory):
                 job.requester, job.archive, job.pocket,
                 channels=removeSecurityProxy(job.channels),
                 build_request=job.build_request)
-        self.assertRequestedBuildsMatch(builds, job, ["sparc"], job.channels)
+        self.assertRequestedBuildsMatch(
+            builds, job, ["sparc"], None, job.channels)
 
     def test_requestBuildsFromJob_no_explicit_architectures(self):
         # If the snap doesn't specify any architectures,
@@ -704,7 +723,7 @@ class TestSnap(TestCaseWithFactory):
                 channels=removeSecurityProxy(job.channels),
                 build_request=job.build_request)
         self.assertRequestedBuildsMatch(
-            builds, job, ["mips64el", "riscv64"], job.channels)
+            builds, job, ["mips64el", "riscv64"], None, job.channels)
 
     def test_requestBuildsFromJob_architectures_parameter(self):
         # If an explicit set of architectures was given as a parameter,
@@ -722,7 +741,7 @@ class TestSnap(TestCaseWithFactory):
                 architectures={"avr", "riscv64"},
                 build_request=job.build_request)
         self.assertRequestedBuildsMatch(
-            builds, job, ["avr", "riscv64"], job.channels)
+            builds, job, ["avr", "riscv64"], None, job.channels)
 
     def test_requestBuildsFromJob_no_distroseries_explicit_base(self):
         # If the snap doesn't specify a distroseries but has an explicit
@@ -752,8 +771,8 @@ class TestSnap(TestCaseWithFactory):
                 job.requester, job.archive, job.pocket,
                 build_request=job.build_request)
         self.assertRequestedBuildsMatch(
-            builds, job, ["mips64el", "riscv64"], snap_base.build_channels,
-            distro_series=snap_base.distro_series)
+            builds, job, ["mips64el", "riscv64"], snap_base,
+            snap_base.build_channels, distro_series=snap_base.distro_series)
 
     def test_requestBuildsFromJob_no_distroseries_no_explicit_base(self):
         # If the snap doesn't specify a distroseries and has no explicit
@@ -783,8 +802,8 @@ class TestSnap(TestCaseWithFactory):
                 job.requester, job.archive, job.pocket,
                 build_request=job.build_request)
         self.assertRequestedBuildsMatch(
-            builds, job, ["mips64el", "riscv64"], snap_base.build_channels,
-            distro_series=snap_base.distro_series)
+            builds, job, ["mips64el", "riscv64"], snap_base,
+            snap_base.build_channels, distro_series=snap_base.distro_series)
 
     def test_requestBuildsFromJob_no_distroseries_no_default_base(self):
         # If the snap doesn't specify a distroseries and has an explicit
@@ -822,7 +841,7 @@ class TestSnap(TestCaseWithFactory):
                 removeSecurityProxy(job.channels),
                 build_request=job.build_request)
         self.assertRequestedBuildsMatch(
-            builds, job, ["mips64el", "riscv64"], job.channels)
+            builds, job, ["mips64el", "riscv64"], None, job.channels)
 
     def test_requestBuildsFromJob_triggers_webhooks(self):
         # requestBuildsFromJob triggers webhooks, and the payload includes a
diff --git a/lib/lp/snappy/tests/test_snapbuild.py b/lib/lp/snappy/tests/test_snapbuild.py
index ee67203..e949c69 100644
--- a/lib/lp/snappy/tests/test_snapbuild.py
+++ b/lib/lp/snappy/tests/test_snapbuild.py
@@ -760,6 +760,7 @@ class TestSnapBuildWebservice(TestCaseWithFactory):
             self.assertEqual(
                 db_build.distro_arch_series.architecturetag, build["arch_tag"])
             self.assertEqual("Updates", build["pocket"])
+            self.assertIsNone(build["snap_base_link"])
             self.assertIsNone(build["channels"])
             self.assertIsNone(build["score"])
             self.assertFalse(build["can_be_rescored"])
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index d4ed1fd..10251b6 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -4831,9 +4831,9 @@ class BareLaunchpadObjectFactory(ObjectFactory):
 
     def makeSnapBuild(self, requester=None, registrant=None, snap=None,
                       archive=None, distroarchseries=None, pocket=None,
-                      channels=None, date_created=DEFAULT, build_request=None,
-                      status=BuildStatus.NEEDSBUILD, builder=None,
-                      duration=None, **kwargs):
+                      snap_base=None, channels=None, date_created=DEFAULT,
+                      build_request=None, status=BuildStatus.NEEDSBUILD,
+                      builder=None, duration=None, **kwargs):
         """Make a new SnapBuild."""
         if requester is None:
             requester = self.makePerson()
@@ -4861,7 +4861,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
             pocket = PackagePublishingPocket.UPDATES
         snapbuild = getUtility(ISnapBuildSet).new(
             requester, snap, archive, distroarchseries, pocket,
-            channels=channels, date_created=date_created,
+            snap_base=snap_base, channels=channels, date_created=date_created,
             build_request=build_request)
         if duration is not None:
             removeSecurityProxy(snapbuild).updateStatus(