Merge lp:~wgrant/launchpad/archive-defer-deletion into lp:launchpad

Proposed by William Grant
Status: Merged
Merged at revision: 16499
Proposed branch: lp:~wgrant/launchpad/archive-defer-deletion
Merge into: lp:launchpad
Diff against target: 324 lines (+110/-52)
7 files modified
lib/lp/archivepublisher/publishing.py (+29/-0)
lib/lp/archivepublisher/tests/test_publisher.py (+32/-0)
lib/lp/soyuz/doc/archive-deletion.txt (+9/-31)
lib/lp/soyuz/interfaces/publishing.py (+5/-3)
lib/lp/soyuz/model/archive.py (+13/-10)
lib/lp/soyuz/model/publishing.py (+16/-8)
lib/lp/soyuz/tests/test_publishing.py (+6/-0)
To merge this branch: bzr merge lp:~wgrant/launchpad/archive-defer-deletion
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+148988@code.launchpad.net

Commit message

Defer PPA publication deletion to the publisher, remove NBS binaries, and allow files to expire.

Description of the change

This branch fixes three archive deletion bugs.

Firstly, it was marking all publications deleted in the webapp request, which is obviously inappropriate as it's an unbounded number of writes. It's also racy, as the archive is disabled in the same transaction so packages may end up successfully uploading slightly after the other publications are deleted. I moved the publication deletion phase to the publisher. Since we don't have the deletion requester at that point, they're now attributed to the janitor.

Secondly, PPA deletion left BPPHs around if there was no corresponding active SPPH (usually an NBS case). I fixed this by also deleting any remaining binaries.

