← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stevenk/launchpad/deny-obsolete-series-uploads into lp:launchpad

 

Steve Kowalik has proposed merging lp:~stevenk/launchpad/deny-obsolete-series-uploads into lp:launchpad with lp:~stevenk/launchpad/db-add-archive-permit-obsolete as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #902836 in Launchpad itself: "Building in a PPA for obsolete release Karmic Koala isn't disabled"
  https://bugs.launchpad.net/launchpad/+bug/902836

For more details, see:
https://code.launchpad.net/~stevenk/launchpad/deny-obsolete-series-uploads/+merge/165515

Make use of the new Archive.permit_obsolete_series_uploads column by adding it to the model and rejecting any source upload or build against an obsolete series with the flag unset.
-- 
https://code.launchpad.net/~stevenk/launchpad/deny-obsolete-series-uploads/+merge/165515
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/deny-obsolete-series-uploads into lp:launchpad.
=== modified file 'lib/lp/soyuz/browser/archive.py'
--- lib/lp/soyuz/browser/archive.py	2013-05-14 08:27:03 +0000
+++ lib/lp/soyuz/browser/archive.py	2013-05-24 01:51:31 +0000
@@ -2034,6 +2034,7 @@
         'require_virtualized',
         'build_debug_symbols',
         'publish_debug_symbols',
+        'permit_obsolete_series_uploads',
         'authorized_size',
         'relative_build_score',
         'external_dependencies',

=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml	2013-05-14 08:27:03 +0000
+++ lib/lp/soyuz/configure.zcml	2013-05-24 01:51:31 +0000
@@ -426,9 +426,11 @@
         <require
             permission="launchpad.Admin"
             interface="lp.soyuz.interfaces.archive.IArchiveAdmin"
-            set_attributes="name authorized_size build_debug_symbols
+            set_attributes="authorized_size build_debug_symbols
                             buildd_secret enabled_restricted_families
-                            external_dependencies private publish_debug_symbols
+                            external_dependencies name
+                            permit_obsolete_series_uploads
+                            private publish_debug_symbols
                             require_virtualized"/>
         <require
             permission="launchpad.Moderate"

=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py	2013-05-14 08:27:03 +0000
+++ lib/lp/soyuz/interfaces/archive.py	2013-05-24 01:51:31 +0000
@@ -17,6 +17,7 @@
     'CannotUploadToArchive',
     'CannotUploadToPPA',
     'CannotUploadToPocket',
+    'CannotUploadToSeries',
     'FULL_COMPONENT_SUPPORT',
     'IArchive',
     'IArchiveAdmin',
@@ -187,7 +188,7 @@
 
     def __init__(self, **args):
         """Construct a `CannotUploadToArchive`."""
-        Exception.__init__(self, self._fmt % args)
+        super(CannotUploadToArchive, self).__init__(self._fmt % args)
 
 
 class InvalidPocketForPartnerArchive(CannotUploadToArchive):
@@ -201,7 +202,7 @@
     """Returned when a pocket is closed for uploads."""
 
     def __init__(self, distroseries, pocket):
-        Exception.__init__(self,
+        super(CannotUploadToPocket, self).__init__(
             "Not permitted to upload to the %s pocket in a series in the "
             "'%s' state." % (pocket.name, distroseries.status.name))
 
@@ -249,7 +250,7 @@
         "Signer is not permitted to upload to the component '%(component)s'.")
 
     def __init__(self, component):
-        CannotUploadToArchive.__init__(self, component=component.name)
+        super(NoRightsForComponent, self).__init__(component=component.name)
 
 
 class InvalidPocketForPPA(CannotUploadToArchive):
@@ -264,7 +265,17 @@
     _fmt = ("%(archive_name)s is disabled.")
 
     def __init__(self, archive_name):
-        CannotUploadToArchive.__init__(self, archive_name=archive_name)
+        super(ArchiveDisabled, self).__init__(archive_name=archive_name)
+
+
+class CannotUploadToSeries(CannotUploadToArchive):
+    """Uploading to an obsolete series is not allowed."""
+
+    _fmt = ("%(distroseries)s is obsolete and will not accept new uploads.")
+
+    def __init__(self, distroseries):
+        super(CannotUploadToSeries, self).__init__(
+            distroseries=distroseries.name)
 
 
 @error_status(httplib.BAD_REQUEST)
@@ -489,6 +500,11 @@
         description=_(
             "Publish debug symbol packages in the apt repository."))
 
