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
1=== renamed file 'cronscripts/expire-ppa-binaries.py' => 'cronscripts/expire-ppa-files.py'
2--- cronscripts/expire-ppa-binaries.py 2009-10-13 14:38:07 +0000
3+++ cronscripts/expire-ppa-files.py 2010-01-20 10:35:29 +0000
4@@ -17,6 +17,6 @@
5
6 if __name__ == '__main__':
7 script = PPABinaryExpirer(
8- 'expire-ppa-binaries', dbuser=config.binaryfile_expire.dbuser)
9+ 'expire-ppa-files', dbuser=config.binaryfile_expire.dbuser)
10 script.lock_and_run()
11
12
13=== modified file 'database/schema/security.cfg'
14--- database/schema/security.cfg 2010-01-06 09:06:56 +0000
15+++ database/schema/security.cfg 2010-01-20 10:35:29 +0000
16@@ -1612,6 +1612,9 @@
17 public.person = SELECT
18 public.libraryfilealias = SELECT, UPDATE
19 public.securebinarypackagepublishinghistory = SELECT
20+public.sourcepackagereleasefile = SELECT
21+public.sourcepackagepublishinghistory = SELECT
22+public.sourcepackagerelease = SELECT
23
24 [create-merge-proposals]
25 type=user
26
27=== modified file 'lib/lp/soyuz/scripts/expire_ppa_binaries.py'
28--- lib/lp/soyuz/scripts/expire_ppa_binaries.py 2009-12-11 14:35:28 +0000
29+++ lib/lp/soyuz/scripts/expire_ppa_binaries.py 2010-01-20 10:35:29 +0000
30@@ -54,7 +54,61 @@
31 help=("The number of days after which to expire binaries. "
32 "Must be specified."))
33
34- def determineExpirables(self, num_days):
35+ def determineSourceExpirables(self, num_days):
36+ """Return expirable libraryfilealias IDs."""
37+ # Avoid circular imports.
38+ from lp.soyuz.interfaces.archive import ArchivePurpose
39+
40+ stay_of_execution = '%d days' % num_days
41+
42+ # The subquery here has to repeat the checks for privacy and
43+ # blacklisting on *other* publications that are also done in
44+ # the main loop for the archive being considered.
45+ results = self.store.execute("""
46+ SELECT lfa.id
47+ FROM
48+ LibraryFileAlias AS lfa,
49+ Archive,
50+ SourcePackageReleaseFile AS sprf,
51+ SourcePackageRelease AS spr,
52+ SourcePackagePublishingHistory AS spph
53+ WHERE
54+ lfa.id = sprf.libraryfile
55+ AND spr.id = sprf.sourcepackagerelease
56+ AND spph.sourcepackagerelease = spr.id
57+ AND spph.dateremoved < (
58+ CURRENT_TIMESTAMP AT TIME ZONE 'UTC' - interval %s)
59+ AND spph.archive = archive.id
60+ AND archive.purpose = %s
61+ AND lfa.expires IS NULL
62+ EXCEPT
63+ SELECT sprf.libraryfile
64+ FROM
65+ SourcePackageRelease AS spr,
66+ SourcePackageReleaseFile AS sprf,
67+ SourcePackagePublishingHistory AS spph,
68+ Archive AS a,
69+ Person AS p
70+ WHERE
71+ spr.id = sprf.sourcepackagerelease
72+ AND spph.sourcepackagerelease = spr.id
73+ AND spph.archive = a.id
74+ AND p.id = a.owner
75+ AND (
76+ p.name IN %s
77+ OR a.private IS TRUE
78+ OR a.purpose != %s
79+ OR dateremoved >
80+ CURRENT_TIMESTAMP AT TIME ZONE 'UTC' - interval %s
81+ OR dateremoved IS NULL);
82+ """ % sqlvalues(
83+ stay_of_execution, ArchivePurpose.PPA, self.blacklist,
84+ ArchivePurpose.PPA, stay_of_execution))
85+
86+ lfa_ids = results.get_all()
87+ return lfa_ids
88+
89+ def determineBinaryExpirables(self, num_days):
90 """Return expirable libraryfilealias IDs."""
91 # Avoid circular imports.
92 from lp.soyuz.interfaces.archive import ArchivePurpose
93@@ -116,7 +170,8 @@
94 self.store = getUtility(IStoreSelector).get(
95 MAIN_STORE, DEFAULT_FLAVOR)
96
97- lfa_ids = self.determineExpirables(num_days)
98+ lfa_ids = self.determineSourceExpirables(num_days)
99+ lfa_ids.extend(self.determineBinaryExpirables(num_days))
100 batch_count = 0
101 batch_limit = 500
102 for id in lfa_ids:
103
104=== modified file 'lib/lp/soyuz/scripts/tests/test_expire_ppa_bins.py'
105--- lib/lp/soyuz/scripts/tests/test_expire_ppa_bins.py 2009-12-11 14:35:28 +0000
106+++ lib/lp/soyuz/scripts/tests/test_expire_ppa_bins.py 2010-01-20 10:35:29 +0000
107@@ -74,54 +74,76 @@
108 self.layer.switchDbUser(self.dbuser)
109 script.main()
110
111- def assertExpired(self, publication):
112- self.assertNotEqual(
113- publication.binarypackagerelease.files[0].libraryfile.expires,
114- None,
115- "lfa.expires should be set, but it's not.")
116-
117- def assertNotExpired(self, publication):
118- self.assertEqual(
119- publication.binarypackagerelease.files[0].libraryfile.expires,
120+ def assertBinaryExpired(self, publication):
121+ self.assertNotEqual(
122+ publication.binarypackagerelease.files[0].libraryfile.expires,
123+ None,
124+ "lfa.expires should be set, but it's not.")
125+
126+ def assertBinaryNotExpired(self, publication):
127+ self.assertEqual(
128+ publication.binarypackagerelease.files[0].libraryfile.expires,
129+ None,
130+ "lfa.expires should be None, but it's not.")
131+
132+ def assertSourceExpired(self, publication):
133+ self.assertNotEqual(
134+ publication.sourcepackagerelease.files[0].libraryfile.expires,
135+ None,
136+ "lfa.expires should be set, but it's not.")
137+
138+ def assertSourceNotExpired(self, publication):
139+ self.assertEqual(
140+ publication.sourcepackagerelease.files[0].libraryfile.expires,
141 None,
142 "lfa.expires should be None, but it's not.")
143
144 def testNoExpirationWithNoDateremoved(self):
145 """Test that no expiring happens if no dateremoved set."""
146 pkg1 = self.stp.getPubSource(
147- sourcename="pkg1", architecturehintlist="i386", archive=self.ppa)
148+ sourcename="pkg1", architecturehintlist="i386", archive=self.ppa,
149+ dateremoved=None)
150 [pub] = self.stp.getPubBinaries(
151 pub_source=pkg1, dateremoved=None, archive=self.ppa)
152
153 self.runScript()
154- self.assertNotExpired(pub)
155+ self.assertSourceNotExpired(pkg1)
156+ self.assertBinaryNotExpired(pub)
157
158 def testNoExpirationWithDateUnderThreshold(self):
159 """Test no expiring if dateremoved too recent."""
160 pkg2 = self.stp.getPubSource(
161- sourcename="pkg2", architecturehintlist="i386", archive=self.ppa)
162+ sourcename="pkg2", architecturehintlist="i386", archive=self.ppa,
163+ dateremoved=self.under_threshold_date)
164 [pub] = self.stp.getPubBinaries(
165 pub_source=pkg2, dateremoved=self.under_threshold_date,
166 archive=self.ppa)
167
168 self.runScript()
169- self.assertNotExpired(pub)
170+ self.assertSourceNotExpired(pkg2)
171+ self.assertBinaryNotExpired(pub)
172
173 def testExpirationWithDateOverThreshold(self):
174 """Test expiring works if dateremoved old enough."""
175 pkg3 = self.stp.getPubSource(
176- sourcename="pkg3", architecturehintlist="i386", archive=self.ppa)
177+ sourcename="pkg3", architecturehintlist="i386", archive=self.ppa,
178+ dateremoved=self.over_threshold_date)
179 [pub] = self.stp.getPubBinaries(
180 pub_source=pkg3, dateremoved=self.over_threshold_date,
181 archive=self.ppa)
182
183 self.runScript()
184- self.assertExpired(pub)
185+ self.assertSourceExpired(pkg3)
186+ self.assertBinaryExpired(pub)
187
188 def testNoExpirationWithDateOverThresholdAndOtherValidPublication(self):
189 """Test no expiry if dateremoved old enough but other publication."""
190 pkg4 = self.stp.getPubSource(
191- sourcename="pkg4", architecturehintlist="i386", archive=self.ppa)
192+ sourcename="pkg4", architecturehintlist="i386", archive=self.ppa,
193+ dateremoved=self.over_threshold_date)
194+ other_source = pkg4.copyTo(
195+ pkg4.distroseries, pkg4.pocket, self.ppa2)
196+ other_source.secure_record.dateremoved = None
197 [pub] = self.stp.getPubBinaries(
198 pub_source=pkg4, dateremoved=self.over_threshold_date,
199 archive=self.ppa)
200@@ -130,16 +152,21 @@
201 other_binary.secure_record.dateremoved = None
202
203 self.runScript()
204- self.assertNotExpired(pub)
205+ self.assertSourceNotExpired(pkg4)
206+ self.assertBinaryNotExpired(pub)
207
208 def testNoExpirationWithDateOverThresholdAndOtherPubUnderThreshold(self):
209 """Test no expiring.
210-
211+
212 Test no expiring if dateremoved old enough but other publication
213 not over date threshold.
214 """
215 pkg5 = self.stp.getPubSource(
216- sourcename="pkg5", architecturehintlist="i386", archive=self.ppa)
217+ sourcename="pkg5", architecturehintlist="i386", archive=self.ppa,
218+ dateremoved=self.over_threshold_date)
219+ other_source = pkg5.copyTo(
220+ pkg5.distroseries, pkg5.pocket, self.ppa2)
221+ other_source.secure_record.dateremoved = self.under_threshold_date
222 [pub] = self.stp.getPubBinaries(
223 pub_source=pkg5, dateremoved=self.over_threshold_date,
224 archive=self.ppa)
225@@ -148,67 +175,77 @@
226 other_binary.secure_record.dateremoved = self.under_threshold_date
227
228 self.runScript()
229- self.assertNotExpired(pub)
230+ self.assertSourceNotExpired(pkg5)
231+ self.assertBinaryNotExpired(pub)
232
233 def _setUpExpirablePublications(self, archive=None):
234 """Helper to set up two publications that are both expirable."""
235 if archive is None:
236 archive = self.ppa
237 pkg5 = self.stp.getPubSource(
238- sourcename="pkg5", architecturehintlist="i386", archive=archive)
239+ sourcename="pkg5", architecturehintlist="i386", archive=archive,
240+ dateremoved=self.over_threshold_date)
241+ other_source = pkg5.copyTo(
242+ pkg5.distroseries, pkg5.pocket, self.ppa2)
243+ other_source.secure_record.dateremoved = self.over_threshold_date
244 [pub] = self.stp.getPubBinaries(
245 pub_source=pkg5, dateremoved=self.over_threshold_date,
246 archive=archive)
247 [other_binary] = pub.copyTo(
248 pub.distroarchseries.distroseries, pub.pocket, self.ppa2)
249 other_binary.secure_record.dateremoved = self.over_threshold_date
250- return pub
251+ return pkg5, pub
252
253 def testNoExpirationWithDateOverThresholdAndOtherPubOverThreshold(self):
254 """Test expiring works.
255-
256+
257 Test expiring works if dateremoved old enough and other publication
258 is over date threshold.
259 """
260- pub = self._setUpExpirablePublications()
261+ source, binary = self._setUpExpirablePublications()
262 self.runScript()
263- self.assertExpired(pub)
264+ self.assertSourceExpired(source)
265+ self.assertBinaryExpired(binary)
266
267 def testBlacklistingWorks(self):
268 """Test that blacklisted PPAs are not expired."""
269- pub = self._setUpExpirablePublications()
270+ source, binary = self._setUpExpirablePublications()
271 script = self.getScript()
272 script.blacklist = ["cprov",]
273 self.layer.txn.commit()
274 self.layer.switchDbUser(self.dbuser)
275 script.main()
276- self.assertNotExpired(pub)
277+ self.assertSourceNotExpired(source)
278+ self.assertBinaryNotExpired(binary)
279
280 def testPrivatePPAsNotExpired(self):
281 """Test that private PPAs are not expired."""
282 self.ppa.private = True
283 self.ppa.buildd_secret = "foo"
284- pub = self._setUpExpirablePublications()
285+ source, binary = self._setUpExpirablePublications()
286 self.runScript()
287- self.assertNotExpired(pub)
288+ self.assertSourceNotExpired(source)
289+ self.assertBinaryNotExpired(binary)
290
291 def testDryRun(self):
292 """Test that when dryrun is specified, nothing is expired."""
293- pub = self._setUpExpirablePublications()
294+ source, binary = self._setUpExpirablePublications()
295 # We have to commit here otherwise when the script aborts it
296 # will remove the test publications we just created.
297 self.layer.txn.commit()
298 script = self.getScript(['--dry-run'])
299 self.layer.switchDbUser(self.dbuser)
300 script.main()
301- self.assertNotExpired(pub)
302+ self.assertSourceNotExpired(source)
303+ self.assertBinaryNotExpired(binary)
304
305 def testDoesNotAffectNonPPA(self):
306 """Test that expiry does not happen for non-PPA publications."""
307 ubuntu_archive = getUtility(IDistributionSet)['ubuntu'].main_archive
308- pub = self._setUpExpirablePublications(ubuntu_archive)
309+ source, binary = self._setUpExpirablePublications(ubuntu_archive)
310 self.runScript()
311- self.assertNotExpired(pub)
312+ self.assertSourceNotExpired(source)
313+ self.assertBinaryNotExpired(binary)
314
315
316 def test_suite():