← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/nu-overrides-refactor into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/nu-overrides-refactor into lp:launchpad.

Commit message:
Rewrite NascentUpload.find_and_apply_overrides to make its decisions more obvious, easing porting to lp.soyuz.adapters.overrides.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/nu-overrides-refactor/+merge/226818

This branch significantly reworks almost all of the the code under NascentUpload.find_and_apply_overrides, in preparation for replacing it with an evolved lp.soyuz.adapters.overrides.

The per-ArchivePurpose differences are now isolated in a single spot, and the cross-pocket and cross-archive lookup possibilities are much more explicit. There is some duplication between the source and binary paths, but that will disappear as part of the lp.soyuz.adapters.overrides port.

I don't believe there are any behaviour changes here, just a lot of refactoring. This also removes the last callsite of DistroArchSeries.getReleasedPackages, which I'll kill off in a later branch.
-- 
https://code.launchpad.net/~wgrant/launchpad/nu-overrides-refactor/+merge/226818
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/nu-overrides-refactor into lp:launchpad.
=== modified file 'lib/lp/archiveuploader/nascentupload.py'
--- lib/lp/archiveuploader/nascentupload.py	2013-07-25 12:39:54 +0000
+++ lib/lp/archiveuploader/nascentupload.py	2014-07-15 13:01:26 +0000
@@ -44,8 +44,12 @@
 from lp.registry.interfaces.sourcepackage import SourcePackageFileType
 from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
 from lp.services.librarian.interfaces import ILibraryFileAliasSet
-from lp.soyuz.adapters.overrides import UnknownOverridePolicy
-from lp.soyuz.interfaces.archive import MAIN_ARCHIVE_PURPOSES
+from lp.soyuz.adapters.overrides import (
+    BinaryOverride,
+    SourceOverride,
+    UnknownOverridePolicy,
+    )
+from lp.soyuz.enums import PackagePublishingStatus
 from lp.soyuz.interfaces.queue import QueueInconsistentStateError
 
 
@@ -181,7 +185,9 @@
         self.find_and_apply_overrides()
         self._overrideDDEBSs()
 
-        # Override archive location if necessary.
+        # Override archive location if necessary to cope with partner
+        # uploads to a primary path. Yes, we actually potentially change
+        # the target archive based on the override component.
         self.overrideArchive()
 
         # Check upload rights for the signer of the upload.
@@ -378,10 +384,9 @@
         lockstep.
         """
         for uploaded_file in self.changes.files:
-            if isinstance(uploaded_file, DdebBinaryUploadFile):
-                if uploaded_file.deb_file is not None:
-                    self._overrideBinaryFile(uploaded_file,
-                                             uploaded_file.deb_file)
+            if (isinstance(uploaded_file, DdebBinaryUploadFile)
+                    and uploaded_file.deb_file is not None):
+                self.overrideBinaryFile(uploaded_file, uploaded_file.deb_file)
     #
     # Helpers for warnings and rejections
     #
@@ -525,61 +530,28 @@
     #
     # Handling checking of versions and overrides
     #
-    def getSourceAncestry(self, uploaded_file):
+    def getSourceAncestry(self, uploaded_file, archives, lookup_pockets):
         """Return the last published source (ancestry) for a given file.
 
         Return the most recent ISPPH instance matching the uploaded file
         package name or None.
         """
-        # Only lookup uploads ancestries in target pocket and fallback
-        # to RELEASE pocket
-        # Upload ancestries found here will guide the auto-override
-        # procedure and the version consistency check:
-        #
-        #  * uploaded_version > ancestry_version
-        #
-        # which is the *only right* check we can do automatically.
-        # Post-release history and proposed content may diverge and can't
-        # be properly automatically overridden.
-        #
-        # We are relaxing version constraints when processing uploads since
-        # there are many corner cases when checking version consistency
-        # against post-release pockets, like:
-        #
-        #  * SECURITY/UPDATES can be lower than PROPOSED/BACKPORTS
-        #  * UPDATES can be lower than SECURITY
-        #  * ...
-        #
-        # And they really depends more on the package contents than the
-        # version number itself.
-        # Version inconsistencies will (should) be identified during the
-        # mandatory review in queue, anyway.
-        # See bug #83976
-        source_name = getUtility(
-            ISourcePackageNameSet).queryByName(uploaded_file.package)
-
-        if source_name is None:
-            return None
-
-        lookup_pockets = [self.policy.pocket, PackagePublishingPocket.RELEASE]
-
         for pocket in lookup_pockets:
-
-            if self.is_ppa:
-                archive = self.policy.archive
-            else:
-                archive = None
-            candidates = self.policy.distroseries.getPublishedSources(
-                source_name, include_pending=True, pocket=pocket,
-                archive=archive)
-            try:
-                return candidates[0]
-            except IndexError:
-                pass
-
+            for archive in archives:
+                candidates = archive.getPublishedSources(
+                    name=uploaded_file.package, exact_match=True,
+                    distroseries=self.policy.distroseries,
+                    pocket=lookup_pockets,
+                    status=(
+                        PackagePublishingStatus.PUBLISHED,
+                        PackagePublishingStatus.PENDING))
+                try:
+                    return candidates[0]
+                except IndexError:
+                    pass
         return None
 
-    def getBinaryAncestry(self, uploaded_file, try_other_archs=True):
+    def getBinaryAncestry(self, uploaded_file, archives, lookup_pockets):
         """Return the last published binary (ancestry) for given file.
 
         Return the most recent IBPPH instance matching the uploaded file
@@ -598,15 +570,6 @@
         else:
             ancestry_name = uploaded_file.package
 
-        # Avoid cyclic import.
-        from lp.soyuz.interfaces.binarypackagename import (
-            IBinaryPackageNameSet)
-        binary_name = getUtility(
-            IBinaryPackageNameSet).queryByName(ancestry_name)
-
-        if binary_name is None:
-            return None
-
         if uploaded_file.architecture == "all":
             arch_indep = self.policy.distroseries.nominatedarchindep
             archtag = arch_indep.architecturetag
@@ -618,40 +581,19 @@
         # But it should be refactored ASAP.
         dar = self.policy.distroseries[archtag]
 
-        # See the comment below, in getSourceAncestry
-        lookup_pockets = [self.policy.pocket, PackagePublishingPocket.RELEASE]
-
-        # If the archive is a main archive or a copy archive, we want to
-        # look up the ancestry in all the main archives.
-        if (self.policy.archive.purpose not in MAIN_ARCHIVE_PURPOSES and
-            not self.policy.archive.is_copy):
-            archive = self.policy.archive
-        else:
-            archive = None
-
-        for pocket in lookup_pockets:
-            candidates = dar.getReleasedPackages(
-                binary_name, include_pending=True, pocket=pocket,
-                archive=archive)
-
-            if candidates:
-                return candidates[0]
-
-            if not try_other_archs:
-                continue
-
-            # Try the other architectures...
-            dars = self.policy.distroseries.architectures
-            other_dars = [other_dar for other_dar in dars
-                          if other_dar.id != dar.id]
-            for other_dar in other_dars:
-                candidates = other_dar.getReleasedPackages(
-                    binary_name, include_pending=True, pocket=pocket,
-                    archive=archive)
-
-                if candidates:
-                    return candidates[0]
-        return None
+        for (arch, check_version) in (
+                (dar, True), (self.policy.distroseries.architectures, False)):
+            for archive in archives:
+                for pocket in lookup_pockets:
+                    candidates = archive.getAllPublishedBinaries(
+                        name=ancestry_name, exact_match=True,
+                        distroarchseries=arch, pocket=pocket,
+                        status=(
+                            PackagePublishingStatus.PUBLISHED,
+                            PackagePublishingStatus.PENDING))
+                    if candidates:
+                        return (candidates[0], check_version)
+        return None, None
 
     def _checkVersion(self, proposed_version, archive_version, filename):
         """Check if the proposed version is higher than the one in archive."""
@@ -686,79 +628,30 @@
         filename = uploaded_file.filename
         self._checkVersion(proposed_version, archive_version, filename)
 
-    def overrideSource(self, uploaded_file, override):
+    def overrideSourceFile(self, uploaded_file, override):
         """Overrides the uploaded source based on its override information.
 
         Override target component and section.
         """
-        if self.is_ppa:
-            # There are no overrides for PPAs.
-            return
-
-        self.logger.debug("%s (source) exists in %s" % (
-            override.sourcepackagerelease.title,
-            override.pocket.name))
-
-        uploaded_file.component_name = override.component.name
-        uploaded_file.section_name = override.section.name
-
-    def overrideBinary(self, uploaded_file, override):
+        if override.component is not None:
+            uploaded_file.component_name = override.component.name
+        if override.section is not None:
+            uploaded_file.section_name = override.section.name
+
+    def overrideBinaryFile(self, uploaded_file, override):
         """Overrides the uploaded binary based on its override information.
 
         Override target component, section and priority.
         """
-        if self.is_ppa:
-            # There are no overrides for PPAs.
-            return
-
-        self.logger.debug("%s (binary) exists in %s/%s" % (
-            override.binarypackagerelease.title,
-            override.distroarchseries.architecturetag,
-            override.pocket.name))
-        self._overrideBinaryFile(uploaded_file, override)
-
-    def _overrideBinaryFile(self, uploaded_file, override):
-        uploaded_file.component_name = override.component.name
-        uploaded_file.section_name = override.section.name
+        if override.component is not None:
+            uploaded_file.component_name = override.component.name
+        if override.section is not None:
+            uploaded_file.section_name = override.section.name
         # Both, changesfiles and nascentuploadfile local maps, reffer to
         # priority in lower-case names, but the DBSCHEMA name is upper-case.
         # That's why we need this conversion here.
-        uploaded_file.priority_name = override.priority.name.lower()
-
-    def processUnknownFile(self, uploaded_file, override=None):
-        """Apply a set of actions for newly-uploaded (unknown) files.
-
-        Here we use the override, if specified, or simply default to the policy
-        defined in UnknownOverridePolicy.
-
-        In the case of a PPA, files are not touched.  They are always
-        overridden to 'main' at publishing time, though.
-
-        All files are also marked as new unless it's a PPA file, which are
-        never considered new as they are auto-accepted.
-
-        COPY archive build uploads are also auto-accepted, otherwise they
-        would sit in the NEW queue since it's likely there's no ancestry.
-        """
-        if self.is_ppa or self.policy.archive.is_copy:
-            return
-
-        # All newly-uploaded, non-PPA files must be marked as new so that
-        # the upload goes to the correct queue.  PPA uploads are always
-        # auto-accepted so they are never new.
-        uploaded_file.new = True
-
-        if self.is_partner:
-            # Don't override partner uploads.
-            return
-
-        # Use the specified override, or delegate to UnknownOverridePolicy.
-        if override:
-            uploaded_file.component_name = override.component.name
-            return
-        component_name_override = UnknownOverridePolicy.getComponentOverride(
-            uploaded_file.component_name)
-        uploaded_file.component_name = component_name_override
+        if override.priority is not None:
+            uploaded_file.priority_name = override.priority.name.lower()
 
     def find_and_apply_overrides(self):
         """Look for ancestry and overrides information.
@@ -771,27 +664,74 @@
         """
         self.logger.debug("Finding and applying overrides.")
 
+        # Fall back to just the RELEASE pocket if there is no ancestry
+        # in the given pocket. The relationships are more complicated in
+        # reality, but versions can diverge between post-release pockets
+        # so we can't automatically check beyond this (eg. bug #83976).
+        lookup_pockets = [self.policy.pocket]
+        if PackagePublishingPocket.RELEASE not in lookup_pockets:
+            lookup_pockets.append(PackagePublishingPocket.RELEASE)
+
+        archives = [self.policy.archive]
+        foreign_archive = False
+        use_default_component = True
+        autoaccept_new = False
+        override_at_all = True
+        if self.policy.archive.is_primary:
+            # overrideArchive can switch to the partner archive if there
+            # is ancestry there, so try partner after primary.
+            partner = self.policy.archive.distribution.getArchiveByComponent(
+                PARTNER_COMPONENT_NAME)
+            if partner is not None:
+                archives.append(partner)
+        elif self.policy.archive.is_copy:
+            # Copy archives always inherit their overrides from the
+            # primary archive. We don't want to perform the version
+            # check in this case, as the rebuild may finish after a new
+            # version exists in the primary archive.
+            archives = [self.policy.archive.distribution.main_archive]
+            foreign_archive = True
+            # XXX wgrant 2014-07-14 bug=1103491: This causes new binaries in
+            # copy archives to stay in contrib/non-free, so the upload gets
+            # rejected. But I'm just preserving existing behaviour for now.
+            use_default_component = False
+            autoaccept_new = True
+        elif self.policy.archive.is_ppa:
+            autoaccept_new = True
+            override_at_all = False
+
+        # NascentUpload.is_partner additionally checks if any of the
+        # components are partner.
+        if (self.policy.archive.is_partner or
+                (self.policy.archive.is_primary and self.is_partner)):
+            use_default_component = False
+
         for uploaded_file in self.changes.files:
             if isinstance(uploaded_file, DSCFile):
                 self.logger.debug(
                     "Checking for %s/%s source ancestry"
                     % (uploaded_file.package, uploaded_file.version))
-                ancestry = self.getSourceAncestry(uploaded_file)
+                ancestry = self.getSourceAncestry(
+                    uploaded_file, archives, lookup_pockets)
                 if ancestry is not None:
+                    self.logger.debug("%s (source) exists in %s" % (
+                        ancestry.sourcepackagerelease.title,
+                        ancestry.pocket.name))
                     self.checkSourceVersion(uploaded_file, ancestry)
-                    # XXX cprov 2007-02-12: The current override mechanism is
-                    # broken, since it modifies original contents of SPR/BPR.
-                    # We could do better by having a specific override table
-                    # that relates a SPN/BPN to a specific DR/DAR and carries
-                    # the respective information to be overridden.
-                    self.overrideSource(uploaded_file, ancestry)
-                    uploaded_file.new = False
                 else:
-                    # If the source is new, then apply default overrides.
-                    self.logger.debug(
-                        "%s: (source) NEW" % (uploaded_file.package))
-                    self.processUnknownFile(uploaded_file)
-
+                    if not autoaccept_new:
+                        self.logger.debug(
+                            "%s: (source) NEW", uploaded_file.package)
+                        uploaded_file.new = True
+                    if use_default_component:
+                        ancestry = SourceOverride(
+                            None,
+                            UnknownOverridePolicy.getComponentOverride(
+                                uploaded_file.component_name,
+                                return_component=True),
+                            None)
+                if override_at_all and ancestry is not None:
+                    self.overrideSourceFile(uploaded_file, ancestry)
             elif isinstance(uploaded_file, BaseBinaryUploadFile):
                 self.logger.debug(
                     "Checking for %s/%s/%s binary ancestry"
@@ -800,44 +740,42 @@
                         uploaded_file.version,
                         uploaded_file.architecture,
                         ))
-                try:
-                    ancestry = self.getBinaryAncestry(uploaded_file)
-                except NotFoundError:
-                    self.reject("%s: Unable to find arch: %s"
-                                % (uploaded_file.package,
-                                   uploaded_file.architecture))
-                    ancestry = None
+                # Find the best ancestor publication for this binary. If
+                # it's from this archive and architecture we also want
+                # to make sure the version isn't going backwards.
+                ancestry, check_version = self.getBinaryAncestry(
+                    uploaded_file, archives, lookup_pockets)
                 if ancestry is not None:
-                    # XXX cprov 2007-02-12: see above.
-                    self.overrideBinary(uploaded_file, ancestry)
-                    uploaded_file.new = False
-                    # XXX cprov 2007-03-05 bug=89846:
-                    # For binary versions verification we should only
-                    # use ancestries in the same architecture. If none
-                    # was found we can go w/o any checks, since it's
-                    # a NEW binary in this architecture, any version is
-                    # fine.
-                    ancestry = self.getBinaryAncestry(
-                        uploaded_file, try_other_archs=False)
-                    if (ancestry is not None and
-                        not self.policy.archive.is_copy):
-                        # Ignore version checks for copy archives
-                        # because the ancestry comes from the primary
-                        # which may have changed since the copy.
+                    self.logger.debug("%s (binary) exists in %s/%s" % (
+                        ancestry.binarypackagerelease.title,
+                        ancestry.distroarchseries.architecturetag,
+                        ancestry.pocket.name))
+                    if check_version and not foreign_archive:
                         self.checkBinaryVersion(uploaded_file, ancestry)
                 else:
