Ines Almeida has proposed merging ~ines-almeida/launchpad:fetch-service-update-build-metadata-url into launchpad:master.
Commit message: Add DB load bulk load for `build_metadata_url` attributes when getting multiple builds This reduces the number of DB queries made in the case for where a snap has multiple builds. Also: revert commit that commented out a test that verified the snap.builds queries number Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~ines-almeida/launchpad/+git/launchpad/+merge/464839 The test that was previously commented out now runs successfully. Also added a new test, and re-ran the old `build_metadata_url` related tests succesfully. -- Your team Launchpad code reviewers is requested to review the proposed merge of ~ines-almeida/launchpad:fetch-service-update-build-metadata-url into launchpad:master.
diff --git a/lib/lp/snappy/model/snapbuild.py b/lib/lp/snappy/model/snapbuild.py index ea7bf99..842b142 100644 --- a/lib/lp/snappy/model/snapbuild.py +++ b/lib/lp/snappy/model/snapbuild.py @@ -6,6 +6,7 @@ __all__ = [ "SnapFile", ] +from collections import defaultdict from datetime import timedelta, timezone from operator import attrgetter @@ -51,7 +52,7 @@ from lp.registry.model.distribution import Distribution from lp.registry.model.distroseries import DistroSeries from lp.registry.model.person import Person from lp.services.config import config -from lp.services.database.bulk import load_related +from lp.services.database.bulk import load_referencing, load_related from lp.services.database.constants import DEFAULT from lp.services.database.decoratedresultset import DecoratedResultSet from lp.services.database.enumcol import DBEnum @@ -427,12 +428,15 @@ class SnapBuild(PackageBuildMixin, StormBase): return [self.lfaUrl(lfa) for _, lfa, _ in self.getFiles()] @property - def build_metadata_url(self): - metadata_filename = BUILD_METADATA_FILENAME_FORMAT.format( + def metadata_filename(self): + return BUILD_METADATA_FILENAME_FORMAT.format( build_id=self.build_cookie ) + + @cachedproperty + def build_metadata_url(self): try: - return self.lfaUrl(self.getFileByName(metadata_filename)) + return self.lfaUrl(self.getFileByName(self.metadata_filename)) except NotFoundError: return None @@ -663,6 +667,24 @@ class SnapBuildSet(SpecificBuildFarmJobSourceMixin): ) load_related(Job, sbjs, ["job_id"]) + # Prefetch all snaps metadata files + snap_files = load_referencing(SnapFile, builds, ["snapbuild_id"]) + lfas = load_related(LibraryFileAlias, snap_files, ["libraryfile_id"]) + + metadata_files = defaultdict(list) + for snap_file in snap_files: + if ( + snap_file.libraryfile.filename + == snap_file.snapbuild.metadata_filename + ): + metadata_files[snap_file.snapbuild_id] = snap_file.libraryfile + + for build in builds: + cache = get_property_cache(build) + cache.build_metadata_url = build.lfaUrl( + metadata_files.get(build.id) + ) + def getByBuildFarmJobs(self, build_farm_jobs): """See `ISpecificBuildFarmJobSource`.""" if len(build_farm_jobs) == 0: diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py index eace8dd..7aa8c29 100644 --- a/lib/lp/snappy/tests/test_snap.py +++ b/lib/lp/snappy/tests/test_snap.py @@ -5798,70 +5798,121 @@ class TestSnapWebservice(TestCaseWithFactory): self.webservice.get(url) self.assertThat(recorder, HasQueryCount(Equals(15))) - # XXX ines-almeida 2024-04-22: after adding the new API attribute - # `build_metadata_url` to the snap builds, we actually perform an extra - # query per build. We need to make a decision on whether we are OK with - # this (and in such case, this test should be reworked or eventually - # removed) - # - # def test_builds_query_count(self): - # # The query count of Snap.builds is constant in the number of - # # builds, even if they have store upload jobs. - # self.pushConfig( - # "snappy", - # store_url="http://sca.example/", - # store_upload_url="http://updown.example/", - # ) - # with admin_logged_in(): - # snappyseries = self.factory.makeSnappySeries() - # distroseries = self.factory.makeDistroSeries( - # distribution=getUtility(IDistributionSet)["ubuntu"], - # registrant=self.person, - # ) - # processor = self.factory.makeProcessor(supports_virtualized=True) - # distroarchseries = self.makeBuildableDistroArchSeries( - # distroseries=distroseries, processor=processor, owner=self.person - # ) - # with person_logged_in(self.person): - # snap = self.factory.makeSnap( - # registrant=self.person, - # owner=self.person, - # distroseries=distroseries, - # processors=[processor], - # ) - # snap.store_series = snappyseries - # snap.store_name = self.factory.getUniqueUnicode() - # snap.store_upload = True - # snap.store_secrets = {"root": Macaroon().serialize()} - # builds_url = "%s/builds" % api_url(snap) - # logout() - # - # def make_build(): - # with person_logged_in(self.person): - # builder = self.factory.makeBuilder() - # build = snap.requestBuild( - # self.person, - # distroseries.main_archive, - # distroarchseries, - # PackagePublishingPocket.PROPOSED, - # ) - # with dbuser(config.builddmaster.dbuser): - # build.updateStatus( - # BuildStatus.BUILDING, date_started=snap.date_created - # ) - # build.updateStatus( - # BuildStatus.FULLYBUILT, - # builder=builder, - # date_finished=( - # snap.date_created + timedelta(minutes=10) - # ), - # ) - # return build - # - # def get_builds(): - # response = self.webservice.get(builds_url) - # self.assertEqual(200, response.status) - # return response - # - # recorder1, recorder2 = record_two_runs(get_builds, make_build, 2) - # self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) + def test_builds_query_count(self): + # The query count of Snap.builds is constant regardless of the number + # of builds, even if they have store upload jobs. + self.pushConfig( + "snappy", + store_url="http://sca.example/", + store_upload_url="http://updown.example/", + ) + with admin_logged_in(): + snappyseries = self.factory.makeSnappySeries() + distroseries = self.factory.makeDistroSeries( + distribution=getUtility(IDistributionSet)["ubuntu"], + registrant=self.person, + ) + processor = self.factory.makeProcessor(supports_virtualized=True) + distroarchseries = self.makeBuildableDistroArchSeries( + distroseries=distroseries, processor=processor, owner=self.person + ) + with person_logged_in(self.person): + snap = self.factory.makeSnap( + registrant=self.person, + owner=self.person, + distroseries=distroseries, + processors=[processor], + ) + snap.store_series = snappyseries + snap.store_name = self.factory.getUniqueUnicode() + snap.store_upload = True + snap.store_secrets = {"root": Macaroon().serialize()} + builds_url = "%s/builds" % api_url(snap) + logout() + + def make_build(): + with person_logged_in(self.person): + builder = self.factory.makeBuilder() + build = snap.requestBuild( + self.person, + distroseries.main_archive, + distroarchseries, + PackagePublishingPocket.PROPOSED, + ) + with dbuser(config.builddmaster.dbuser): + build.updateStatus( + BuildStatus.BUILDING, date_started=snap.date_created + ) + build.updateStatus( + BuildStatus.FULLYBUILT, + builder=builder, + date_finished=( + snap.date_created + timedelta(minutes=10) + ), + ) + return build + + def get_builds(): + response = self.webservice.get(builds_url) + self.assertEqual(200, response.status) + return response + + recorder1, recorder2 = record_two_runs(get_builds, make_build, 2) + self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) + + def test_builds_metadata_url(self): + # The DB load for the `build_metadata_url` attribute of builds returns + # the expected value + with admin_logged_in(): + snappyseries = self.factory.makeSnappySeries() + distroseries = self.factory.makeDistroSeries( + distribution=getUtility(IDistributionSet)["ubuntu"], + registrant=self.person, + ) + processor = self.factory.makeProcessor(supports_virtualized=True) + distroarchseries = self.makeBuildableDistroArchSeries( + distroseries=distroseries, processor=processor, owner=self.person + ) + with person_logged_in(self.person): + snap = self.factory.makeSnap( + registrant=self.person, + owner=self.person, + distroseries=distroseries, + processors=[processor], + ) + snap.store_series = snappyseries + snap.store_name = self.factory.getUniqueUnicode() + snap.store_upload = True + snap.store_secrets = {"root": Macaroon().serialize()} + builds_url = "%s/builds" % api_url(snap) + logout() + + with person_logged_in(self.person): + build = snap.requestBuild( + self.person, + distroseries.main_archive, + distroarchseries, + PackagePublishingPocket.PROPOSED, + ) + snap1_lfa = self.factory.makeLibraryFileAlias( + filename="test.snap", + content=b"dummy snap content", + db_only=True, + ) + metadata_filename = f"{build.build_cookie}_metadata.json" + snap2_lfa = self.factory.makeLibraryFileAlias( + filename=metadata_filename, + content=b"dummy snap content", + db_only=True, + ) + self.factory.makeSnapFile(snapbuild=build, libraryfile=snap2_lfa) + self.factory.makeSnapFile(snapbuild=build, libraryfile=snap1_lfa) + + response = self.webservice.get(builds_url) + self.assertEqual(200, response.status) + snap_builds = (response.jsonBody()["entries"],) + self.assertEqual(1, len(snap_builds)) + self.assertIn( + metadata_filename, + snap_builds[0][0]["build_metadata_url"], + )
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp