← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~julian-edwards/launchpad/arch-all-domination-bug-34086 into lp:launchpad

 

Julian Edwards has proposed merging lp:~julian-edwards/launchpad/arch-all-domination-bug-34086 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #34086 in Launchpad itself: "removal of arch-all packages while there are arch-specific packages dependent on it results in uninstallable binaries"
  https://bugs.launchpad.net/launchpad/+bug/34086

For more details, see:
https://code.launchpad.net/~julian-edwards/launchpad/arch-all-domination-bug-34086/+merge/78931

Fix bug 34086 - "removal of arch-all packages while there are arch-specific packages dependent on it results in uninstallable binaries"

The simplest case of this can be described thusly:

* source foo 1.0 enters the archive and creates
  * foo-bin 1.0 (i386) (depends on foo-common 1.0)
  * foo-common 1.0 (arch-indep)
* source foo 1.1 is uploaded
* foo-common 1.1 finishes building and supersedes foo-bin 1.0
* foo-bin 1.0 is now uninstallable as its dependencies can't be met

The fix works by making sure that any arch-indep binaries do not get superseded until all the binaries built by the same source are also superseded.  This means that we can now have multiple live versions of arch-indep binaries in the archive index.
-- 
https://code.launchpad.net/~julian-edwards/launchpad/arch-all-domination-bug-34086/+merge/78931
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~julian-edwards/launchpad/arch-all-domination-bug-34086 into lp:launchpad.
=== modified file 'lib/lp/archivepublisher/domination.py'
--- lib/lp/archivepublisher/domination.py	2011-09-23 13:00:49 +0000
+++ lib/lp/archivepublisher/domination.py	2011-10-13 12:06:33 +0000
@@ -153,6 +153,7 @@
     class.
     """
     def __init__(self, is_source=True):
+        self.is_source = is_source
         if is_source:
             self.traits = SourcePublicationTraits
         else:
@@ -204,6 +205,27 @@
         self.logger = logger
         self.archive = archive
 
+    def _checkArchIndep(self, publication):
+        """Return True if the binary publication can be superseded.
+
+        If the publication is an arch-indep binary, we can only supersede
+        it if all the binaries from the same source are also superseded,
+        else those binaries may become uninstallable.
+        See bug 34086.
+        """
+        binary = publication.binarypackagerelease
+        if not binary.architecturespecific:
+            # getOtherPublicationsForSameSource returns PENDING in
+            # addition to PUBLISHED binaries, and we rely on this since
+            # they must also block domination.
+            others = publication.getOtherPublicationsForSameSource()
+            if others.any():
+                # Don't dominate this arch:all binary as there are
+                # other arch-specific binaries from the same build
+                # that are still active.
+                return False
+        return True
+
     def dominatePackage(self, publications, live_versions, generalization):
         """Dominate publications for a single package.
 
@@ -260,7 +282,9 @@
                 pub.supersede(current_dominant, logger=self.logger)
                 self.logger.debug2(
                     "Superseding older publication for version %s.", version)
-            elif version in live_versions:
+            elif (version in live_versions or
+                  (not generalization.is_source and
+                   not self._checkArchIndep(pub))):
                 # This publication stays active; if any publications
                 # that follow right after this are to be superseded,
                 # this is the release that they are superseded by.
@@ -359,27 +383,6 @@
 
         self.logger.debug("Beginning superseded processing...")
 
-        # XXX: dsilvers 2005-09-22 bug=55030:
-        # Need to make binaries go in groups but for now this'll do.
-        # An example of the concrete problem here is:
-        # - Upload foo-1.0, which builds foo and foo-common (arch all).
-        # - Upload foo-1.1, ditto.
-        # - foo-common-1.1 is built (along with the i386 binary for foo)
-        # - foo-common-1.0 is superseded
-        # Foo is now uninstallable on any architectures which don't yet
-        # have a build of foo-1.1, as the foo-common for foo-1.0 is gone.
-
-        # Essentially we ideally don't want to lose superseded binaries
-        # unless the entire group is ready to be made pending removal.
-        # In this instance a group is defined as all the binaries from a
-        # given build. This assumes we've copied the arch_all binaries
-        # from whichever build provided them into each arch-specific build
-        # which we publish. If instead we simply publish the arch-all
-        # binaries from another build then instead we should scan up from
-        # the binary to its source, and then back from the source to each
-        # binary published in *this* distroarchseries for that source.
-        # if the binaries as a group (in that definition) are all superseded
-        # then we can consider them eligible for removal.
         for pub_record in binary_records:
             binpkg_release = pub_record.binarypackagerelease
             self.logger.debug(
@@ -450,19 +453,19 @@
         generalization = GeneralizedPublication(is_source=False)
 
         for distroarchseries in distroseries.architectures:
-            self.logger.debug(
+            self.logger.info(
                 "Performing domination across %s/%s (%s)",
                 distroseries.name, pocket.title,
                 distroarchseries.architecturetag)
 
-            bpph_location_clauses = And(
+            bpph_location_clauses = [
                 BinaryPackagePublishingHistory.status ==
                     PackagePublishingStatus.PUBLISHED,
                 BinaryPackagePublishingHistory.distroarchseries ==
                     distroarchseries,
                 BinaryPackagePublishingHistory.archive == self.archive,
-                BinaryPackagePublishingHistory.pocket == pocket,
-                )
+                BinaryPackagePublishingHistory.pocket == pocket
+                ]
             candidate_binary_names = Select(
                 BinaryPackageName.id,
                 And(
@@ -474,18 +477,43 @@
                 ),
                 group_by=BinaryPackageName.id,
                 having=Count(BinaryPackagePublishingHistory.id) > 1)
-            binaries = IStore(BinaryPackagePublishingHistory).find(
+            main_clauses = [
                 BinaryPackagePublishingHistory,
                 BinaryPackageRelease.id ==
-                    BinaryPackagePublishingHistory.binarypackagereleaseID,
+                BinaryPackagePublishingHistory.binarypackagereleaseID,
                 BinaryPackageRelease.binarypackagenameID.is_in(
                     candidate_binary_names),
                 BinaryPackageRelease.binpackageformat !=
-                    BinaryPackageFormat.DDEB,
-                bpph_location_clauses)
-            self.logger.debug("Dominating binaries...")
-            self._dominatePublications(
-                self._sortPackages(binaries, generalization), generalization)
+                BinaryPackageFormat.DDEB,
+                bpph_location_clauses]
+
+            # Arch-indep binaries need to be done last as they depend on
+            # arch-specific binaries being superseded.
+            arch_specific_clauses = []
+            arch_specific_clauses.extend(main_clauses)
+            arch_specific_clauses.append(
+                BinaryPackageRelease.architecturespecific == True)
+            arch_specific_clauses.extend(bpph_location_clauses)
+            self.logger.info("Finding arch-specific binaries...")
+            arch_specific_bins = IStore(BinaryPackagePublishingHistory).find(
+                *arch_specific_clauses)
+            self.logger.info("Dominating arch-specific binaries...")
+            self._dominatePublications(
+                self._sortPackages(arch_specific_bins, generalization),
+                generalization)
+
+            arch_indep_clauses = []
+            arch_indep_clauses.extend(main_clauses)
+            arch_indep_clauses.append(
+                BinaryPackageRelease.architecturespecific == False)
+            arch_indep_clauses.extend(bpph_location_clauses)
+            self.logger.info("Finding arch-indep binaries...")
+            arch_indep_bins = IStore(BinaryPackagePublishingHistory).find(
+                *arch_indep_clauses)
+            self.logger.info("Dominating arch-indep binaries...")
+            self._dominatePublications(
+                self._sortPackages(arch_indep_bins, generalization),
+                generalization)
 
     def _composeActiveSourcePubsCondition(self, distroseries, pocket):
         """Compose ORM condition for restricting relevant source pubs."""

=== modified file 'lib/lp/archivepublisher/tests/test_dominator.py'
--- lib/lp/archivepublisher/tests/test_dominator.py	2011-09-26 11:05:27 +0000
+++ lib/lp/archivepublisher/tests/test_dominator.py	2011-10-13 12:06:33 +0000
@@ -23,7 +23,9 @@
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.registry.interfaces.series import SeriesStatus
 from lp.services.log.logger import DevNullLogger
-from lp.soyuz.enums import PackagePublishingStatus
+from lp.soyuz.enums import (
+    PackagePublishingStatus,
+    )
 from lp.soyuz.interfaces.publishing import ISourcePackagePublishingHistory
 from lp.soyuz.tests.test_publishing import TestNativePublishingBase
 from lp.testing import (
@@ -169,6 +171,69 @@
             dominator._dominatePublications,
             pubs, GeneralizedPublication(True))
 
+    def test_archall_domination(self):
+        # Arch-all binaries should not be dominated when a new source
+        # version builds an updated arch-all binary, because slower builds
+        # of other architectures will leave the previous version
+        # uninstallable if they depend on the arch-all binary.
+        # See https://bugs.launchpad.net/launchpad/+bug/34086
+
+        # Set up a source, "foo" which builds "foo-bin" and foo-common
+        # (which is arch-all).
+        foo_10_src = self.getPubSource(
+            sourcename="foo", version="1.0", architecturehintlist="i386",
+            status=PackagePublishingStatus.PUBLISHED)
+        [foo_10_i386_bin] = self.getPubBinaries(
+            binaryname="foo-bin", status=PackagePublishingStatus.PUBLISHED,
+            architecturespecific=True, version="1.0", pub_source=foo_10_src)
+        [build] = foo_10_src.getBuilds()
+        bpr = self.factory.makeBinaryPackageRelease(
+            binarypackagename="foo-common", version="1.0", build=build,
+            architecturespecific=False)
+        foo_10_all_bins = self.publishBinaryInArchive(
+            bpr, self.ubuntutest.main_archive, pocket=foo_10_src.pocket,
+            status=PackagePublishingStatus.PUBLISHED)
+
+        # Now, make version 1.1 of foo and add a foo-common but not foo-bin
+        # (imagine that it's not finished building yet).
+        foo_11_src = self.getPubSource(
+            sourcename="foo", version="1.1", architecturehintlist="all",
+            status=PackagePublishingStatus.PUBLISHED)
+        foo_11_all_bins = self.getPubBinaries(
+            binaryname="foo-common", status=PackagePublishingStatus.PUBLISHED,
+            architecturespecific=False, version="1.1", pub_source=foo_11_src)
+
+        dominator = Dominator(self.logger, self.ubuntutest.main_archive)
+        dominator.judgeAndDominate(
+            foo_10_src.distroseries, foo_10_src.pocket)
+
+        # The source will be superseded.
+        self.checkPublication(foo_10_src, PackagePublishingStatus.SUPERSEDED)
+        # The arch-specific has no dominant, so it's still published
+        self.checkPublication(
+            foo_10_i386_bin, PackagePublishingStatus.PUBLISHED)
+        # The arch-indep has a dominant but must not be superseded yet
+        # since the arch-specific is still published.
+        self.checkPublications(
+            foo_10_all_bins, PackagePublishingStatus.PUBLISHED)
+
+        # Now creating a newer foo-bin should see those last two
+        # publications superseded.
+        [build2] = foo_11_src.getBuilds()
+        foo_11_bin = self.factory.makeBinaryPackageRelease(
+            binarypackagename="foo-bin", version="1.1", build=build2,
+            architecturespecific=True)
+        self.publishBinaryInArchive(
+            foo_11_bin, self.ubuntutest.main_archive,
+            pocket=foo_10_src.pocket,
+            status=PackagePublishingStatus.PUBLISHED)
+        dominator.judgeAndDominate(
+            foo_10_src.distroseries, foo_10_src.pocket)
+        self.checkPublication(
+            foo_10_i386_bin, PackagePublishingStatus.SUPERSEDED)
+        self.checkPublications(
+            foo_10_all_bins, PackagePublishingStatus.SUPERSEDED)
+
 
 class TestDomination(TestNativePublishingBase):
     """Test overall domination procedure."""

=== modified file 'lib/lp/soyuz/model/publishing.py'
--- lib/lp/soyuz/model/publishing.py	2011-09-27 07:53:02 +0000
+++ lib/lp/soyuz/model/publishing.py	2011-10-13 12:06:33 +0000
@@ -33,6 +33,7 @@
     Desc,
     LeftJoin,
     Or,
+    Select,
     Sum,
     )
 from storm.store import Store
@@ -1118,6 +1119,62 @@
                 section=self.section,
                 priority=self.priority)
 
+    def getOtherPublicationsForSameSource(self, include_archindep=False):
+        """Return all the other published or pending binaries for this
+        source.
+
+        For example if source package foo builds:
+        foo - i386
+        foo - amd64
+        foo-common - arch-all (published in i386 and amd64)
+        then if this publication is the arch-all amd64, return foo(i386),
+        foo(amd64). If include_archindep is True then also return
+        foo-common (i386)
+
+        :param include_archindep: If True, return architecture independent
+            publications too. Defaults to False.
+
+        :return: an iterable of `BinaryPackagePublishingHistory`
+        """
+        # Avoid circular wotsits.
+        from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
+        from lp.soyuz.model.distroarchseries import DistroArchSeries
+        from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
+        source_select = Select(
+            SourcePackageRelease.id,
+            And(
+                BinaryPackageBuild.source_package_release_id ==
+                    SourcePackageRelease.id,
+                BinaryPackageRelease.build == BinaryPackageBuild.id,
+                BinaryPackagePublishingHistory.binarypackagereleaseID ==
+                    BinaryPackageRelease.id,
+                BinaryPackagePublishingHistory.id == self.id,
+            ))
+        pubs = [
+            BinaryPackageBuild.source_package_release_id ==
+                SourcePackageRelease.id,
+            SourcePackageRelease.id.is_in(source_select),
+            BinaryPackageRelease.build == BinaryPackageBuild.id,
+            BinaryPackagePublishingHistory.binarypackagereleaseID ==
+                BinaryPackageRelease.id,
+            BinaryPackagePublishingHistory.archiveID == self.archive.id,
+            BinaryPackagePublishingHistory.distroarchseriesID ==
+                DistroArchSeries.id,
+            DistroArchSeries.distroseriesID == self.distroseries.id,
+            BinaryPackagePublishingHistory.pocket == self.pocket,
+            BinaryPackagePublishingHistory.status.is_in(
+                active_publishing_status),
+            BinaryPackagePublishingHistory.id != self.id
+            ]
+
+        if not include_archindep:
+            pubs.append(BinaryPackageRelease.architecturespecific == True)
+
+        return IMasterStore(BinaryPackagePublishingHistory).find(
+            BinaryPackagePublishingHistory,
+            *pubs
+            )
+
     def supersede(self, dominant=None, logger=None):
         """See `IBinaryPackagePublishingHistory`."""
         # At this point only PUBLISHED (ancient versions) or PENDING (

=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py	2011-09-23 07:47:04 +0000
+++ lib/lp/soyuz/tests/test_publishing.py	2011-10-13 12:06:33 +0000
@@ -1471,6 +1471,138 @@
         self.assertEquals(spph.ancestor.displayname, ancestor.displayname)
 
 
+class TestGetOtherPublicationsForSameSource(TestNativePublishingBase):
+    """Test parts of the BinaryPackagePublishingHistory model.
+
+    See also lib/lp/soyuz/doc/publishing.txt
+    """
+
+    layer = LaunchpadZopelessLayer
+
+    def _makeMixedSingleBuildPackage(self, version="1.0"):
+        # Set up a source with a build that generated four binaries,
+        # two of them an arch-all.
+        foo_src_pub = self.getPubSource(
+            sourcename="foo", version=version, architecturehintlist="i386",
+            status=PackagePublishingStatus.PUBLISHED)
+        [foo_bin_pub] = self.getPubBinaries(
+            binaryname="foo-bin", status=PackagePublishingStatus.PUBLISHED,
+            architecturespecific=True, version=version,
+            pub_source=foo_src_pub)
+        # Now need to grab the build for the source so we can add
+        # more binaries to it.
+        [build] = foo_src_pub.getBuilds()
+        foo_one_common = self.factory.makeBinaryPackageRelease(
+            binarypackagename="foo-one-common", version=version, build=build,
+            architecturespecific=False)
+        foo_one_common_pubs = self.publishBinaryInArchive(
+            foo_one_common, self.ubuntutest.main_archive,
+            pocket=foo_src_pub.pocket,
+            status=PackagePublishingStatus.PUBLISHED)
+        foo_two_common = self.factory.makeBinaryPackageRelease(
+            binarypackagename="foo-two-common", version=version, build=build,
+            architecturespecific=False)
+        foo_two_common_pubs = self.publishBinaryInArchive(
+            foo_two_common, self.ubuntutest.main_archive,
+            pocket=foo_src_pub.pocket,
+            status=PackagePublishingStatus.PUBLISHED)
+        foo_three = self.factory.makeBinaryPackageRelease(
+            binarypackagename="foo-three", version=version, build=build,
+            architecturespecific=True)
+        [foo_three_pub] = self.publishBinaryInArchive(
+            foo_three, self.ubuntutest.main_archive,
+            pocket=foo_src_pub.pocket,
+            status=PackagePublishingStatus.PUBLISHED)
+        # So now we have source foo, which has arch specific binaries
+        # foo-bin and foo-three, and arch:all binaries foo-one-common and
+        # foo-two-common. The latter two will have multiple publications,
+        # one for each DAS in the series.
+        return (
+            foo_src_pub, foo_bin_pub, foo_one_common_pubs,
+            foo_two_common_pubs, foo_three_pub)
+
+    def test_getOtherPublicationsForSameSource(self):
+        (foo_src_pub, foo_bin_pub, foo_one_common_pubs, foo_two_common_pubs,
+            foo_three_pub) = self._makeMixedSingleBuildPackage()
+
+        foo_one_common_pub = foo_one_common_pubs[0]
+        others = foo_one_common_pub.getOtherPublicationsForSameSource()
+        others = list(others)
+
+        self.assertEqual(2, len(others))
+        self.assertNotIn(foo_one_common_pub, others)
+        self.assertIn(foo_three_pub, others)
+        self.assertIn(foo_bin_pub, others)
+
+    def test_getOtherPublicationsForSameSource_include_archindep(self):
+        (foo_src_pub, foo_bin_pub, foo_one_common_pubs, foo_two_common_pubs,
+         foo_three_pub) = self._makeMixedSingleBuildPackage()
+
+        foo_one_common_pub = foo_one_common_pubs[0]
+        others = foo_one_common_pub.getOtherPublicationsForSameSource(
+            include_archindep=True)
+        others = list(others)
+
+        # We expect all publications created above to be returned,
+        # except the one we use to call the method on.
+        self.assertEqual(5, len(others))
+        self.assertIn(foo_three_pub, others)
+        self.assertIn(foo_bin_pub, others)
+        for pub in foo_one_common_pubs[1:] + foo_two_common_pubs:
+            self.assertIn(pub, others)
+
+    def test_getOtherPublicationsForSameSource_extra_builds(self):
+        (foo_src_pub, foo_bin_pub, foo_one_common_pubs, foo_two_common_pubs,
+         foo_three_pub) = self._makeMixedSingleBuildPackage()
+
+        # Add an additional build in a different architecture.
+        z80 = self.factory.makeDistroArchSeries(
+            distroseries=foo_src_pub.distroseries, architecturetag="z80")
+        build2 = self.factory.makeBinaryPackageBuild(
+            source_package_release=foo_src_pub.sourcepackagerelease,
+            distroarchseries=z80, archive=foo_src_pub.archive)
+        foo_one_z80 = self.factory.makeBinaryPackageRelease(
+            binarypackagename="foo-one-z80", version="1.0", build=build2,
+            architecturespecific=True)
+        [foo_one_z80_pub] = self.publishBinaryInArchive(
+            foo_one_z80, self.ubuntutest.main_archive,
+            pocket=foo_src_pub.pocket,
+            status=PackagePublishingStatus.PUBLISHED)
+
+        # The publication for the new build should be returned.
+        foo_one_common_pub = foo_one_common_pubs[0]
+        others = foo_one_common_pub.getOtherPublicationsForSameSource()
+        others = list(others)
+        self.assertIn(foo_one_z80_pub, others)
+
+    def test_getOtherPublicationsForSameSource_inactive(self):
+        # Check that inactive publications are not returned.
+        (foo_src_pub, foo_bin_pub, foo_one_common_pubs, foo_two_common_pubs,
+             foo_three_pub) = self._makeMixedSingleBuildPackage()
+        foo_bin_pub.status = PackagePublishingStatus.SUPERSEDED
+        foo_three_pub.status = PackagePublishingStatus.SUPERSEDED
+        foo_one_common_pub = foo_one_common_pubs[0]
+        others = foo_one_common_pub.getOtherPublicationsForSameSource()
+        others = list(others)
+
+        self.assertEqual(0, len(others))
+
+    def test_getOtherPublicationsForSameSource_multiple_versions(self):
+        # Check that only the right versions are returned.
+        (foo_src_pub, foo_bin_pub, foo_one_common_pubs, foo_two_common_pubs,
+         foo_three_pub) = self._makeMixedSingleBuildPackage(version="1.0")
+        self._makeMixedSingleBuildPackage(version="1.1")
+
+        foo_one_common_pub = foo_one_common_pubs[0]
+        others = foo_one_common_pub.getOtherPublicationsForSameSource()
+        others = list(others)
+
+        self.assertEqual(2, len(others))
+        self.assertNotIn(foo_one_common_pub, others)
+        self.assertIn(foo_three_pub, others)
+        self.assertIn(foo_bin_pub, others)
+
+
 class TestGetBuiltBinaries(TestNativePublishingBase):
     """Test SourcePackagePublishingHistory.getBuiltBinaries() works."""