-                    self.logger.debug(
-                        "%s: (binary) NEW" % (uploaded_file.package))
+                    if not autoaccept_new:
+                        self.logger.debug(
+                            "%s: (binary) NEW", uploaded_file.package)
+                        uploaded_file.new = True
                     # Check the current source publication's component.
                     # If there is a corresponding source publication, we will
                     # use the component from that, otherwise default mappings
                     # are used.
-                    spph = None
                     try:
                         spph = uploaded_file.findCurrentSourcePublication()
+                        ancestry = BinaryOverride(
+                            None, None, spph.component, None, None, None)
                     except UploadError:
                         pass
-                    self.processUnknownFile(uploaded_file, spph)
+                if ancestry is None and use_default_component:
+                    ancestry = BinaryOverride(
+                        None, None,
+                        UnknownOverridePolicy.getComponentOverride(
+                            uploaded_file.component_name,
+                            return_component=True),
+                        None, None, None)
+                if override_at_all and ancestry is not None:
+                    self.overrideBinaryFile(uploaded_file, ancestry)
 
     #
     # Actually processing accepted or rejected uploads -- and mailing people

=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt	2014-07-03 00:47:45 +0000
+++ lib/lp/soyuz/doc/archive.txt	2014-07-15 13:01:26 +0000
@@ -47,12 +47,16 @@
     True
     >>> cprov_archive.is_main
     False
+    >>> cprov_archive.is_primary
+    False
     >>> cprov_archive.is_partner
     False
     >>> cprov_archive.is_active
     True
     >>> cprov_archive.distribution.main_archive.is_main
     True
+    >>> cprov_archive.distribution.main_archive.is_primary
+    True
     >>> cprov_archive.total_count
     4
     >>> cprov_archive.pending_count
@@ -1012,6 +1016,10 @@
     partner
     >>> print partner_archive.is_partner
     True
+    >>> print partner_archive.is_primary
+    False
+    >>> print partner_archive.is_main
+    True
 
 It explicitly fails when purpose is PPA, since such lookup should be
 restricted by archive owner.

=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py	2014-07-08 09:30:41 +0000
+++ lib/lp/soyuz/interfaces/archive.py	2014-07-15 13:01:26 +0000
@@ -343,8 +343,14 @@
                 "subscribers. This can only be changed if the archive has "
                 "never had any sources published.")))
 
+    is_primary = Attribute("True if this archive is a primary archive.")
+
     is_ppa = Attribute("True if this archive is a PPA.")
 
+    is_partner = Attribute("True if this archive is a partner archive.")
+
+    is_copy = Attribute("True if this archive is a copy archive.")
+
     is_main = Bool(
         title=_("True if archive is a main archive type"), required=False)
 
@@ -390,7 +396,6 @@
     is_active = Bool(
         title=_("True if the archive is in the active state"),
         required=False, readonly=True)
-    is_copy = Attribute("True if this archive is a copy archive.")
     num_pkgs_building = Attribute(
         "Tuple of packages building and waiting to build")
     publish = Bool(
@@ -548,8 +553,6 @@
             "The default component for this archive. Publications without a "
             "valid component will be assigned this one."))
 
-    is_partner = Attribute("True if this archive is a partner archive.")
-
     number_of_sources = Attribute(
         'The number of sources published in the context archive.')
     number_of_binaries = Attribute(

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py	2014-07-08 09:30:41 +0000
+++ lib/lp/soyuz/model/archive.py	2014-07-15 13:01:26 +0000
@@ -384,6 +384,11 @@
         return self.purpose == ArchivePurpose.PPA
 
     @property
+    def is_primary(self):
+        """See `IArchive`."""
+        return self.purpose == ArchivePurpose.PRIMARY
+
+    @property
     def is_partner(self):
         """See `IArchive`."""
         return self.purpose == ArchivePurpose.PARTNER


Follow ups