Merge lp:~julian-edwards/launchpad/ppa-expire-sources into lp:launchpad

Proposed by Julian Edwards
Status: Merged
Approved by: Brad Crittenden
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~julian-edwards/launchpad/ppa-expire-sources
Merge into: lp:launchpad
Diff against target: 316 lines (+131/-36)
4 files modified
cronscripts/expire-ppa-files.py (+1/-1)
database/schema/security.cfg (+3/-0)
lib/lp/soyuz/scripts/expire_ppa_binaries.py (+57/-2)
lib/lp/soyuz/scripts/tests/test_expire_ppa_bins.py (+70/-33)
To merge this branch: bzr merge lp:~julian-edwards/launchpad/ppa-expire-sources
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+17669@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

= Summary =
Expire superseded PPA source files

== Proposed fix ==
This change extends the existing script that expires PPA binaries so that it
expires sources as well.

== Pre-implementation notes ==
None, I suck. Besides, it's trivial.

== Implementation details ==
Fairly straightforward extension of the existing script so that it injects
librarian IDs for sources into the loop that was deleting just the binaries.

My only question to you, my reviewer, is to ask if you can come up with a
decent refactoring of the two nearly identical SQL strings. I tried a bunch
of things but only succeeded in making it totally unreadable.

Finally, the script name is now slightly bogus, but that can be fixed some
other time. The LOSAs are desperate for more librarian space.

== Tests ==
bin/test -vvct test_expire_ppa_bins

== Demo and Q/A ==

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  database/schema/security.cfg
  lib/lp/soyuz/scripts/tests/test_expire_ppa_bins.py
  lib/lp/soyuz/scripts/expire_ppa_binaries.py

Revision history for this message
Brad Crittenden (bac) wrote :

Hi Julian,

The branch looks good. You mentioned renaming the file but deferred due to time constraints. Seems like it would take 10 seconds so you might as well do it now. expire_ppa_resources.py ?

As far as refactoring the SQL it looks like you could use common aliases and then many of your comparisons would be the same, leaving you to just customize the WHERE clauses. Would that help? Or, if it was Storm-i-fied you may find it easier to refactor. Just two lame suggestions.

Otherwise your change looks very well done. Thanks.

review: Approve (code)
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Tuesday 19 January 2010 19:30:24 Brad Crittenden wrote:
> Review: Approve code
> Hi Julian,
>
> The branch looks good. You mentioned renaming the file but deferred due to
> time constraints. Seems like it would take 10 seconds so you might as
> well do it now. expire_ppa_resources.py ?

I would have done that if it were not for the fact that the losas also need to
change their crontabs, which makes it a >10 second job :) But still, we'll
take the hit and do it. Thanks for prodding me.

> As far as refactoring the SQL it looks like you could use common aliases
> and then many of your comparisons would be the same, leaving you to just
> customize the WHERE clauses. Would that help?

I tried. It was really unreadable and the refactoring didn't help reduce the
size of the code that much.

> Or, if it was Storm-i-fied
> you may find it easier to refactor. Just two lame suggestions.

I'd love to do that but it's quite hard, STORM doesn't have the EXCEPT
command.

> Otherwise your change looks very well done. Thanks.

