Merge lp:~cjwatson/launchpad/remove-query-distro-pending-suites into lp:launchpad

Proposed by Colin Watson on 2012-04-07
Status: Merged
Approved by: Jeroen T. Vermeulen on 2012-04-10
Approved revision: no longer in the source branch.
Merged at revision: 15073
Proposed branch: lp:~cjwatson/launchpad/remove-query-distro-pending-suites
Merge into: lp:launchpad
Diff against target: 372 lines (+36/-141)
6 files modified
lib/lp/archivepublisher/scripts/publish_ftpmaster.py (+17/-16)
lib/lp/archivepublisher/tests/test_publish_ftpmaster.py (+3/-3)
lib/lp/soyuz/scripts/querydistro.py (+1/-76)
lib/lp/soyuz/scripts/tests/germinate-test-data/mock-lp-root/scripts/ftpmaster-tools/lp-query-distro.py (+4/-8)
lib/lp/soyuz/scripts/tests/test_lpquerydistro.py (+11/-33)
scripts/ftpmaster-tools/lp-query-distro.py (+0/-5)
To merge this branch: bzr merge lp:~cjwatson/launchpad/remove-query-distro-pending-suites
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) 2012-04-07 Approve on 2012-04-09
Review via email: mp+101174@code.launchpad.net

Commit Message

Remove now-pointless indirection through LpQueryDistro pending_suites, and remove several other obsolete actions.

Description of the Change

== Summary ==

The "pending_suites" action in the lp-query-distro.py script (and the LpQueryDistro script class) made sense when the ftpmaster publisher script was a shell script; pending_suites is used in exactly one place, to discover whether any security suites are pending publication (see r6303.1.2). However, since it was rewritten in Python, it's just pointless indirection, not to mention inefficient because it means setting up and tearing down a LaunchpadScript instance. PublishFTPMaster can just talk to the database directly. We can refactor this.

I also noticed that several other LpQueryDistro actions are no longer used, and removed them; and likewise from a mock version of lp-query-distro.py in use in the cron.germinate tests. (It probably wouldn't be desperately hard to get rid of the entire script at this point, but that's a matter for another day and another branch.)

I think it's extremely unlikely that we'll need this code in future and regret removing it. lp-query-distro.py was always mainly for shell scripts that nobody had got round to rewriting in Python yet, and anything new that needs to know about this stuff is very likely to be in Python. There was a secondary goal at one point of helping out local archive administration scripts on ftpmaster, but we have nothing left that uses lp-query-distro.py and anything new we write will be based on the API.

== Tests ==

bin/test -vvct test_lpquerydistro -t test_publish_ftpmaster -t test_generate_contents_files -t test_cron_germinate

== lint ==

Just one false positive:

./scripts/ftpmaster-tools/lp-query-distro.py
      23: '_pythonpath' imported but unused

To post a comment you must log in.
pedro cavazos (aaacavazos) :
Jeroen T. Vermeulen (jtv) wrote :

This makes me very happy. It's one thing to say “we should look into cleaning this up some day,” but actually doing it is another. It's also a very well-written merge proposal and it very much looks like a good branch.

Just a small note, partly because it bothered me to see one piece of code growing a bit instead of shrinking:

=== modified file 'lib/lp/archivepublisher/scripts/publish_ftpmaster.py'
--- lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2012-02-28 04:24:19 +0000
+++ lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2012-04-07 14:59:21 +0000

@@ -329,12 +324,20 @@
     def getDirtySuites(self, distribution):
         """Return list of suites that have packages pending publication."""
         self.logger.debug("Querying which suites are pending publication...")
- query_distro = LpQueryDistro(
- test_args=['-d', distribution.name, "pending_suites"],
- logger=self.logger)
- receiver = StoreArgument()
- query_distro.runAction(presenter=receiver)
- return receiver.argument.split()
+
+ pending_suites = set()
+ pending_sources = distribution.main_archive.getPublishedSources(
+ status=PackagePublishingStatus.PENDING)
+ for pub in pending_sources:
+ pending_suites.add((pub.distroseries, pub.pocket))
*
+ pending_binaries = distribution.main_archive.getAllPublishedBinaries(
+ status=PackagePublishingStatus.PENDING)
+ for pub in pending_binaries:
+ pending_suites.add((pub.distroarchseries.distroseries, pub.pocket))
+
+ return [distroseries.name + pocketsuffix[pocket]
+ for distroseries, pocket in pending_suites]

As far as I can make out, the return value will duplicate a suite name between architectures (where "source" is effectively also an architecture). BI'll assume that's not what you want.

If it weren't for performance, the code could have been written more simply as:

        archive = distribution.main_archive
        pending = PackagePublishingStatus.PENDING
        pending_sources = list(archive.getPublishedSources(pending))
        pending_binaries = list(archive.getAllPublishedBinaries(pending))
        return set(
            pub.distroseries.name + pocketsuffix[pub.pocket]
            for pub in pending_sources + pending_binaries)

If the pending_binaries bit could fetch a large number of distroarchseries from the database — I'm assuming the number of distroseries will be small and probably already in memory — you can bulk-load those using load_related using a single query:

        archive = distribution.main_archive
        pending = PackagePublishingStatus.PENDING
        pending_sources = list(archive.getPublishedSources(pending))
        pending_binaries = list(archive.getAllPublishedBinaries(pending))
        load_related(DistroArchSeries, pending_binaries, 'distroarchseries_id')
        return set(
            pub.distroseries.name + pocketsuffix[pub.pocket]
            for pub in pending_sources + pending_binaries)

This does return a set instead of a list. But I can't think of any problems that might cause.

review: Approve
Jeroen T. Vermeulen (jtv) wrote :

Wow. It's a good thing I was substantially done with that review, because it submitted by accident as I was trying to turn off a caps lock that had gotten accidentally enabled. I have no idea what made Chromium submit the form. You can still tell where I was typing at “BI'll.”

Colin Watson (cjwatson) wrote :

On Mon, Apr 09, 2012 at 05:38:18AM -0000, Jeroen T. Vermeulen wrote:
> Just a small note, partly because it bothered me to see one piece of code growing a bit instead of shrinking:

I should point out for the record that I just moved this code around; I
didn't actually write it originally. (That said, obviously I don't
object to improving it.)

> As far as I can make out, the return value will duplicate a suite name between architectures (where "source" is effectively also an architecture). BI'll assume that's not what you want.

I can't see how that's the case. pending_suites is a set, so that deals
with deduplication.

Thanks for the suggestions about rewriting this section of code, though.
I hadn't previously known anything about the bulk loading interfaces
inside Launchpad, so that was a good thing to learn about. I can't
imagine that it loads a particularly large number of distroarchseries -
this script only touches Ubuntu, and there are only 32 active
distroarchseries in Ubuntu right now - but it doesn't hurt to load them
in one go anyway. I've pushed a modified version for your delectation
and delight.

Jeroen T. Vermeulen (jtv) wrote :

Thanks. I was mistaken about the duplications.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archivepublisher/scripts/publish_ftpmaster.py'
2--- lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2012-02-28 04:24:19 +0000
3+++ lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2012-04-10 00:33:18 +0000
4@@ -21,16 +21,20 @@
5 from lp.registry.interfaces.pocket import pocketsuffix
6 from lp.registry.interfaces.series import SeriesStatus
7 from lp.services.config import config
8+from lp.services.database.bulk import load_related
9 from lp.services.scripts.base import (
10 LaunchpadCronScript,
11 LaunchpadScriptFailure,
12 )
13 from lp.services.utils import file_exists
14-from lp.soyuz.enums import ArchivePurpose
15+from lp.soyuz.enums import (
16+ ArchivePurpose,
17+ PackagePublishingStatus,
18+ )
19+from lp.soyuz.model.distroarchseries import DistroArchSeries
20 from lp.soyuz.scripts.custom_uploads_copier import CustomUploadsCopier
21 from lp.soyuz.scripts.processaccepted import ProcessAccepted
22 from lp.soyuz.scripts.publishdistro import PublishDistro
23-from lp.soyuz.scripts.querydistro import LpQueryDistro
24
25
26 def get_publishable_archives(distribution):
27@@ -121,13 +125,6 @@
28 return {"PATH": '"$PATH":%s' % shell_quote(scripts_dir)}
29
30
31-class StoreArgument:
32- """Helper class: receive argument and store it."""
33-
34- def __call__(self, argument):
35- self.argument = argument
36-
37-
38 def find_run_parts_dir(distro, parts):
39 """Find the requested run-parts directory, if it exists."""
40 run_parts_location = config.archivepublisher.run_parts_location
41@@ -327,14 +324,18 @@
42 script.main()
43
44 def getDirtySuites(self, distribution):
45- """Return list of suites that have packages pending publication."""
46+ """Return set of suites that have packages pending publication."""
47 self.logger.debug("Querying which suites are pending publication...")
48- query_distro = LpQueryDistro(
49- test_args=['-d', distribution.name, "pending_suites"],
50- logger=self.logger)
51- receiver = StoreArgument()
52- query_distro.runAction(presenter=receiver)
53- return receiver.argument.split()
54+
55+ archive = distribution.main_archive
56+ pending = PackagePublishingStatus.PENDING
57+ pending_sources = list(archive.getPublishedSources(status=pending))
58+ pending_binaries = list(archive.getAllPublishedBinaries(
59+ status=pending))
60+ load_related(DistroArchSeries, pending_binaries, 'distroarchseriesID')
61+ return set(
62+ pub.distroseries.name + pocketsuffix[pub.pocket]
63+ for pub in pending_sources + pending_binaries)
64
65 def getDirtySecuritySuites(self, distribution):
66 """List security suites with pending publications."""
67
68=== modified file 'lib/lp/archivepublisher/tests/test_publish_ftpmaster.py'
69--- lib/lp/archivepublisher/tests/test_publish_ftpmaster.py 2012-01-01 02:58:52 +0000
70+++ lib/lp/archivepublisher/tests/test_publish_ftpmaster.py 2012-04-10 00:33:18 +0000
71@@ -1,4 +1,4 @@
72-# Copyright 2011 Canonical Ltd. This software is licensed under the
73+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
74 # GNU Affero General Public License version 3 (see the file LICENSE).
75
76 """Test publish-ftpmaster cron script."""
77@@ -400,7 +400,7 @@
78 distro = spph.distroseries.distribution
79 script = self.makeScript(spph.distroseries.distribution)
80 script.setUp()
81- self.assertEqual(
82+ self.assertContentEqual(
83 [name_spph_suite(spph)], script.getDirtySuites(distro))
84
85 def test_getDirtySuites_returns_suites_with_pending_publications(self):
86@@ -423,7 +423,7 @@
87 distro = spph.distroseries.distribution
88 script = self.makeScript(spph.distroseries.distribution)
89 script.setUp()
90- self.assertEqual([], script.getDirtySuites(distro))
91+ self.assertContentEqual([], script.getDirtySuites(distro))
92
93 def test_getDirtySecuritySuites_returns_security_suites(self):
94 distro = self.makeDistroWithPublishDirectory()
95
96=== modified file 'lib/lp/soyuz/scripts/querydistro.py'
97--- lib/lp/soyuz/scripts/querydistro.py 2012-01-19 03:06:04 +0000
98+++ lib/lp/soyuz/scripts/querydistro.py 2012-04-10 00:33:18 +0000
99@@ -7,7 +7,6 @@
100
101 __all__ = ['LpQueryDistro']
102
103-from lp.registry.interfaces.pocket import pocketsuffix
104 from lp.registry.interfaces.series import SeriesStatus
105 from lp.services.scripts.base import (
106 LaunchpadScript,
107@@ -17,7 +16,6 @@
108 build_package_location,
109 PackageLocationError,
110 )
111-from lp.soyuz.enums import PackagePublishingStatus
112
113
114 class LpQueryDistro(LaunchpadScript):
115@@ -28,9 +26,7 @@
116
117 Also initialize the list 'allowed_arguments'.
118 """
119- self.allowed_actions = [
120- 'current', 'development', 'supported', 'pending_suites', 'archs',
121- 'official_archs', 'nominated_arch_indep', 'pocket_suffixes']
122+ self.allowed_actions = ['development', 'supported', 'archs']
123 self.usage = '%%prog <%s>' % ' | '.join(self.allowed_actions)
124 LaunchpadScript.__init__(self, *args, **kwargs)
125
126@@ -122,23 +118,6 @@
127 "Action does not accept defined suite.")
128
129 @property
130- def current(self):
131- """Return the name of the CURRENT distroseries.
132-
133- It is restricted for the context distribution.
134-
135- It may raise LaunchpadScriptFailure if a suite was passed on the
136- command-line or if not CURRENT distroseries was found.
137- """
138- self.checkNoSuiteDefined()
139- series = self.location.distribution.getSeriesByStatus(
140- SeriesStatus.CURRENT)
141- if not series:
142- raise LaunchpadScriptFailure("No CURRENT series.")
143-
144- return series[0].name
145-
146- @property
147 def development(self):
148 """Return the name of the DEVELOPMENT distroseries.
149
150@@ -197,28 +176,6 @@
151 return " ".join(supported_series)
152
153 @property
154- def pending_suites(self):
155- """Return the suite names containing PENDING publication.
156-
157- It check for sources and/or binary publications.
158- """
159- self.checkNoSuiteDefined()
160- pending_suites = set()
161- pending_sources = self.location.archive.getPublishedSources(
162- status=PackagePublishingStatus.PENDING)
163- for pub in pending_sources:
164- pending_suites.add((pub.distroseries, pub.pocket))
165-
166- pending_binaries = self.location.archive.getAllPublishedBinaries(
167- status=PackagePublishingStatus.PENDING)
168- for pub in pending_binaries:
169- pending_suites.add(
170- (pub.distroarchseries.distroseries, pub.pocket))
171-
172- return " ".join([distroseries.name + pocketsuffix[pocket]
173- for distroseries, pocket in pending_suites])
174-
175- @property
176 def archs(self):
177 """Return a space-separated list of architecture tags.
178
179@@ -226,35 +183,3 @@
180 """
181 architectures = self.location.distroseries.architectures
182 return " ".join(arch.architecturetag for arch in architectures)
183-
184- @property
185- def official_archs(self):
186- """Return a space-separated list of official architecture tags.
187-
188- It is restricted to the context distribution and suite.
189- """
190- architectures = self.location.distroseries.architectures
191- return " ".join(arch.architecturetag
192- for arch in architectures
193- if arch.official)
194-
195- @property
196- def nominated_arch_indep(self):
197- """Return the nominated arch indep architecture tag.
198-
199- It is restricted to the context distribution and suite.
200- """
201- series = self.location.distroseries
202- return series.nominatedarchindep.architecturetag
203-
204- @property
205- def pocket_suffixes(self):
206- """Return a space-separated list of non-empty pocket suffixes.
207-
208- The RELEASE pocket (whose suffix is the empty string) is omitted.
209-
210- The returned space-separated string is ordered alphabetically.
211- """
212- sorted_non_empty_suffixes = sorted(
213- suffix for suffix in pocketsuffix.values() if suffix != '')
214- return " ".join(sorted_non_empty_suffixes)
215
216=== modified file 'lib/lp/soyuz/scripts/tests/germinate-test-data/mock-lp-root/scripts/ftpmaster-tools/lp-query-distro.py'
217--- lib/lp/soyuz/scripts/tests/germinate-test-data/mock-lp-root/scripts/ftpmaster-tools/lp-query-distro.py 2011-10-15 02:57:02 +0000
218+++ lib/lp/soyuz/scripts/tests/germinate-test-data/mock-lp-root/scripts/ftpmaster-tools/lp-query-distro.py 2012-04-10 00:33:18 +0000
219@@ -8,8 +8,8 @@
220
221
222 def error_and_exit():
223- sys.stderr.write("ERROR: I'm a mock, I only support 'development' "
224- "and 'supported' as argument\n")
225+ sys.stderr.write("ERROR: I'm a mock, I only support 'supported' as "
226+ "argument\n")
227 sys.exit(1)
228
229
230@@ -18,12 +18,8 @@
231 # test for it and error if it looks wrong
232 if len(args) == 2:
233 distro = args[1]
234- if distro == "development":
235- return "natty"
236- elif distro == "supported":
237- return "hardy jaunty karmic lucid maverick"
238- elif len(args) == 4 and args[1] == '-s' and args[3] == 'archs':
239- return "i386 amd64 powerpc armel"
240+ if distro == "supported":
241+ return "hardy jaunty karmic lucid maverick"
242 error_and_exit()
243
244
245
246=== modified file 'lib/lp/soyuz/scripts/tests/test_lpquerydistro.py'
247--- lib/lp/soyuz/scripts/tests/test_lpquerydistro.py 2012-01-19 03:09:38 +0000
248+++ lib/lp/soyuz/scripts/tests/test_lpquerydistro.py 2012-04-10 00:33:18 +0000
249@@ -50,15 +50,15 @@
250 Check that:
251 * return code is ZERO,
252 * standard error is empty
253- * standard output contains only the 'current distroseries' name
254+ * standard output contains only the 'development distroseries' name
255 """
256 returncode, out, err = self.runLpQueryDistro(
257- extra_args=['current'])
258+ extra_args=['development'])
259
260 self.assertEqual(
261 0, returncode, "\nScript Failed:%s\nStdout:\n%s\nStderr\n%s\n"
262 % (returncode, out, err))
263- self.assertEqual(out.strip(), 'warty')
264+ self.assertEqual(out.strip(), 'hoary')
265 self.assertEqual(err.strip(), '')
266
267 def testMissingAction(self):
268@@ -108,7 +108,7 @@
269 * standard error contains additional information about the failure.
270 """
271 returncode, out, err = self.runLpQueryDistro(
272- extra_args=['-s', 'hoary', 'current'])
273+ extra_args=['-s', 'warty', 'development'])
274
275 self.assertEqual(
276 1, returncode,
277@@ -140,15 +140,6 @@
278 """
279 self.test_output = '%s' % args
280
281- def testSuccessfullyAction(self):
282- """Check if the 'current' action is executed sucessfully."""
283- helper = self.getLpQueryDistro(test_args=['current'])
284- helper.runAction(presenter=self.presenter)
285- warty = self.ubuntu['warty']
286- self.assertEqual(warty.status.name, 'CURRENT')
287- self.assertEqual(helper.location.distribution.name, u'ubuntu')
288- self.assertEqual(self.test_output, u'warty')
289-
290 def testDevelopmentAndFrozenDistroSeries(self):
291 """The 'development' action should cope with FROZEN distroseries."""
292 helper = self.getLpQueryDistro(test_args=['development'])
293@@ -185,7 +176,8 @@
294 Some actions do not allow passing 'suite'.
295 See testActionswithDefinedSuite for further information.
296 """
297- helper = self.getLpQueryDistro(test_args=['-s', 'hoary', 'current'])
298+ helper = self.getLpQueryDistro(
299+ test_args=['-s', 'warty', 'development'])
300 self.assertRaises(LaunchpadScriptFailure,
301 helper.runAction, self.presenter)
302
303@@ -213,23 +205,16 @@
304 def testActionsWithUndefinedSuite(self):
305 """Check the actions supposed to work with undefined suite.
306
307- Only 'current', 'development' and 'supported' work with undefined
308- suite.
309- The other actions ('archs', 'official_arch', 'nominated_arch_indep')
310- will assume the CURRENT distroseries in context.
311+ Only 'development' and 'supported' work with undefined suite.
312+ The other actions ('archs') will assume the CURRENT distroseries in
313+ context.
314 """
315 helper = self.getLpQueryDistro(test_args=[])
316 helper._buildLocation()
317
318- self.assertEqual(helper.current, 'warty')
319 self.assertEqual(helper.development, 'hoary')
320 self.assertEqual(helper.supported, 'hoary warty')
321- self.assertEqual(helper.pending_suites, 'warty')
322 self.assertEqual(helper.archs, 'hppa i386')
323- self.assertEqual(helper.official_archs, 'i386')
324- self.assertEqual(helper.nominated_arch_indep, 'i386')
325- self.assertEqual(helper.pocket_suffixes,
326- '-backports -proposed -security -updates')
327
328 def assertAttributeRaisesScriptFailure(self, obj, attr_name):
329 """Asserts if accessing the given attribute name fails.
330@@ -242,20 +227,13 @@
331 def testActionsWithDefinedSuite(self):
332 """Opposite of testActionsWithUndefinedSuite.
333
334- Only some actions ('archs', 'official_arch', 'nominated_arch_indep',
335- and pocket_suffixes) work with defined suite, the other actions
336- ('current', 'development' and 'supported') will raise
337+ Only some actions ('archs') work with defined suite, the other
338+ actions ('development' and 'supported') will raise
339 LaunchpadScriptError if the suite is defined.
340 """
341 helper = self.getLpQueryDistro(test_args=['-s', 'warty'])
342 helper._buildLocation()
343
344- self.assertAttributeRaisesScriptFailure(helper, 'current')
345 self.assertAttributeRaisesScriptFailure(helper, 'development')
346 self.assertAttributeRaisesScriptFailure(helper, 'supported')
347- self.assertAttributeRaisesScriptFailure(helper, 'pending_suites')
348 self.assertEqual(helper.archs, 'hppa i386')
349- self.assertEqual(helper.official_archs, 'i386')
350- self.assertEqual(helper.nominated_arch_indep, 'i386')
351- self.assertEqual(helper.pocket_suffixes,
352- '-backports -proposed -security -updates')
353
354=== modified file 'scripts/ftpmaster-tools/lp-query-distro.py'
355--- scripts/ftpmaster-tools/lp-query-distro.py 2012-01-19 03:09:38 +0000
356+++ scripts/ftpmaster-tools/lp-query-distro.py 2012-04-10 00:33:18 +0000
357@@ -9,15 +9,10 @@
358 It should provide an easy way to retrieve current information from
359 Launchpad when using plain shell scripts, for example:
360
361- * CURRENT distroseries name: `./ubuntu-helper.py -d ubuntu current`
362 * DEVELOPMENT distroseries name:
363 `./ubuntu-helper.py -d ubuntu development`
364 * Distroseries architectures:
365 `./lp-query-distro.py -d ubuntu -s feisty archs`
366- * Distroseries official architectures:
367- `./lp-query-distro.py -d ubuntu -s feisty official_archs`
368- * Distroseries nominated-arch-indep:
369- `./lp-query-distro.py -d ubuntu -s feisty nominated_arch_indep`
370
371 Standard Output will carry the successfully executed information and
372 exit_code will be ZERO.