Merge lp:~jtv/launchpad/bug-824499 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Superseded
Proposed branch: lp:~jtv/launchpad/bug-824499
Merge into: lp:launchpad
Diff against target: 925 lines (+236/-162)
2 files modified
lib/lp/archivepublisher/scripts/publish_ftpmaster.py (+140/-111)
lib/lp/archivepublisher/tests/test_publish_ftpmaster.py (+96/-51)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-824499
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+71543@code.launchpad.net

This proposal has been superseded by a proposal from 2011-08-15.

Commit message

all-derived-distros option for publish-ftpmaster.

Description of the change

= Summary =

This adds an option to publish-ftpmaster to, rather than publishing a single distribution named on the command line (and let's face it, that's probably going to be Ubuntu) loops over all Ubuntu-derived distributions. That way we can (and will) set up a separate server to published derived distros, without needing admin intervention for each new distro.

== Proposed fix ==

Replace the script's self.distribution with a list, self.distributions, and loop over it. That gives us the same code path for a single distro (the existing mode of operation) and a loop over several. Next, add code for looking up the derived distros (dead easy since I've done it exactly twice before so it's in a reusable place) and checking that no more than one of the options is given.

== Pre-implementation notes ==

Julian expected me to do this as part of the same work on publish-distro, but as per Sod's Law I ended up forgetting that part. Understandable I hope, since publish-ftpmaster calls publish-distro instead of the other way around.

== Implementation details ==

The self.archives attribute is gone, and self.configs gets one extra layer of indirection. Removing self.archives also let me inline getArchives.

The ordering between setUpDirs and processAccepted has changed. I couldn't see any disadvantage to that, but it let me avoid calling setUpDirs separately for each distro. (This avoids a lot of noise in tests). Another method that stayed outside the distributions loop is recoverWorkingDists; restoring a sane working state of the filesystem now happens right up front for all distributions. The script's lock is tight enough to ensure that these directories aren't moved around later, while the loop is already running.

== Tests ==

{{{
./bin/test -vvc lp.archivepublisher.tests.test_publish_ftpmaster
}}}

== Demo and Q/A ==

Run publish-ftpmaster with the --all-derived option (instead of the usual --distribution=ubuntu). It should publish any derived distros, but not Ubuntu itself.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/archivepublisher/scripts/publish_ftpmaster.py
  lib/lp/archivepublisher/tests/test_publish_ftpmaster.py

To post a comment you must log in.

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 2011-08-09 10:30:43 +0000
3+++ lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2011-08-15 11:04:18 +0000
4@@ -31,12 +31,20 @@
5 from lp.soyuz.scripts.processaccepted import ProcessAccepted
6 from lp.soyuz.scripts.publishdistro import PublishDistro
7
8-# XXX JeroenVermeulen 2011-03-31 bug=746229: to start publishing debug
9-# archives, get rid of this list.
10-ARCHIVES_TO_PUBLISH = [
11- ArchivePurpose.PRIMARY,
12- ArchivePurpose.PARTNER,
13- ]
14+
15+def get_publishable_archives(distribution):
16+ """Get all archives for `distribution` that should be published."""
17+ # XXX JeroenVermeulen 2011-03-31 bug=746229: to start publishing
18+ # debug archives, simply stop filtering them out here. It may be
19+ # enough to return list(distribution.all_distro_archives) directly.
20+ ARCHIVES_TO_PUBLISH = [
21+ ArchivePurpose.PRIMARY,
22+ ArchivePurpose.PARTNER,
23+ ]
24+ return [
25+ archive
26+ for archive in distribution.all_distro_archives
27+ if archive.purpose in ARCHIVES_TO_PUBLISH]
28
29
30 def compose_shell_boolean(boolean_value):
31@@ -166,6 +174,9 @@
32 def add_my_options(self):
33 """See `LaunchpadScript`."""
34 self.parser.add_option(
35+ '-a', '--all-derived', dest='all_derived', action='store_true',
36+ default=False, help="Process all derived distributions.")
37+ self.parser.add_option(
38 '-d', '--distribution', dest='distribution', default=None,
39 help="Distribution to publish.")
40 self.parser.add_option(
41@@ -179,16 +190,25 @@
42 def processOptions(self):
43 """Handle command-line options.
44
45- Sets `self.distribution` to the `Distribution` to publish.
46+ Sets `self.distributions` to the `Distribution`s to publish.
47 """
48- if self.options.distribution is None:
49- raise LaunchpadScriptFailure("Specify a distribution.")
50+ if self.options.distribution is None and not self.options.all_derived:
51+ raise LaunchpadScriptFailure(
52+ "Specify a distribution, or --all-derived.")
53+ if self.options.distribution is not None and self.options.all_derived:
54+ raise LaunchpadScriptFailure(
55+ "Can't combine the --distribution and --all-derived options.")
56
57- self.distribution = getUtility(IDistributionSet).getByName(
58- self.options.distribution)
59- if self.distribution is None:
60- raise LaunchpadScriptFailure(
61- "Distribution %s not found." % self.options.distribution)
62+ if self.options.all_derived:
63+ distro_set = getUtility(IDistributionSet)
64+ self.distributions = distro_set.getDerivedDistributions()
65+ else:
66+ distro = getUtility(IDistributionSet).getByName(
67+ self.options.distribution)
68+ if distro is None:
69+ raise LaunchpadScriptFailure(
70+ "Distribution %s not found." % self.options.distribution)
71+ self.distributions = [distro]
72
73 def executeShell(self, command_line, failure=None):
74 """Run `command_line` through a shell.
75@@ -210,35 +230,31 @@
76 self.logger.debug("Command failed: %s", failure)
77 raise failure
78
79- def getArchives(self):
80- """Find archives for `self.distribution` that should be published."""
81- # XXX JeroenVermeulen 2011-03-31 bug=746229: to start publishing
82- # debug archives, change this to return
83- # list(self.distribution.all_distro_archives).
84- return [
85- archive
86- for archive in self.distribution.all_distro_archives
87- if archive.purpose in ARCHIVES_TO_PUBLISH]
88-
89 def getConfigs(self):
90 """Set up configuration objects for archives to be published.
91
92- The configs dict maps the archive purposes that are relevant for
93- publishing to the respective archives' configurations.
94+ The configs dict maps each distribution to another dict that maps the
95+ archive purposes that are relevant for publishing to the respective
96+ archives' configurations.
97+
98+ So: getConfigs[distro][purpose] gives you a config.
99 """
100 return dict(
101- (archive.purpose, getPubConfig(archive))
102- for archive in self.archives)
103+ (distro, dict(
104+ (archive.purpose, getPubConfig(archive))
105+ for archive in get_publishable_archives(distro)))
106+ for distro in self.distributions)
107
108- def locateIndexesMarker(self, suite):
109+ def locateIndexesMarker(self, distribution, suite):
110 """Give path for marker file whose presence marks index creation.
111
112 The file will be created once the archive indexes for `suite`
113 have been created. This is how future runs will know that this
114 work is done.
115 """
116- archive_root = self.configs[ArchivePurpose.PRIMARY].archiveroot
117- return os.path.join(archive_root, ".created-indexes-for-%s" % suite)
118+ config = self.configs[distribution][ArchivePurpose.PRIMARY]
119+ return os.path.join(
120+ config.archiveroot, ".created-indexes-for-%s" % suite)
121
122 def listSuitesNeedingIndexes(self, distroseries):
123 """Find suites in `distroseries` that need indexes created.
124@@ -265,50 +281,51 @@
125 for pocket in pocketsuffix.iterkeys()]
126 return [
127 suite for suite in suites
128- if not file_exists(self.locateIndexesMarker(suite))]
129+ if not file_exists(self.locateIndexesMarker(distro, suite))]
130
131- def markIndexCreationComplete(self, suite):
132+ def markIndexCreationComplete(self, distribution, suite):
133 """Note that archive indexes for `suite` have been created.
134
135 This tells `listSuitesNeedingIndexes` that no, this suite no
136 longer needs archive indexes to be set up.
137 """
138- with file(self.locateIndexesMarker(suite), "w") as marker:
139+ marker_name = self.locateIndexesMarker(distribution, suite)
140+ with file(marker_name, "w") as marker:
141 marker.write(
142 "Indexes for %s were created on %s.\n"
143 % (suite, datetime.now(utc)))
144
145- def createIndexes(self, suite):
146+ def createIndexes(self, distribution, suite):
147 """Create archive indexes for `distroseries`."""
148 self.logger.info("Creating archive indexes for %s.", suite)
149- self.runPublishDistro(args=['-A'], suites=[suite])
150- self.markIndexCreationComplete(suite)
151+ self.runPublishDistro(distribution, args=['-A'], suites=[suite])
152+ self.markIndexCreationComplete(distribution, suite)
153
154- def processAccepted(self):
155+ def processAccepted(self, distribution):
156 """Run the process-accepted script."""
157 self.logger.debug(
158 "Processing the accepted queue into the publishing records...")
159 script = ProcessAccepted(
160- test_args=[self.distribution.name], logger=self.logger)
161+ test_args=[distribution.name], logger=self.logger)
162 script.txn = self.txn
163 script.main()
164
165- def getDirtySuites(self):
166+ def getDirtySuites(self, distribution):
167 """Return list of suites that have packages pending publication."""
168 self.logger.debug("Querying which suites are pending publication...")
169 query_distro = LpQueryDistro(
170- test_args=['-d', self.distribution.name, "pending_suites"],
171+ test_args=['-d', distribution.name, "pending_suites"],
172 logger=self.logger)
173 receiver = StoreArgument()
174 query_distro.runAction(presenter=receiver)
175 return receiver.argument.split()
176
177- def getDirtySecuritySuites(self):
178+ def getDirtySecuritySuites(self, distribution):
179 """List security suites with pending publications."""
180- suites = self.getDirtySuites()
181+ suites = self.getDirtySuites(distribution)
182 return [suite for suite in suites if suite.endswith('-security')]
183
184- def rsyncBackupDists(self):
185+ def rsyncBackupDists(self, distribution):
186 """Populate the backup dists with a copy of distsroot.
187
188 Uses "rsync -aH --delete" so that any obsolete files that may
189@@ -316,7 +333,7 @@
190
191 :param archive_purpose: The (purpose of the) archive to copy.
192 """
193- for purpose, archive_config in self.configs.iteritems():
194+ for purpose, archive_config in self.configs[distribution].iteritems():
195 dists = get_dists(archive_config)
196 backup_dists = get_backup_dists(archive_config)
197 self.executeShell(
198@@ -345,32 +362,35 @@
199 run died while in this state, restore the directory to its
200 permanent location.
201 """
202- for archive_config in self.configs.itervalues():
203- self.recoverArchiveWorkingDir(archive_config)
204+ for distro_configs in self.configs.itervalues():
205+ for archive_config in distro_configs.itervalues():
206+ self.recoverArchiveWorkingDir(archive_config)
207
208 def setUpDirs(self):
209 """Create archive roots and such if they did not yet exist."""
210- for archive_purpose, archive_config in self.configs.iteritems():
211- archiveroot = archive_config.archiveroot
212- if not file_exists(archiveroot):
213- self.logger.debug("Creating archive root %s.", archiveroot)
214- os.makedirs(archiveroot)
215- dists = get_dists(archive_config)
216- if not file_exists(dists):
217- self.logger.debug("Creating dists root %s.", dists)
218- os.makedirs(dists)
219- distscopy = get_backup_dists(archive_config)
220- if not file_exists(distscopy):
221- self.logger.debug(
222- "Creating backup dists directory %s", distscopy)
223- os.makedirs(distscopy)
224+ for distro_configs in self.configs.itervalues():
225+ for archive_purpose, archive_config in distro_configs.iteritems():
226+ archiveroot = archive_config.archiveroot
227+ if not file_exists(archiveroot):
228+ self.logger.debug(
229+ "Creating archive root %s.", archiveroot)
230+ os.makedirs(archiveroot)
231+ dists = get_dists(archive_config)
232+ if not file_exists(dists):
233+ self.logger.debug("Creating dists root %s.", dists)
234+ os.makedirs(dists)
235+ distscopy = get_backup_dists(archive_config)
236+ if not file_exists(distscopy):
237+ self.logger.debug(
238+ "Creating backup dists directory %s", distscopy)
239+ os.makedirs(distscopy)
240
241- def runPublishDistro(self, args=[], suites=None):
242+ def runPublishDistro(self, distribution, args=[], suites=None):
243 """Execute `publish-distro`."""
244 if suites is None:
245 suites = []
246 arguments = (
247- ['-d', self.distribution.name] +
248+ ['-d', distribution.name] +
249 args +
250 sum([['-s', suite] for suite in suites], []))
251
252@@ -380,7 +400,8 @@
253 publish_distro.txn = self.txn
254 publish_distro.main()
255
256- def publishDistroArchive(self, archive, security_suites=None):
257+ def publishDistroArchive(self, distribution, archive,
258+ security_suites=None):
259 """Publish the results for an archive.
260
261 :param archive: Archive to publish.
262@@ -388,9 +409,9 @@
263 the publishing to.
264 """
265 purpose = archive.purpose
266- archive_config = self.configs[purpose]
267+ archive_config = self.configs[distribution][purpose]
268 self.logger.debug(
269- "Publishing the %s %s...", self.distribution.name, purpose.title)
270+ "Publishing the %s %s...", distribution.name, purpose.title)
271
272 # For reasons unknown, publishdistro only seems to work with a
273 # directory that's inside the archive root. So we move it there
274@@ -403,31 +424,32 @@
275
276 os.rename(get_backup_dists(archive_config), temporary_dists)
277 try:
278- self.runPublishDistro(args=arguments, suites=security_suites)
279+ self.runPublishDistro(
280+ distribution, args=arguments, suites=security_suites)
281 finally:
282 os.rename(temporary_dists, get_backup_dists(archive_config))
283
284- self.runPublishDistroParts(archive)
285+ self.runPublishDistroParts(distribution, archive)
286
287- def runPublishDistroParts(self, archive):
288+ def runPublishDistroParts(self, distribution, archive):
289 """Execute the publish-distro hooks."""
290- archive_config = self.configs[archive.purpose]
291+ archive_config = self.configs[distribution][archive.purpose]
292 env = {
293 'ARCHIVEROOT': shell_quote(archive_config.archiveroot),
294 'DISTSROOT': shell_quote(get_backup_dists(archive_config)),
295 }
296 if archive_config.overrideroot is not None:
297 env["OVERRIDEROOT"] = shell_quote(archive_config.overrideroot)
298- self.runParts('publish-distro.d', env)
299+ self.runParts(distribution, 'publish-distro.d', env)
300
301- def installDists(self):
302+ def installDists(self, distribution):
303 """Put the new dists into place, as near-atomically as possible.
304
305 For each archive, this switches the dists directory and the
306 backup dists directory around.
307 """
308 self.logger.debug("Moving the new dists into place...")
309- for archive_config in self.configs.itervalues():
310+ for archive_config in self.configs[distribution].itervalues():
311 # Use the dists "working location" as a temporary place to
312 # move the current dists out of the way for the switch. If
313 # we die in this state, the next run will know to move the
314@@ -440,12 +462,12 @@
315 os.rename(backup_dists, dists)
316 os.rename(temp_dists, backup_dists)
317
318- def generateListings(self):
319+ def generateListings(self, distribution):
320 """Create ls-lR.gz listings."""
321 self.logger.debug("Creating ls-lR.gz...")
322 lslr = "ls-lR.gz"
323 lslr_new = "." + lslr + ".new"
324- for purpose, archive_config in self.configs.iteritems():
325+ for purpose, archive_config in self.configs[distribution].iteritems():
326 lslr_file = os.path.join(archive_config.archiveroot, lslr)
327 new_lslr_file = os.path.join(archive_config.archiveroot, lslr_new)
328 if file_exists(new_lslr_file):
329@@ -457,22 +479,23 @@
330 "Failed to create %s for %s." % (lslr, purpose.title)))
331 os.rename(new_lslr_file, lslr_file)
332
333- def clearEmptyDirs(self):
334+ def clearEmptyDirs(self, distribution):
335 """Clear out any redundant empty directories."""
336- for archive_config in self.configs.itervalues():
337+ for archive_config in self.configs[distribution].itervalues():
338 self.executeShell(
339 "find '%s' -type d -empty | xargs -r rmdir"
340 % archive_config.archiveroot)
341
342- def runParts(self, parts, env):
343+ def runParts(self, distribution, parts, env):
344 """Execute run-parts.
345
346+ :param distribution: `Distribution` to execute run-parts scripts for.
347 :param parts: The run-parts directory to execute:
348 "publish-distro.d" or "finalize.d".
349 :param env: A dict of environment variables to pass to the
350 scripts in the run-parts directory.
351 """
352- parts_dir = find_run_parts_dir(self.distribution, parts)
353+ parts_dir = find_run_parts_dir(distribution, parts)
354 if parts_dir is None:
355 self.logger.debug("Skipping run-parts %s: not configured.", parts)
356 return
357@@ -482,38 +505,39 @@
358 failure=LaunchpadScriptFailure(
359 "Failure while executing run-parts %s." % parts_dir))
360
361- def runFinalizeParts(self, security_only=False):
362+ def runFinalizeParts(self, distribution, security_only=False):
363 """Run the finalize.d parts to finalize publication."""
364 archive_roots = shell_quote(' '.join([
365 archive_config.archiveroot
366- for archive_config in self.configs.itervalues()]))
367+ for archive_config in self.configs[distribution].itervalues()]))
368
369 env = {
370 'SECURITY_UPLOAD_ONLY': compose_shell_boolean(security_only),
371 'ARCHIVEROOTS': archive_roots,
372 }
373- self.runParts('finalize.d', env)
374+ self.runParts(distribution, 'finalize.d', env)
375
376- def publishSecurityUploads(self):
377+ def publishSecurityUploads(self, distribution):
378 """Quickly process just the pending security uploads."""
379 self.logger.debug("Expediting security uploads.")
380- security_suites = self.getDirtySecuritySuites()
381+ security_suites = self.getDirtySecuritySuites(distribution)
382 if len(security_suites) == 0:
383 self.logger.debug("Nothing to do for security publisher.")
384 return
385
386 self.publishDistroArchive(
387- self.distribution.main_archive, security_suites=security_suites)
388+ distribution, distribution.main_archive,
389+ security_suites=security_suites)
390
391- def publishAllUploads(self):
392+ def publishDistroUploads(self, distribution):
393 """Publish the distro's complete uploads."""
394 self.logger.debug("Full publication. This may take some time.")
395- for archive in self.archives:
396+ for archive in get_publishable_archives(distribution):
397 # This, for the main archive, is where the script spends
398 # most of its time.
399- self.publishDistroArchive(archive)
400+ self.publishDistroArchive(distribution, archive)
401
402- def publish(self, security_only=False):
403+ def publish(self, distribution, security_only=False):
404 """Do the main publishing work.
405
406 :param security_only: If True, limit publication to security
407@@ -523,13 +547,13 @@
408 """
409 try:
410 if security_only:
411- self.publishSecurityUploads()
412+ self.publishSecurityUploads(distribution)
413 else:
414- self.publishAllUploads()
415+ self.publishDistroUploads(distribution)
416
417 # Swizzle the now-updated backup dists and the current dists
418 # around.
419- self.installDists()
420+ self.installDists(distribution)
421 except:
422 # If we failed here, there's a chance that we left a
423 # working dists directory in its temporary location. If so,
424@@ -542,40 +566,45 @@
425 def setUp(self):
426 """Process options, and set up internal state."""
427 self.processOptions()
428- self.archives = self.getArchives()
429 self.configs = self.getConfigs()
430
431- def main(self):
432- """See `LaunchpadScript`."""
433- self.setUp()
434- self.recoverWorkingDists()
435-
436- for series in self.distribution.series:
437+ def processDistro(self, distribution):
438+ """Process `distribution`."""
439+ for series in distribution.series:
440 suites_needing_indexes = self.listSuitesNeedingIndexes(series)
441 if len(suites_needing_indexes) > 0:
442 for suite in suites_needing_indexes:
443- self.createIndexes(suite)
444+ self.createIndexes(distribution, suite)
445 # Don't try to do too much in one run. Leave the rest
446 # of the work for next time.
447 return
448
449- self.processAccepted()
450- self.setUpDirs()
451+ self.processAccepted(distribution)
452
453- self.rsyncBackupDists()
454- self.publish(security_only=True)
455- self.runFinalizeParts(security_only=True)
456+ self.rsyncBackupDists(distribution)
457+ self.publish(distribution, security_only=True)
458+ self.runFinalizeParts(distribution, security_only=True)
459
460 if not self.options.security_only:
461- self.rsyncBackupDists()
462- self.publish(security_only=False)
463- self.generateListings()
464- self.clearEmptyDirs()
465- self.runFinalizeParts(security_only=False)
466+ self.rsyncBackupDists(distribution)
467+ self.publish(distribution, security_only=False)
468+ self.generateListings(distribution)
469+ self.clearEmptyDirs(distribution)
470+ self.runFinalizeParts(distribution, security_only=False)
471
472 if self.options.post_rsync:
473 # Update the backup dists with the published changes. The
474 # initial rsync on the next run will not need to make any
475 # changes, and so it'll take the next run a little less
476 # time to publish its security updates.
477- self.rsyncBackupDists()
478+ self.rsyncBackupDists(distribution)
479+
480+ def main(self):
481+ """See `LaunchpadScript`."""
482+ self.setUp()
483+ self.recoverWorkingDists()
484+ self.setUpDirs()
485+
486+ for distribution in self.distributions:
487+ self.processDistro(distribution)
488+ self.txn.commit()
489
490=== modified file 'lib/lp/archivepublisher/tests/test_publish_ftpmaster.py'
491--- lib/lp/archivepublisher/tests/test_publish_ftpmaster.py 2011-08-03 06:24:53 +0000
492+++ lib/lp/archivepublisher/tests/test_publish_ftpmaster.py 2011-08-15 11:04:18 +0000
493@@ -116,10 +116,11 @@
494
495
496 def get_marker_files(script, distroseries):
497- suites = [
498- distroseries.getSuite(pocket)
499- for pocket in pocketsuffix.iterkeys()]
500- return [script.locateIndexesMarker(suite) for suite in suites]
501+ """Return filesystem paths for all indexes markers for `distroseries`."""
502+ suites = [
503+ distroseries.getSuite(pocket) for pocket in pocketsuffix.iterkeys()]
504+ distro = distroseries.distribution
505+ return [script.locateIndexesMarker(distro, suite) for suite in suites]
506
507
508 class HelpersMixin:
509@@ -289,6 +290,22 @@
510 self.SCRIPT_PATH + " -d ubuntu")
511 self.assertEqual(0, retval, "Script failure:\n" + stderr)
512
513+ def test_getConfigs_maps_distro_and_purpose_to_matching_config(self):
514+ distro = self.makeDistroWithPublishDirectory()
515+ script = self.makeScript(distro)
516+ script.setUp()
517+ reference_config = getPubConfig(distro.main_archive)
518+ config = script.getConfigs()[distro][ArchivePurpose.PRIMARY]
519+ self.assertEqual(reference_config.temproot, config.temproot)
520+ self.assertEqual(reference_config.distroroot, config.distroroot)
521+ self.assertEqual(reference_config.archiveroot, config.archiveroot)
522+
523+ def test_getConfigs_maps_distros(self):
524+ distro = self.makeDistroWithPublishDirectory()
525+ script = self.makeScript(distro)
526+ script.setUp()
527+ self.assertEqual([distro], script.getConfigs().keys())
528+
529 def test_script_is_happy_with_no_publications(self):
530 distro = self.makeDistroWithPublishDirectory()
531 self.makeScript(distro).main()
532@@ -366,9 +383,11 @@
533
534 def test_getDirtySuites_returns_suite_with_pending_publication(self):
535 spph = self.factory.makeSourcePackagePublishingHistory()
536+ distro = spph.distroseries.distribution
537 script = self.makeScript(spph.distroseries.distribution)
538 script.setUp()
539- self.assertEqual([name_spph_suite(spph)], script.getDirtySuites())
540+ self.assertEqual(
541+ [name_spph_suite(spph)], script.getDirtySuites(distro))
542
543 def test_getDirtySuites_returns_suites_with_pending_publications(self):
544 distro = self.makeDistroWithPublishDirectory()
545@@ -382,14 +401,15 @@
546 script.setUp()
547 self.assertContentEqual(
548 [name_spph_suite(spph) for spph in spphs],
549- script.getDirtySuites())
550+ script.getDirtySuites(distro))
551
552 def test_getDirtySuites_ignores_suites_without_pending_publications(self):
553 spph = self.factory.makeSourcePackagePublishingHistory(
554 status=PackagePublishingStatus.PUBLISHED)
555+ distro = spph.distroseries.distribution
556 script = self.makeScript(spph.distroseries.distribution)
557 script.setUp()
558- self.assertEqual([], script.getDirtySuites())
559+ self.assertEqual([], script.getDirtySuites(distro))
560
561 def test_getDirtySecuritySuites_returns_security_suites(self):
562 distro = self.makeDistroWithPublishDirectory()
563@@ -404,7 +424,7 @@
564 script.setUp()
565 self.assertContentEqual(
566 [name_spph_suite(spph) for spph in spphs],
567- script.getDirtySecuritySuites())
568+ script.getDirtySecuritySuites(distro))
569
570 def test_getDirtySecuritySuites_ignores_non_security_suites(self):
571 distroseries = self.factory.makeDistroSeries()
572@@ -419,7 +439,8 @@
573 distroseries=distroseries, pocket=pocket)
574 script = self.makeScript(distroseries.distribution)
575 script.setUp()
576- self.assertEqual([], script.getDirtySecuritySuites())
577+ self.assertEqual(
578+ [], script.getDirtySecuritySuites(distroseries.distribution))
579
580 def test_rsync_copies_files(self):
581 distro = self.makeDistroWithPublishDirectory()
582@@ -431,7 +452,7 @@
583 os.makedirs(dists_backup)
584 os.makedirs(dists_root)
585 write_marker_file([dists_root, "new-file"], "New file")
586- script.rsyncBackupDists()
587+ script.rsyncBackupDists(distro)
588 self.assertEqual(
589 "New file", read_marker_file([dists_backup, "new-file"]))
590
591@@ -445,7 +466,7 @@
592 old_file = [dists_backup, "old-file"]
593 write_marker_file(old_file, "old-file")
594 os.makedirs(get_dists_root(get_pub_config(distro)))
595- script.rsyncBackupDists()
596+ script.rsyncBackupDists(distro)
597 self.assertFalse(path_exists(*old_file))
598
599 def test_setUpDirs_creates_directory_structure(self):
600@@ -478,10 +499,11 @@
601 script.setUp()
602 script.setUpDirs()
603 script.runParts = FakeMethod()
604- script.publishDistroArchive(distro.main_archive)
605+ script.publishDistroArchive(distro, distro.main_archive)
606 self.assertEqual(1, script.runParts.call_count)
607 args, kwargs = script.runParts.calls[0]
608- parts_dir, env = args
609+ run_distro, parts_dir, env = args
610+ self.assertEqual(distro, run_distro)
611 self.assertEqual("publish-distro.d", parts_dir)
612
613 def test_runPublishDistroParts_passes_parameters(self):
614@@ -490,9 +512,9 @@
615 script.setUp()
616 script.setUpDirs()
617 script.runParts = FakeMethod()
618- script.runPublishDistroParts(distro.main_archive)
619+ script.runPublishDistroParts(distro, distro.main_archive)
620 args, kwargs = script.runParts.calls[0]
621- parts_dir, env = args
622+ run_distro, parts_dir, env = args
623 required_parameters = set([
624 "ARCHIVEROOT", "DISTSROOT", "OVERRIDEROOT"])
625 missing_parameters = required_parameters.difference(set(env.keys()))
626@@ -503,7 +525,7 @@
627 script = self.makeScript(distro)
628 script.setUp()
629 script.setUpDirs()
630- script.generateListings()
631+ script.generateListings(distro)
632 pass
633
634 def test_clearEmptyDirs_cleans_up_empty_directories(self):
635@@ -514,7 +536,7 @@
636 empty_dir = os.path.join(
637 get_dists_root(get_pub_config(distro)), 'empty-dir')
638 os.makedirs(empty_dir)
639- script.clearEmptyDirs()
640+ script.clearEmptyDirs(distro)
641 self.assertFalse(file_exists(empty_dir))
642
643 def test_clearEmptyDirs_does_not_clean_up_nonempty_directories(self):
644@@ -526,7 +548,7 @@
645 get_dists_root(get_pub_config(distro)), 'nonempty-dir')
646 os.makedirs(nonempty_dir)
647 write_marker_file([nonempty_dir, "placeholder"], "Data here!")
648- script.clearEmptyDirs()
649+ script.clearEmptyDirs(distro)
650 self.assertTrue(file_exists(nonempty_dir))
651
652 def test_processOptions_finds_distribution(self):
653@@ -534,7 +556,19 @@
654 script = self.makeScript(distro)
655 script.processOptions()
656 self.assertEqual(distro.name, script.options.distribution)
657- self.assertEqual(distro, script.distribution)
658+ self.assertEqual([distro], script.distributions)
659+
660+ def test_processOptions_for_all_derived_finds_derived_distros(self):
661+ dsp = self.factory.makeDistroSeriesParent()
662+ script = PublishFTPMaster(test_args=['--all-derived'])
663+ script.processOptions()
664+ self.assertIn(dsp.derived_series.distribution, script.distributions)
665+
666+ def test_processOptions_for_all_derived_ignores_nonderived_distros(self):
667+ distro = self.factory.makeDistribution()
668+ script = PublishFTPMaster(test_args=['--all-derived'])
669+ script.processOptions()
670+ self.assertNotIn(distro, script.distributions)
671
672 def test_processOptions_complains_about_unknown_distribution(self):
673 script = self.makeScript()
674@@ -545,8 +579,9 @@
675 self.enableRunParts()
676 script = self.makeScript(self.prepareUbuntu())
677 script.setUp()
678+ distro = script.distributions[0]
679 script.executeShell = FakeMethod()
680- script.runParts("finalize.d", {})
681+ script.runParts(distro, "finalize.d", {})
682 self.assertEqual(1, script.executeShell.call_count)
683 args, kwargs = script.executeShell.calls[-1]
684 command_line, = args
685@@ -559,10 +594,11 @@
686 self.enableRunParts()
687 script = self.makeScript(self.prepareUbuntu())
688 script.setUp()
689+ distro = script.distributions[0]
690 script.executeShell = FakeMethod()
691 key = self.factory.getUniqueString()
692 value = self.factory.getUniqueString()
693- script.runParts("finalize.d", {key: value})
694+ script.runParts(distro, "finalize.d", {key: value})
695 args, kwargs = script.executeShell.calls[-1]
696 command_line, = args
697 self.assertIn("%s=%s" % (key, value), command_line)
698@@ -595,10 +631,11 @@
699 def test_runFinalizeParts_passes_parameters(self):
700 script = self.makeScript(self.prepareUbuntu())
701 script.setUp()
702+ distro = script.distributions[0]
703 script.runParts = FakeMethod()
704- script.runFinalizeParts()
705+ script.runFinalizeParts(distro)
706 args, kwargs = script.runParts.calls[0]
707- parts_dir, env = args
708+ run_distro, parts_dir, env = args
709 required_parameters = set(["ARCHIVEROOTS", "SECURITY_UPLOAD_ONLY"])
710 missing_parameters = required_parameters.difference(set(env.keys()))
711 self.assertEqual(set(), missing_parameters)
712@@ -606,12 +643,13 @@
713 def test_publishSecurityUploads_skips_pub_if_no_security_updates(self):
714 script = self.makeScript()
715 script.setUp()
716+ distro = script.distributions[0]
717 script.setUpDirs()
718 script.installDists = FakeMethod()
719- script.publishSecurityUploads()
720+ script.publishSecurityUploads(distro)
721 self.assertEqual(0, script.installDists.call_count)
722
723- def test_publishAllUploads_publishes_all_distro_archives(self):
724+ def test_publishDistroUploads_publishes_all_distro_archives(self):
725 distro = self.makeDistroWithPublishDirectory()
726 distroseries = self.factory.makeDistroSeries(distribution=distro)
727 partner_archive = self.factory.makeArchive(
728@@ -624,9 +662,9 @@
729 script.setUp()
730 script.setUpDirs()
731 script.publishDistroArchive = FakeMethod()
732- script.publishAllUploads()
733+ script.publishDistroUploads(distro)
734 published_archives = [
735- args[0] for args, kwargs in script.publishDistroArchive.calls]
736+ args[1] for args, kwargs in script.publishDistroArchive.calls]
737
738 self.assertContentEqual(
739 distro.all_distro_archives, published_archives)
740@@ -648,7 +686,7 @@
741 script.logger = BufferLogger()
742 script.logger.setLevel(logging.INFO)
743 script.setUpDirs()
744- archive_config = script.configs[ArchivePurpose.PRIMARY]
745+ archive_config = getPubConfig(distro.main_archive)
746 backup_dists = os.path.join(
747 archive_config.archiveroot + "-distscopy", "dists")
748 working_dists = get_working_dists(archive_config)
749@@ -677,16 +715,16 @@
750 args, kwargs = script.publish.calls[0]
751 self.assertEqual({'security_only': True}, kwargs)
752
753- def test_publishAllUploads_processes_all_archives(self):
754+ def test_publishDistroUploads_processes_all_archives(self):
755 distro = self.makeDistroWithPublishDirectory()
756 partner_archive = self.factory.makeArchive(
757 distribution=distro, purpose=ArchivePurpose.PARTNER)
758 script = self.makeScript(distro)
759 script.publishDistroArchive = FakeMethod()
760 script.setUp()
761- script.publishAllUploads()
762+ script.publishDistroUploads(distro)
763 published_archives = [
764- args[0] for args, kwargs in script.publishDistroArchive.calls]
765+ args[1] for args, kwargs in script.publishDistroArchive.calls]
766 self.assertContentEqual(
767 [distro.main_archive, partner_archive], published_archives)
768
769@@ -726,7 +764,7 @@
770 done
771 """))
772
773- script.runFinalizeParts()
774+ script.runFinalizeParts(distro)
775
776 for archive in [distro.main_archive, distro.getArchive("partner")]:
777 archive_root = getPubConfig(archive).archiveroot
778@@ -745,20 +783,24 @@
779
780 message = self.factory.getUniqueString()
781 script = self.makeScript()
782- script.publishAllUploads = FakeMethod(failure=MoonPhaseError(message))
783+ script.publishDistroUploads = FakeMethod(
784+ failure=MoonPhaseError(message))
785 script.setUp()
786- self.assertRaisesWithContent(MoonPhaseError, message, script.publish)
787+ self.assertRaisesWithContent(
788+ MoonPhaseError, message,
789+ script.publish, script.distributions[0])
790
791 def test_publish_obeys_keyboard_interrupt(self):
792 # Similar to an Exception, a keyboard interrupt does not get
793 # swallowed.
794 message = self.factory.getUniqueString()
795 script = self.makeScript()
796- script.publishAllUploads = FakeMethod(
797+ script.publishDistroUploads = FakeMethod(
798 failure=KeyboardInterrupt(message))
799 script.setUp()
800 self.assertRaisesWithContent(
801- KeyboardInterrupt, message, script.publish)
802+ KeyboardInterrupt, message,
803+ script.publish, script.distributions[0])
804
805 def test_publish_recovers_working_dists_on_exception(self):
806 # If an Exception comes up while publishing, the publish method
807@@ -769,12 +811,12 @@
808 failure = MoonPhaseError(self.factory.getUniqueString())
809
810 script = self.makeScript()
811- script.publishAllUploads = FakeMethod(failure=failure)
812+ script.publishDistroUploads = FakeMethod(failure=failure)
813 script.recoverArchiveWorkingDir = FakeMethod()
814 script.setUp()
815
816 try:
817- script.publish()
818+ script.publish(script.distributions[0])
819 except MoonPhaseError:
820 pass
821
822@@ -786,12 +828,12 @@
823 failure = KeyboardInterrupt("Ctrl-C!")
824
825 script = self.makeScript()
826- script.publishAllUploads = FakeMethod(failure=failure)
827+ script.publishDistroUploads = FakeMethod(failure=failure)
828 script.recoverArchiveWorkingDir = FakeMethod()
829 script.setUp()
830
831 try:
832- script.publish()
833+ script.publish(script.distributions[0])
834 except KeyboardInterrupt:
835 pass
836
837@@ -804,7 +846,8 @@
838
839 def createIndexesMarkerDir(self, script, distroseries):
840 """Create the directory for `distroseries`'s indexes marker."""
841- marker = script.locateIndexesMarker(get_a_suite(distroseries))
842+ marker = script.locateIndexesMarker(
843+ distroseries.distribution, get_a_suite(distroseries))
844 os.makedirs(os.path.dirname(marker))
845
846 def makeDistroSeriesNeedingIndexes(self, distribution=None):
847@@ -860,7 +903,7 @@
848
849 needful_suites = script.listSuitesNeedingIndexes(series)
850 suite = get_a_suite(series)
851- script.markIndexCreationComplete(suite)
852+ script.markIndexCreationComplete(distro, suite)
853 needful_suites.remove(suite)
854 self.assertContentEqual(
855 needful_suites, script.listSuitesNeedingIndexes(series))
856@@ -885,9 +928,9 @@
857 script.markIndexCreationComplete = FakeMethod()
858 script.runPublishDistro = FakeMethod()
859 suite = get_a_suite(series)
860- script.createIndexes(suite)
861+ script.createIndexes(distro, suite)
862 self.assertEqual(
863- [((suite, ), {})], script.markIndexCreationComplete.calls)
864+ [((distro, suite), {})], script.markIndexCreationComplete.calls)
865
866 def test_failed_index_creation_is_not_marked_complete(self):
867 # If index creation fails, it is not marked as having been
868@@ -900,7 +943,7 @@
869 script.markIndexCreationComplete = FakeMethod()
870 script.runPublishDistro = FakeMethod(failure=Boom("Sorry!"))
871 try:
872- script.createIndexes(get_a_suite(series))
873+ script.createIndexes(series.distribution, get_a_suite(series))
874 except:
875 pass
876 self.assertEqual([], script.markIndexCreationComplete.calls)
877@@ -911,9 +954,10 @@
878 series = self.factory.makeDistroSeries()
879 script = self.makeScript(series.distribution)
880 script.setUp()
881- archive_root = script.configs[ArchivePurpose.PRIMARY].archiveroot
882+ archive_root = getPubConfig(series.main_archive).archiveroot
883 self.assertThat(
884- script.locateIndexesMarker(get_a_suite(series)),
885+ script.locateIndexesMarker(
886+ series.distribution, get_a_suite(series)),
887 StartsWith(os.path.normpath(archive_root)))
888
889 def test_locateIndexesMarker_uses_separate_files_per_suite(self):
890@@ -946,7 +990,8 @@
891 script.setUp()
892 suite = get_a_suite(series)
893 self.assertThat(
894- os.path.basename(script.locateIndexesMarker(suite)),
895+ os.path.basename(script.locateIndexesMarker(
896+ series.distribution, suite)),
897 StartsWith("."))
898
899 def test_script_calls_createIndexes_for_new_series(self):
900@@ -958,7 +1003,7 @@
901 script.createIndexes = FakeMethod()
902 script.main()
903 expected_calls = [
904- ((series.getSuite(pocket), ), {})
905+ ((distro, series.getSuite(pocket)), {})
906 for pocket in pocketsuffix.iterkeys()]
907 self.assertContentEqual(expected_calls, script.createIndexes.calls)
908
909@@ -974,7 +1019,7 @@
910 self.createIndexesMarkerDir(script, series)
911 suite = get_a_suite(series)
912
913- script.createIndexes(suite)
914+ script.createIndexes(distro, suite)
915
916 args, kwargs = script.runPublishDistro.calls[0]
917 self.assertEqual([suite], kwargs['suites'])
918@@ -994,6 +1039,6 @@
919 script.main()
920 self.assertEqual([], script.listSuitesNeedingIndexes(series))
921 sources = os.path.join(
922- script.configs[ArchivePurpose.PRIMARY].distsroot,
923+ getPubConfig(series.main_archive).distsroot,
924 series.name, "main", "source", "Sources")
925 self.assertTrue(file_exists(sources))