+    permit_obsolete_series_uploads = Bool(
+        title=_("Permit uploads to obsolete series"), required=False,
+        description=_(
+            "Allow uploads targeted to obsolete series."))
+
     authorized_size = exported(
         Int(
             title=_("Authorized size"), required=False,

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py	2013-05-14 08:27:03 +0000
+++ lib/lp/soyuz/model/archive.py	2013-05-24 01:51:31 +0000
@@ -126,6 +126,7 @@
     CannotSwitchPrivacy,
     CannotUploadToPocket,
     CannotUploadToPPA,
+    CannotUploadToSeries,
     ComponentNotFound,
     default_name_by_purpose,
     FULL_COMPONENT_SUPPORT,
@@ -282,6 +283,9 @@
     publish_debug_symbols = BoolCol(
         dbName='publish_debug_symbols', notNull=False, default=False)
 
+    permit_obsolete_series_uploads = BoolCol(
+        dbName='permit_obsolete_series_uploads', notNull=True, default=False)
+
     authorized_size = IntCol(
         dbName='authorized_size', notNull=False, default=2048)
 
@@ -1286,6 +1290,13 @@
             else:
                 return None
 
+        # If the target series is OBSOLETE and permit_obsolete_series_uploads
+        # is not set, reject.
+        if (
+            distroseries.status == SeriesStatus.OBSOLETE and
+            not self.permit_obsolete_series_uploads):
+            return CannotUploadToSeries(distroseries)
+
         # Users with pocket upload permissions may upload to anything in the
         # given pocket.
         if pocket is not None and self.checkArchivePermission(person, pocket):

=== modified file 'lib/lp/soyuz/model/buildpackagejob.py'
--- lib/lp/soyuz/model/buildpackagejob.py	2013-01-30 01:41:33 +0000
+++ lib/lp/soyuz/model/buildpackagejob.py	2013-05-24 01:51:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -19,6 +19,7 @@
 from lp.buildmaster.interfaces.builder import IBuilderSet
 from lp.buildmaster.model.buildfarmjob import BuildFarmJobOld
 from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.registry.interfaces.series import SeriesStatus
 from lp.services.database.bulk import load_related
 from lp.services.database.lpstorm import IStore
 from lp.services.database.sqlbase import sqlvalues
@@ -222,17 +223,21 @@
     def postprocessCandidate(job, logger):
         """See `IBuildFarmJob`."""
         # Mark build records targeted to old source versions as SUPERSEDED
-        # and build records target to SECURITY pocket as FAILEDTOBUILD.
+        # and build records target to SECURITY pocket or against an OBSOLETE
+        # distroseries without a flag as FAILEDTOBUILD.
         # Builds in those situation should not be built because they will
         # be wasting build-time.  In the former case, there is already a
         # newer source; the latter case needs an overhaul of the way
         # security builds are handled (by copying from a PPA) to avoid
         # creating duplicate builds.
-        build_set = getUtility(IBinaryPackageBuildSet)
-
-        build = build_set.getByQueueEntry(job)
-        if build.pocket == PackagePublishingPocket.SECURITY:
-            # We never build anything in the security pocket.
+        build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job)
+        distroseries = build.distro_arch_series.distroseries
+        if (
+            build.pocket == PackagePublishingPocket.SECURITY or 
+            (distroseries.status == SeriesStatus.OBSOLETE and
+                not build.archive.permit_obsolete_series_uploads)):
+            # We never build anything in the security pocket, or for obsolete
+            # series without the flag set.
             logger.debug(
                 "Build %s FAILEDTOBUILD, queue item %s REMOVED"
                 % (build.id, job.id))

=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py	2013-05-10 05:30:11 +0000
+++ lib/lp/soyuz/tests/test_archive.py	2013-05-24 01:51:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test Archive features."""
@@ -59,6 +59,7 @@
     CannotCopy,
     CannotUploadToPocket,
     CannotUploadToPPA,
+    CannotUploadToSeries,
     IArchiveSet,
     InsufficientUploadRights,
     InvalidPocketForPartnerArchive,
@@ -783,10 +784,26 @@
         # can upload basically whatever they want to that component, even if
         # the package doesn't exist yet.
         archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
-        person, component = self.makePersonWithComponentPermission(
-            archive)
+        person, component = self.makePersonWithComponentPermission(archive)
         self.assertCanUpload(archive, person, None, component=component)
 
+    def test_checkUpload_obsolete_series(self):
+        distroseries = self.factory.makeDistroSeries(
+            status=SeriesStatus.OBSOLETE)
+        self.assertCannotUpload(
+            CannotUploadToSeries, distroseries.distribution.main_archive,
+            self.factory.makePerson(), None, distroseries=distroseries)
+
+    def test_checkUpload_obsolete_series_with_flag(self):
+        distroseries = self.factory.makeDistroSeries(
+            status=SeriesStatus.OBSOLETE)
+        archive = distroseries.distribution.main_archive
+        person, component = self.makePersonWithComponentPermission(archive)
+        removeSecurityProxy(archive).permit_obsolete_series_uploads = True
+        self.assertCanUpload(
+            archive, person, None, distroseries=distroseries,
+            component=component)
+
     def makePackageToUpload(self, distroseries):
         sourcepackagename = self.factory.makeSourcePackageName()
         return self.factory.makeSuiteSourcePackage(

=== modified file 'lib/lp/soyuz/tests/test_buildpackagejob.py'
--- lib/lp/soyuz/tests/test_buildpackagejob.py	2013-01-22 02:00:27 +0000
+++ lib/lp/soyuz/tests/test_buildpackagejob.py	2013-05-24 01:51:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test BuildQueue features."""
@@ -13,17 +13,20 @@
 from lp.buildmaster.enums import BuildStatus
 from lp.buildmaster.interfaces.builder import IBuilderSet
 from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.registry.interfaces.series import SeriesStatus
 from lp.registry.interfaces.sourcepackage import SourcePackageUrgency
 from lp.services.database.interfaces import (
     DEFAULT_FLAVOR,
     IStoreSelector,
     MAIN_STORE,
     )
+from lp.services.log.logger import DevNullLogger
 from lp.services.webapp.interfaces import OAuthPermission
 from lp.soyuz.enums import (
     ArchivePurpose,
     PackagePublishingStatus,
     )
+from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
 from lp.soyuz.interfaces.buildfarmbuildjob import IBuildFarmBuildJob
 from lp.soyuz.interfaces.buildpackagejob import (
     COPY_ARCHIVE_SCORE_PENALTY,
@@ -34,6 +37,7 @@
     SCORE_BY_URGENCY,
     )
 from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
+from lp.soyuz.model.buildpackagejob import BuildPackageJob
 from lp.soyuz.model.processor import ProcessorFamilySet
 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
 from lp.testing import (
@@ -475,3 +479,42 @@
         with anonymous_logged_in():
             self.assertScoreWriteableByTeam(
                 archive, getUtility(ILaunchpadCelebrities).commercial_admin)
+
+            
+class TestBuildPackageJobPostProcess(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def makeBuildJob(self, pocket="RELEASE"):
+        build = self.factory.makeBinaryPackageBuild(pocket=pocket)
+        return build.queueBuild()
+
+    def test_release_job(self):
+        job = self.makeBuildJob()
+        build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job)
+        self.assertTrue(BuildPackageJob.postprocessCandidate(job, None))
+        self.assertEqual(BuildStatus.NEEDSBUILD, build.status)
+
+    def test_security_job_is_failed(self):
+        job = self.makeBuildJob(pocket="SECURITY")
+        build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job)
+        BuildPackageJob.postprocessCandidate(job, DevNullLogger())
+        self.assertEqual(BuildStatus.FAILEDTOBUILD, build.status)
+
+    def test_obsolete_job_without_flag_is_failed(self):
+        job = self.makeBuildJob()
+        build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job)
+        distroseries = build.distro_arch_series.distroseries
+        removeSecurityProxy(distroseries).status = SeriesStatus.OBSOLETE
+        BuildPackageJob.postprocessCandidate(job, DevNullLogger())
+        self.assertEqual(BuildStatus.FAILEDTOBUILD, build.status)
+
+    def test_obsolete_job_with_flag_is_not_failed(self):
+        job = self.makeBuildJob()
+        build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job)
+        distroseries = build.distro_arch_series.distroseries
+        archive = build.archive
+        removeSecurityProxy(distroseries).status = SeriesStatus.OBSOLETE
+        removeSecurityProxy(archive).permit_obsolete_series_uploads = True
+        BuildPackageJob.postprocessCandidate(job, DevNullLogger())
+        self.assertEqual(BuildStatus.NEEDSBUILD, build.status)