Thirdly, while the files were removed from disk, dateremoved wasn't being set. This prevents any package published in a deleted archive from ever being expired. I adjusted deletion to also set dateremoved in this case, since we don't need to wait for p-d-r to remove the files.

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/archivepublisher/publishing.py'
--- lib/lp/archivepublisher/publishing.py 2012-09-07 05:13:57 +0000
+++ lib/lp/archivepublisher/publishing.py 2013-02-20 02:37:30 +0000
@@ -20,7 +20,9 @@
20 _multivalued,20 _multivalued,
21 Release,21 Release,
22 )22 )
23from zope.component import getUtility
2324
25from lp.app.interfaces.launchpad import ILaunchpadCelebrities
24from lp.archivepublisher import HARDCODED_COMPONENT_ORDER26from lp.archivepublisher import HARDCODED_COMPONENT_ORDER
25from lp.archivepublisher.config import getPubConfig27from lp.archivepublisher.config import getPubConfig
26from lp.archivepublisher.diskpool import DiskPool28from lp.archivepublisher.diskpool import DiskPool
@@ -40,6 +42,7 @@
40 )42 )
41from lp.registry.interfaces.pocket import PackagePublishingPocket43from lp.registry.interfaces.pocket import PackagePublishingPocket
42from lp.registry.interfaces.series import SeriesStatus44from lp.registry.interfaces.series import SeriesStatus
45from lp.services.database.constants import UTC_NOW
43from lp.services.database.sqlbase import sqlvalues46from lp.services.database.sqlbase import sqlvalues
44from lp.services.librarian.client import LibrarianClient47from lp.services.librarian.client import LibrarianClient
45from lp.services.utils import file_exists48from lp.services.utils import file_exists
@@ -49,6 +52,10 @@
49 BinaryPackageFormat,52 BinaryPackageFormat,
50 PackagePublishingStatus,53 PackagePublishingStatus,
51 )54 )
55from lp.soyuz.interfaces.publishing import (
56 active_publishing_status,
57 IPublishingSet,
58 )
52from lp.soyuz.model.publishing import (59from lp.soyuz.model.publishing import (
53 BinaryPackagePublishingHistory,60 BinaryPackagePublishingHistory,
54 SourcePackagePublishingHistory,61 SourcePackagePublishingHistory,
@@ -747,6 +754,28 @@
747 "Attempting to delete archive '%s/%s' at '%s'." % (754 "Attempting to delete archive '%s/%s' at '%s'." % (
748 self.archive.owner.name, self.archive.name, root_dir))755 self.archive.owner.name, self.archive.name, root_dir))
749756
757 # Set all the publications to DELETED.
758 sources = self.archive.getPublishedSources(
759 status=active_publishing_status)
760 getUtility(IPublishingSet).requestDeletion(
761 sources, removed_by=getUtility(ILaunchpadCelebrities).janitor,
762 removal_comment="Removed when deleting archive")
763
764 # Deleting the sources will have killed the corresponding
765 # binaries too, but there may be orphaned leftovers (eg. NBS).
766 binaries = self.archive.getAllPublishedBinaries(
767 status=active_publishing_status)
768 getUtility(IPublishingSet).requestDeletion(
769 binaries, removed_by=getUtility(ILaunchpadCelebrities).janitor,
770 removal_comment="Removed when deleting archive")
771
772 # Now set dateremoved on any publication that doesn't already
773 # have it set, so things can expire from the librarian.
774 for pub in self.archive.getPublishedSources(include_removed=False):
775 pub.dateremoved = UTC_NOW
776 for pub in self.archive.getAllPublishedBinaries(include_removed=False):
777 pub.dateremoved = UTC_NOW
778
750 for directory in (root_dir, self._config.metaroot):779 for directory in (root_dir, self._config.metaroot):
751 if not os.path.exists(directory):780 if not os.path.exists(directory):
752 continue781 continue
753782
=== modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
--- lib/lp/archivepublisher/tests/test_publisher.py 2012-08-17 11:12:37 +0000
+++ lib/lp/archivepublisher/tests/test_publisher.py 2013-02-20 02:37:30 +0000
@@ -148,6 +148,24 @@
148 test_archive = getUtility(IArchiveSet).new(148 test_archive = getUtility(IArchiveSet).new(
149 distribution=self.ubuntutest, owner=ubuntu_team,149 distribution=self.ubuntutest, owner=ubuntu_team,
150 purpose=ArchivePurpose.PPA)150 purpose=ArchivePurpose.PPA)
151
152 # Create some source and binary publications, including an
153 # orphaned NBS binary.
154 spph = self.factory.makeSourcePackagePublishingHistory(
155 archive=test_archive)
156 bpph = self.factory.makeBinaryPackagePublishingHistory(
157 archive=test_archive)
158 orphaned_bpph = self.factory.makeBinaryPackagePublishingHistory(
159 archive=test_archive)
160 bpb = orphaned_bpph.binarypackagerelease.build
161 bpb.current_source_publication.supersede()
162 dead_spph = self.factory.makeSourcePackagePublishingHistory(
163 archive=test_archive)
164 dead_spph.supersede()
165 dead_bpph = self.factory.makeBinaryPackagePublishingHistory(
166 archive=test_archive)
167 dead_bpph.supersede()
168
151 publisher = getPublisher(test_archive, None, self.logger)169 publisher = getPublisher(test_archive, None, self.logger)
152170
153 self.assertTrue(os.path.exists(publisher._config.archiveroot))171 self.assertTrue(os.path.exists(publisher._config.archiveroot))
@@ -168,6 +186,20 @@
168 self.assertEqual(ArchiveStatus.DELETED, test_archive.status)186 self.assertEqual(ArchiveStatus.DELETED, test_archive.status)
169 self.assertEqual(False, test_archive.publish)187 self.assertEqual(False, test_archive.publish)
170188
189 # All of the archive's active publications have been marked
190 # DELETED, and dateremoved has been set early because they've
191 # already been removed from disk.
192 for pub in (spph, bpph, orphaned_bpph):
193 self.assertEqual(PackagePublishingStatus.DELETED, pub.status)
194 self.assertEqual(u'janitor', pub.removed_by.name)
195 self.assertIsNot(None, pub.dateremoved)
196
197 # The SUPERSEDED publications now have dateremoved set, even
198 # though p-d-r hasn't run over them.
199 for pub in (dead_spph, dead_bpph):
200 self.assertIs(None, pub.scheduleddeletiondate)
201 self.assertIsNot(None, pub.dateremoved)
202
171 # Trying to delete it again won't fail, in the corner case where203 # Trying to delete it again won't fail, in the corner case where
172 # some admin manually deleted the repo.204 # some admin manually deleted the repo.
173 publisher.deleteArchive()205 publisher.deleteArchive()
174206
=== modified file 'lib/lp/soyuz/doc/archive-deletion.txt'
--- lib/lp/soyuz/doc/archive-deletion.txt 2012-04-10 14:01:17 +0000
+++ lib/lp/soyuz/doc/archive-deletion.txt 2013-02-20 02:37:30 +0000
@@ -1,17 +1,13 @@
1= Deleting an archive =1= Deleting an archive =
22
3When deleting an archive, the user calls IArchive.delete(), passing in3When deleting an archive, the user calls IArchive.delete(), passing in
4the IPerson who is requesting the deletion.4the IPerson who is requesting the deletion. The archive is disabled and
55the status set to DELETING.
6All of the publishing records will be marked as DELETED, the archive is6
7disabled and the status is set to DELETING.7This status tells the publisher to then set the publications to DELETED
88and delete the repository area. Once it completes that task the status
9This status tells the publisher to then delete the repository area. Once9of the archive itself is set to DELETED.
10it completes that task it will set the status to DELETED.10
11
12 >>> from lp.soyuz.enums import ArchiveStatus
13 >>> from lp.soyuz.interfaces.archive import (
14 ... IArchiveSet, IArchive)
15 >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher11 >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
16 >>> login("admin@canonical.com")12 >>> login("admin@canonical.com")
17 >>> stp = SoyuzTestPublisher()13 >>> stp = SoyuzTestPublisher()
@@ -55,26 +51,8 @@
55 >>> ignored = login_person(archive.owner)51 >>> ignored = login_person(archive.owner)
56 >>> archive.delete(archive.owner)52 >>> archive.delete(archive.owner)
5753
58The deletion code uses a store.execute() command to speed up the operation54Now the archive is disabled and the status is DELETING to tell the
59where many records need to be updated. Therefore we need to invalidate55publisher to remove the publications and the repository:
60the cache to make Storm re-read the database objects.
61
62 >>> Store.of(archive).invalidate()
63
64Now, all the publications are DELETED, the archive is disabled and the
65status is DELETING to tell the publisher to remove the repository:
66
67 >>> publications = list(archive.getPublishedSources())
68 >>> publications.extend(list(archive.getAllPublishedBinaries()))
69 >>> for pub in publications:
70 ... print "%s, %s by %s" % (
71 ... pub.displayname, pub.status.name, pub.removed_by.name)
72 foo 666 in breezy-autotest, DELETED by archive-owner
73 foo 666 in breezy-autotest, DELETED by archive-owner
74 foo-bin1 666 in breezy-autotest i386, DELETED by archive-owner
75 foo-bin1 666 in breezy-autotest hppa, DELETED by archive-owner
76 foo-bin2 666 in breezy-autotest i386, DELETED by archive-owner
77 foo-bin2 666 in breezy-autotest hppa, DELETED by archive-owner
7856
79 >>> print archive.enabled57 >>> print archive.enabled
80 False58 False
8159
=== modified file 'lib/lp/soyuz/interfaces/publishing.py'
--- lib/lp/soyuz/interfaces/publishing.py 2013-01-31 02:02:37 +0000
+++ lib/lp/soyuz/interfaces/publishing.py 2013-02-20 02:37:30 +0000
@@ -1268,13 +1268,15 @@
1268 This is a supporting operation for a deletion request.1268 This is a supporting operation for a deletion request.
1269 """1269 """
12701270
1271 def requestDeletion(sources, removed_by, removal_comment=None):1271 def requestDeletion(pub, removed_by, removal_comment=None):
1272 """Delete the source and binary publications specified.1272 """Delete the source and binary publications specified.
12731273
1274 This method deletes the source publications passed via the first1274 This method deletes the source publications passed via the first
1275 parameter as well as their associated binary publications.1275 parameter as well as their associated binary publications, and any
1276 binary publications passed in.
12761277
1277 :param sources: list of `SourcePackagePublishingHistory` objects.1278 :param pubs: list of `SourcePackagePublishingHistory` and
1279 `BinaryPackagePublishingHistory` objects.
1278 :param removed_by: `IPerson` responsible for the removal.1280 :param removed_by: `IPerson` responsible for the removal.
1279 :param removal_comment: optional text describing the removal reason.1281 :param removal_comment: optional text describing the removal reason.
12801282
12811283
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2013-02-06 10:40:16 +0000
+++ lib/lp/soyuz/model/archive.py 2013-02-20 02:37:30 +0000
@@ -493,7 +493,8 @@
493 def getPublishedSources(self, name=None, version=None, status=None,493 def getPublishedSources(self, name=None, version=None, status=None,
494 distroseries=None, pocket=None,494 distroseries=None, pocket=None,
495 exact_match=False, created_since_date=None,495 exact_match=False, created_since_date=None,
496 eager_load=False, component_name=None):496 eager_load=False, component_name=None,
497 include_removed=True):
497 """See `IArchive`."""498 """See `IArchive`."""
498 # clauses contains literal sql expressions for things that don't work499 # clauses contains literal sql expressions for things that don't work
499 # easily in storm : this method was migrated from sqlobject but some500 # easily in storm : this method was migrated from sqlobject but some
@@ -560,6 +561,9 @@
560 SourcePackagePublishingHistory.datecreated >=561 SourcePackagePublishingHistory.datecreated >=
561 created_since_date)562 created_since_date)
562563
564 if not include_removed:
565 clauses.append(SourcePackagePublishingHistory.dateremoved == None)
566
563 store = Store.of(self)567 store = Store.of(self)
564 resultset = store.find(568 resultset = store.find(
565 SourcePackagePublishingHistory, *clauses).order_by(*orderBy)569 SourcePackagePublishingHistory, *clauses).order_by(*orderBy)
@@ -703,7 +707,7 @@
703 def _getBinaryPublishingBaseClauses(707 def _getBinaryPublishingBaseClauses(
704 self, name=None, version=None, status=None, distroarchseries=None,708 self, name=None, version=None, status=None, distroarchseries=None,
705 pocket=None, exact_match=False, created_since_date=None,709 pocket=None, exact_match=False, created_since_date=None,
706 ordered=True):710 ordered=True, include_removed=True):
707 """Base clauses and clauseTables for binary publishing queries.711 """Base clauses and clauseTables for binary publishing queries.
708712
709 Returns a list of 'clauses' (to be joined in the callsite) and713 Returns a list of 'clauses' (to be joined in the callsite) and
@@ -781,17 +785,22 @@
781 "BinaryPackagePublishingHistory.datecreated >= %s"785 "BinaryPackagePublishingHistory.datecreated >= %s"
782 % sqlvalues(created_since_date))786 % sqlvalues(created_since_date))
783787
788 if not include_removed:
789 clauses.append(
790 "BinaryPackagePublishingHistory.dateremoved IS NULL")
791
784 return clauses, clauseTables, orderBy792 return clauses, clauseTables, orderBy
785793
786 def getAllPublishedBinaries(self, name=None, version=None, status=None,794 def getAllPublishedBinaries(self, name=None, version=None, status=None,
787 distroarchseries=None, pocket=None,795 distroarchseries=None, pocket=None,
788 exact_match=False, created_since_date=None,796 exact_match=False, created_since_date=None,
789 ordered=True):797 ordered=True, include_removed=True):
790 """See `IArchive`."""798 """See `IArchive`."""
791 clauses, clauseTables, orderBy = self._getBinaryPublishingBaseClauses(799 clauses, clauseTables, orderBy = self._getBinaryPublishingBaseClauses(
792 name=name, version=version, status=status, pocket=pocket,800 name=name, version=version, status=status, pocket=pocket,
793 distroarchseries=distroarchseries, exact_match=exact_match,801 distroarchseries=distroarchseries, exact_match=exact_match,
794 created_since_date=created_since_date, ordered=ordered)802 created_since_date=created_since_date, ordered=ordered,
803 include_removed=include_removed)
795804
796 all_binaries = BinaryPackagePublishingHistory.select(805 all_binaries = BinaryPackagePublishingHistory.select(
797 ' AND '.join(clauses), clauseTables=clauseTables,806 ' AND '.join(clauses), clauseTables=clauseTables,
@@ -1971,12 +1980,6 @@
1971 ArchiveStatus.DELETING, ArchiveStatus.DELETED,1980 ArchiveStatus.DELETING, ArchiveStatus.DELETED,
1972 "This archive is already deleted.")1981 "This archive is already deleted.")
19731982
1974 # Set all the publications to DELETED.
1975 sources = self.getPublishedSources(status=active_publishing_status)
1976 getUtility(IPublishingSet).requestDeletion(
1977 sources, removed_by=deleted_by,
1978 removal_comment="Removed when deleting archive")
1979
1980 # Mark the archive's status as DELETING so the repository can be1983 # Mark the archive's status as DELETING so the repository can be
1981 # removed by the publisher.1984 # removed by the publisher.
1982 self.status = ArchiveStatus.DELETING1985 self.status = ArchiveStatus.DELETING
19831986
=== modified file 'lib/lp/soyuz/model/publishing.py'
--- lib/lp/soyuz/model/publishing.py 2013-02-06 07:25:12 +0000
+++ lib/lp/soyuz/model/publishing.py 2013-02-20 02:37:30 +0000
@@ -1992,11 +1992,18 @@
1992 removed_byID=removed_by_id,1992 removed_byID=removed_by_id,
1993 removal_comment=removal_comment)1993 removal_comment=removal_comment)
19941994
1995 def requestDeletion(self, sources, removed_by, removal_comment=None):1995 def requestDeletion(self, pubs, removed_by, removal_comment=None):
1996 """See `IPublishingSet`."""1996 """See `IPublishingSet`."""
1997 sources = list(sources)1997 pubs = list(pubs)
1998 if len(sources) == 0:1998 sources = [
1999 pub for pub in pubs
2000 if ISourcePackagePublishingHistory.providedBy(pub)]
2001 binaries = [
2002 pub for pub in pubs
2003 if IBinaryPackagePublishingHistory.providedBy(pub)]
2004 if not sources and not binaries:
1999 return2005 return
2006 assert len(sources) + len(binaries) == len(pubs)
20002007
2001 spph_ids = [spph.id for spph in sources]2008 spph_ids = [spph.id for spph in sources]
2002 self.setMultipleDeleted(2009 self.setMultipleDeleted(
@@ -2005,11 +2012,12 @@
20052012
2006 getUtility(IDistroSeriesDifferenceJobSource).createForSPPHs(sources)2013 getUtility(IDistroSeriesDifferenceJobSource).createForSPPHs(sources)
20072014
2008 # Mark binary publications deleted.2015 # Append the sources' related binaries to our condemned list,
2009 bpph_ids = [2016 # and mark them all deleted.
2010 bpph.id2017 bpph_ids = [bpph.id for bpph in binaries]
2011 for source, bpph, bin, bin_name, arch2018 bpph_ids.extend(
2012 in self.getBinaryPublicationsForSources(sources)]2019 bpph.id for source, bpph, bin, bin_name, arch
2020 in self.getBinaryPublicationsForSources(sources))
2013 if len(bpph_ids) > 0:2021 if len(bpph_ids) > 0:
2014 self.setMultipleDeleted(2022 self.setMultipleDeleted(
2015 BinaryPackagePublishingHistory, bpph_ids, removed_by,2023 BinaryPackagePublishingHistory, bpph_ids, removed_by,
20162024
=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py 2013-02-14 01:10:48 +0000
+++ lib/lp/soyuz/tests/test_publishing.py 2013-02-20 02:37:30 +0000
@@ -1184,6 +1184,12 @@
1184 [other_spph], self.factory.makePerson())1184 [other_spph], self.factory.makePerson())
1185 self.assertEqual(PackagePublishingStatus.PENDING, spph.status)1185 self.assertEqual(PackagePublishingStatus.PENDING, spph.status)
11861186
1187 def test_requestDeletion_marks_BPPHs_deleted(self):
1188 bpph = self.factory.makeBinaryPackagePublishingHistory()
1189 getUtility(IPublishingSet).requestDeletion(
1190 [bpph], self.factory.makePerson())
1191 self.assertEqual(PackagePublishingStatus.DELETED, bpph.status)
1192
1187 def test_requestDeletion_marks_attached_BPPHs_deleted(self):1193 def test_requestDeletion_marks_attached_BPPHs_deleted(self):
1188 bpph = self.factory.makeBinaryPackagePublishingHistory()1194 bpph = self.factory.makeBinaryPackagePublishingHistory()
1189 spph = self.factory.makeSPPHForBPPH(bpph)1195 spph = self.factory.makeSPPHForBPPH(bpph)