Thanks, I'll land this soon!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== renamed file 'cronscripts/expire-ppa-binaries.py' => 'cronscripts/expire-ppa-files.py'
--- cronscripts/expire-ppa-binaries.py 2009-10-13 14:38:07 +0000
+++ cronscripts/expire-ppa-files.py 2010-01-20 10:35:29 +0000
@@ -17,6 +17,6 @@
1717
18if __name__ == '__main__':18if __name__ == '__main__':
19 script = PPABinaryExpirer(19 script = PPABinaryExpirer(
20 'expire-ppa-binaries', dbuser=config.binaryfile_expire.dbuser)20 'expire-ppa-files', dbuser=config.binaryfile_expire.dbuser)
21 script.lock_and_run()21 script.lock_and_run()
2222
2323
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2010-01-06 09:06:56 +0000
+++ database/schema/security.cfg 2010-01-20 10:35:29 +0000
@@ -1612,6 +1612,9 @@
1612public.person = SELECT1612public.person = SELECT
1613public.libraryfilealias = SELECT, UPDATE1613public.libraryfilealias = SELECT, UPDATE
1614public.securebinarypackagepublishinghistory = SELECT1614public.securebinarypackagepublishinghistory = SELECT
1615public.sourcepackagereleasefile = SELECT
1616public.sourcepackagepublishinghistory = SELECT
1617public.sourcepackagerelease = SELECT
16151618
1616[create-merge-proposals]1619[create-merge-proposals]
1617type=user1620type=user
16181621
=== modified file 'lib/lp/soyuz/scripts/expire_ppa_binaries.py'
--- lib/lp/soyuz/scripts/expire_ppa_binaries.py 2009-12-11 14:35:28 +0000
+++ lib/lp/soyuz/scripts/expire_ppa_binaries.py 2010-01-20 10:35:29 +0000
@@ -54,7 +54,61 @@
54 help=("The number of days after which to expire binaries. "54 help=("The number of days after which to expire binaries. "
55 "Must be specified."))55 "Must be specified."))
5656
57 def determineExpirables(self, num_days):57 def determineSourceExpirables(self, num_days):
58 """Return expirable libraryfilealias IDs."""
59 # Avoid circular imports.
60 from lp.soyuz.interfaces.archive import ArchivePurpose
61
62 stay_of_execution = '%d days' % num_days
63
64 # The subquery here has to repeat the checks for privacy and
65 # blacklisting on *other* publications that are also done in
66 # the main loop for the archive being considered.
67 results = self.store.execute("""
68 SELECT lfa.id
69 FROM
70 LibraryFileAlias AS lfa,
71 Archive,
72 SourcePackageReleaseFile AS sprf,
73 SourcePackageRelease AS spr,
74 SourcePackagePublishingHistory AS spph
75 WHERE
76 lfa.id = sprf.libraryfile
77 AND spr.id = sprf.sourcepackagerelease
78 AND spph.sourcepackagerelease = spr.id
79 AND spph.dateremoved < (
80 CURRENT_TIMESTAMP AT TIME ZONE 'UTC' - interval %s)
81 AND spph.archive = archive.id
82 AND archive.purpose = %s
83 AND lfa.expires IS NULL
84 EXCEPT
85 SELECT sprf.libraryfile
86 FROM
87 SourcePackageRelease AS spr,
88 SourcePackageReleaseFile AS sprf,
89 SourcePackagePublishingHistory AS spph,
90 Archive AS a,
91 Person AS p
92 WHERE
93 spr.id = sprf.sourcepackagerelease
94 AND spph.sourcepackagerelease = spr.id
95 AND spph.archive = a.id
96 AND p.id = a.owner
97 AND (
98 p.name IN %s
99 OR a.private IS TRUE
100 OR a.purpose != %s
101 OR dateremoved >
102 CURRENT_TIMESTAMP AT TIME ZONE 'UTC' - interval %s
103 OR dateremoved IS NULL);
104 """ % sqlvalues(
105 stay_of_execution, ArchivePurpose.PPA, self.blacklist,
106 ArchivePurpose.PPA, stay_of_execution))
107
108 lfa_ids = results.get_all()
109 return lfa_ids
110
111 def determineBinaryExpirables(self, num_days):
58 """Return expirable libraryfilealias IDs."""112 """Return expirable libraryfilealias IDs."""
59 # Avoid circular imports.113 # Avoid circular imports.
60 from lp.soyuz.interfaces.archive import ArchivePurpose114 from lp.soyuz.interfaces.archive import ArchivePurpose
@@ -116,7 +170,8 @@
116 self.store = getUtility(IStoreSelector).get(170 self.store = getUtility(IStoreSelector).get(
117 MAIN_STORE, DEFAULT_FLAVOR)171 MAIN_STORE, DEFAULT_FLAVOR)
118172
119 lfa_ids = self.determineExpirables(num_days)173 lfa_ids = self.determineSourceExpirables(num_days)
174 lfa_ids.extend(self.determineBinaryExpirables(num_days))
120 batch_count = 0175 batch_count = 0
121 batch_limit = 500176 batch_limit = 500
122 for id in lfa_ids:177 for id in lfa_ids:
123178
=== modified file 'lib/lp/soyuz/scripts/tests/test_expire_ppa_bins.py'
--- lib/lp/soyuz/scripts/tests/test_expire_ppa_bins.py 2009-12-11 14:35:28 +0000
+++ lib/lp/soyuz/scripts/tests/test_expire_ppa_bins.py 2010-01-20 10:35:29 +0000
@@ -74,54 +74,76 @@
74 self.layer.switchDbUser(self.dbuser)74 self.layer.switchDbUser(self.dbuser)
75 script.main()75 script.main()
7676
77 def assertExpired(self, publication):77 def assertBinaryExpired(self, publication):
78 self.assertNotEqual(78 self.assertNotEqual(
79 publication.binarypackagerelease.files[0].libraryfile.expires,79 publication.binarypackagerelease.files[0].libraryfile.expires,
80 None,80 None,
81 "lfa.expires should be set, but it's not.")81 "lfa.expires should be set, but it's not.")
8282
83 def assertNotExpired(self, publication):83 def assertBinaryNotExpired(self, publication):
84 self.assertEqual(84 self.assertEqual(
85 publication.binarypackagerelease.files[0].libraryfile.expires,85 publication.binarypackagerelease.files[0].libraryfile.expires,
86 None,
87 "lfa.expires should be None, but it's not.")
88
89 def assertSourceExpired(self, publication):
90 self.assertNotEqual(
91 publication.sourcepackagerelease.files[0].libraryfile.expires,
92 None,
93 "lfa.expires should be set, but it's not.")
94
95 def assertSourceNotExpired(self, publication):
96 self.assertEqual(
97 publication.sourcepackagerelease.files[0].libraryfile.expires,
86 None,98 None,
87 "lfa.expires should be None, but it's not.")99 "lfa.expires should be None, but it's not.")
88100
89 def testNoExpirationWithNoDateremoved(self):101 def testNoExpirationWithNoDateremoved(self):
90 """Test that no expiring happens if no dateremoved set."""102 """Test that no expiring happens if no dateremoved set."""
91 pkg1 = self.stp.getPubSource(103 pkg1 = self.stp.getPubSource(
92 sourcename="pkg1", architecturehintlist="i386", archive=self.ppa)104 sourcename="pkg1", architecturehintlist="i386", archive=self.ppa,
105 dateremoved=None)
93 [pub] = self.stp.getPubBinaries(106 [pub] = self.stp.getPubBinaries(
94 pub_source=pkg1, dateremoved=None, archive=self.ppa)107 pub_source=pkg1, dateremoved=None, archive=self.ppa)
95108
96 self.runScript()109 self.runScript()
97 self.assertNotExpired(pub)110 self.assertSourceNotExpired(pkg1)
111 self.assertBinaryNotExpired(pub)
98112
99 def testNoExpirationWithDateUnderThreshold(self):113 def testNoExpirationWithDateUnderThreshold(self):
100 """Test no expiring if dateremoved too recent."""114 """Test no expiring if dateremoved too recent."""
101 pkg2 = self.stp.getPubSource(115 pkg2 = self.stp.getPubSource(
102 sourcename="pkg2", architecturehintlist="i386", archive=self.ppa)116 sourcename="pkg2", architecturehintlist="i386", archive=self.ppa,
117 dateremoved=self.under_threshold_date)
103 [pub] = self.stp.getPubBinaries(118 [pub] = self.stp.getPubBinaries(
104 pub_source=pkg2, dateremoved=self.under_threshold_date,119 pub_source=pkg2, dateremoved=self.under_threshold_date,
105 archive=self.ppa)120 archive=self.ppa)
106121
107 self.runScript()122 self.runScript()
108 self.assertNotExpired(pub)123 self.assertSourceNotExpired(pkg2)
124 self.assertBinaryNotExpired(pub)
109125
110 def testExpirationWithDateOverThreshold(self):126 def testExpirationWithDateOverThreshold(self):
111 """Test expiring works if dateremoved old enough."""127 """Test expiring works if dateremoved old enough."""
112 pkg3 = self.stp.getPubSource(128 pkg3 = self.stp.getPubSource(
113 sourcename="pkg3", architecturehintlist="i386", archive=self.ppa)129 sourcename="pkg3", architecturehintlist="i386", archive=self.ppa,
130 dateremoved=self.over_threshold_date)
114 [pub] = self.stp.getPubBinaries(131 [pub] = self.stp.getPubBinaries(
115 pub_source=pkg3, dateremoved=self.over_threshold_date,132 pub_source=pkg3, dateremoved=self.over_threshold_date,
116 archive=self.ppa)133 archive=self.ppa)
117134
118 self.runScript()135 self.runScript()
119 self.assertExpired(pub)136 self.assertSourceExpired(pkg3)
137 self.assertBinaryExpired(pub)
120138
121 def testNoExpirationWithDateOverThresholdAndOtherValidPublication(self):139 def testNoExpirationWithDateOverThresholdAndOtherValidPublication(self):
122 """Test no expiry if dateremoved old enough but other publication."""140 """Test no expiry if dateremoved old enough but other publication."""
123 pkg4 = self.stp.getPubSource(141 pkg4 = self.stp.getPubSource(
124 sourcename="pkg4", architecturehintlist="i386", archive=self.ppa)142 sourcename="pkg4", architecturehintlist="i386", archive=self.ppa,
143 dateremoved=self.over_threshold_date)
144 other_source = pkg4.copyTo(
145 pkg4.distroseries, pkg4.pocket, self.ppa2)
146 other_source.secure_record.dateremoved = None
125 [pub] = self.stp.getPubBinaries(147 [pub] = self.stp.getPubBinaries(
126 pub_source=pkg4, dateremoved=self.over_threshold_date,148 pub_source=pkg4, dateremoved=self.over_threshold_date,
127 archive=self.ppa)149 archive=self.ppa)
@@ -130,16 +152,21 @@
130 other_binary.secure_record.dateremoved = None152 other_binary.secure_record.dateremoved = None
131153
132 self.runScript()154 self.runScript()
133 self.assertNotExpired(pub)155 self.assertSourceNotExpired(pkg4)
156 self.assertBinaryNotExpired(pub)
134157
135 def testNoExpirationWithDateOverThresholdAndOtherPubUnderThreshold(self):158 def testNoExpirationWithDateOverThresholdAndOtherPubUnderThreshold(self):
136 """Test no expiring.159 """Test no expiring.
137 160
138 Test no expiring if dateremoved old enough but other publication161 Test no expiring if dateremoved old enough but other publication
139 not over date threshold.162 not over date threshold.
140 """163 """
141 pkg5 = self.stp.getPubSource(164 pkg5 = self.stp.getPubSource(
142 sourcename="pkg5", architecturehintlist="i386", archive=self.ppa)165 sourcename="pkg5", architecturehintlist="i386", archive=self.ppa,
166 dateremoved=self.over_threshold_date)
167 other_source = pkg5.copyTo(
168 pkg5.distroseries, pkg5.pocket, self.ppa2)
169 other_source.secure_record.dateremoved = self.under_threshold_date
143 [pub] = self.stp.getPubBinaries(170 [pub] = self.stp.getPubBinaries(
144 pub_source=pkg5, dateremoved=self.over_threshold_date,171 pub_source=pkg5, dateremoved=self.over_threshold_date,
145 archive=self.ppa)172 archive=self.ppa)
@@ -148,67 +175,77 @@
148 other_binary.secure_record.dateremoved = self.under_threshold_date175 other_binary.secure_record.dateremoved = self.under_threshold_date
149176
150 self.runScript()177 self.runScript()
151 self.assertNotExpired(pub)178 self.assertSourceNotExpired(pkg5)
179 self.assertBinaryNotExpired(pub)
152180
153 def _setUpExpirablePublications(self, archive=None):181 def _setUpExpirablePublications(self, archive=None):
154 """Helper to set up two publications that are both expirable."""182 """Helper to set up two publications that are both expirable."""
155 if archive is None:183 if archive is None:
156 archive = self.ppa184 archive = self.ppa
157 pkg5 = self.stp.getPubSource(185 pkg5 = self.stp.getPubSource(
158 sourcename="pkg5", architecturehintlist="i386", archive=archive)186 sourcename="pkg5", architecturehintlist="i386", archive=archive,
187 dateremoved=self.over_threshold_date)
188 other_source = pkg5.copyTo(
189 pkg5.distroseries, pkg5.pocket, self.ppa2)
190 other_source.secure_record.dateremoved = self.over_threshold_date
159 [pub] = self.stp.getPubBinaries(191 [pub] = self.stp.getPubBinaries(
160 pub_source=pkg5, dateremoved=self.over_threshold_date,192 pub_source=pkg5, dateremoved=self.over_threshold_date,
161 archive=archive)193 archive=archive)
162 [other_binary] = pub.copyTo(194 [other_binary] = pub.copyTo(
163 pub.distroarchseries.distroseries, pub.pocket, self.ppa2)195 pub.distroarchseries.distroseries, pub.pocket, self.ppa2)
164 other_binary.secure_record.dateremoved = self.over_threshold_date196 other_binary.secure_record.dateremoved = self.over_threshold_date
165 return pub197 return pkg5, pub
166198
167 def testNoExpirationWithDateOverThresholdAndOtherPubOverThreshold(self):199 def testNoExpirationWithDateOverThresholdAndOtherPubOverThreshold(self):
168 """Test expiring works.200 """Test expiring works.
169 201
170 Test expiring works if dateremoved old enough and other publication202 Test expiring works if dateremoved old enough and other publication
171 is over date threshold.203 is over date threshold.
172 """204 """
173 pub = self._setUpExpirablePublications()205 source, binary = self._setUpExpirablePublications()
174 self.runScript()206 self.runScript()
175 self.assertExpired(pub)207 self.assertSourceExpired(source)
208 self.assertBinaryExpired(binary)
176209
177 def testBlacklistingWorks(self):210 def testBlacklistingWorks(self):
178 """Test that blacklisted PPAs are not expired."""211 """Test that blacklisted PPAs are not expired."""
179 pub = self._setUpExpirablePublications()212 source, binary = self._setUpExpirablePublications()
180 script = self.getScript()213 script = self.getScript()
181 script.blacklist = ["cprov",]214 script.blacklist = ["cprov",]
182 self.layer.txn.commit()215 self.layer.txn.commit()
183 self.layer.switchDbUser(self.dbuser)216 self.layer.switchDbUser(self.dbuser)
184 script.main()217 script.main()
185 self.assertNotExpired(pub)218 self.assertSourceNotExpired(source)
219 self.assertBinaryNotExpired(binary)
186220
187 def testPrivatePPAsNotExpired(self):221 def testPrivatePPAsNotExpired(self):
188 """Test that private PPAs are not expired."""222 """Test that private PPAs are not expired."""
189 self.ppa.private = True223 self.ppa.private = True
190 self.ppa.buildd_secret = "foo"224 self.ppa.buildd_secret = "foo"
191 pub = self._setUpExpirablePublications()225 source, binary = self._setUpExpirablePublications()
192 self.runScript()226 self.runScript()
193 self.assertNotExpired(pub)227 self.assertSourceNotExpired(source)
228 self.assertBinaryNotExpired(binary)
194229
195 def testDryRun(self):230 def testDryRun(self):
196 """Test that when dryrun is specified, nothing is expired."""231 """Test that when dryrun is specified, nothing is expired."""
197 pub = self._setUpExpirablePublications()232 source, binary = self._setUpExpirablePublications()
198 # We have to commit here otherwise when the script aborts it233 # We have to commit here otherwise when the script aborts it
199 # will remove the test publications we just created.234 # will remove the test publications we just created.
200 self.layer.txn.commit()235 self.layer.txn.commit()
201 script = self.getScript(['--dry-run'])236 script = self.getScript(['--dry-run'])
202 self.layer.switchDbUser(self.dbuser)237 self.layer.switchDbUser(self.dbuser)
203 script.main()238 script.main()
204 self.assertNotExpired(pub)239 self.assertSourceNotExpired(source)
240 self.assertBinaryNotExpired(binary)
205241
206 def testDoesNotAffectNonPPA(self):242 def testDoesNotAffectNonPPA(self):
207 """Test that expiry does not happen for non-PPA publications."""243 """Test that expiry does not happen for non-PPA publications."""
208 ubuntu_archive = getUtility(IDistributionSet)['ubuntu'].main_archive244 ubuntu_archive = getUtility(IDistributionSet)['ubuntu'].main_archive
209 pub = self._setUpExpirablePublications(ubuntu_archive)245 source, binary = self._setUpExpirablePublications(ubuntu_archive)
210 self.runScript()246 self.runScript()
211 self.assertNotExpired(pub)247 self.assertSourceNotExpired(source)
248 self.assertBinaryNotExpired(binary)
212249
213250
214def test_suite():251def test_suite():