Merge lp:~wgrant/launchpad/bug-629835-copier-architectures into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 12220
Proposed branch: lp:~wgrant/launchpad/bug-629835-copier-architectures
Merge into: lp:launchpad
Prerequisite: lp:~wgrant/launchpad/bug-701383-ppa-component-override
Diff against target: 481 lines (+217/-124)
6 files modified
lib/lp/soyuz/interfaces/publishing.py (+23/-0)
lib/lp/soyuz/model/publishing.py (+56/-47)
lib/lp/soyuz/model/queue.py (+11/-41)
lib/lp/soyuz/scripts/packagecopier.py (+9/-1)
lib/lp/soyuz/scripts/tests/test_copypackage.py (+116/-34)
lib/lp/soyuz/tests/test_publishing.py (+2/-1)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-629835-copier-architectures
Reviewer Review Type Date Requested Status
Julian Edwards (community) code Approve
Review via email: mp+46233@code.launchpad.net

Commit message

[r=julian-edwards][ui=none][bug=629835,649859,701357] Architecture-independent binary publication logic has been de-duplicated, and binaries will no longer be copied to disabled or removed architectures.

Description of the change

This branch fixes three architecture-related copy bugs:

 - Bug #629835: Delayed copies didn't take into account the
   architectures in the target series, so they could attempt to publish
   builds into a non-existent DistroArchSeries, crashing
   process-accepted.py. I fixed _do_delayed_copy to skip a build if
   there is no corresponding DAS in the target.

 - Bug #649859: PackageUploadBuild.publish (build publishing) and
   PublishingSet.copyBinariesTo (binary copying) had separate buggy
   implementations of the same architecture-independent copying logic.
   I factored these both out into PublishingSet.publishBinary, wrapping
   PublishingSet.newBinaryPublication to automatically create all
   relevant publications.

 - Bug #701357: We recently added the DistroArchSeries.enabled flag,
   which allows us to prevent builds and publications for a particular
   architecture, effectively removing it. However, copies do not respect
   this, and will happily create new publications in a disabled DAS.
   _do_delayed_copy and copyBinariesTo have been fixed to skip builds
   targeting disabled DASes, and newBinaryPublication refuses to create
   publications in them.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

95 + # XXX: wgrant 2011-01-10:
96 + # This will go wrong if nominatedarchindep gets deleted in a
97 + # future series.

As discussed, this is not an XXX (which by policy would require a bug).

141 + if binaries_in_destination.count() == 0:

This should be a bool(binaries_in_destination) if it hasn't already conflicted with my branch.

Everything else looks great, nice job.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/soyuz/interfaces/publishing.py'
2--- lib/lp/soyuz/interfaces/publishing.py 2010-12-09 16:22:27 +0000
3+++ lib/lp/soyuz/interfaces/publishing.py 2011-01-15 07:18:47 +0000
4@@ -878,6 +878,29 @@
5 publishing histories.
6 """
7
8+ def publishBinary(archive, binarypackagerelease, distroarchseries,
9+ component, section, priority, pocket):
10+ """Publish a `BinaryPackageRelease` in an archive.
11+
12+ Creates one or more `IBinaryPackagePublishingHistory` records,
13+ handling architecture-independent and DDEB publications transparently.
14+
15+ Note that binaries will only be copied if they don't already exist in
16+ the target; this method cannot be used to change overrides.
17+
18+ :param archive: The target `IArchive`.
19+ :param binarypackagerelease: The `IBinaryPackageRelease` to copy.
20+ :param distroarchseries: An `IDistroArchSeries`. If the binary is
21+ architecture-independent, it will be published to all enabled
22+ architectures in this series.
23+ :param component: The target `IComponent`.
24+ :param section: The target `ISection`.
25+ :param priority: The target `PackagePublishingPriority`.
26+ :param pocket: The target `PackagePublishingPocket`.
27+
28+ :return: A list of new `IBinaryPackagePublishingHistory` records.
29+ """
30+
31 def newBinaryPublication(archive, binarypackagerelease, distroarchseries,
32 component, section, priority, pocket):
33 """Create a new `BinaryPackagePublishingHistory`.
34
35=== modified file 'lib/lp/soyuz/model/publishing.py'
36--- lib/lp/soyuz/model/publishing.py 2011-01-14 23:50:24 +0000
37+++ lib/lp/soyuz/model/publishing.py 2011-01-15 07:18:47 +0000
38@@ -92,6 +92,7 @@
39 ISourcePackagePublishingHistory,
40 PoolFileOverwriteError,
41 )
42+from lp.soyuz.interfaces.queue import QueueInconsistentStateError
43 from lp.soyuz.model.binarypackagename import BinaryPackageName
44 from lp.soyuz.model.binarypackagerelease import (
45 BinaryPackageRelease,
46@@ -1258,58 +1259,68 @@
47 def copyBinariesTo(self, binaries, distroseries, pocket, archive):
48 """See `IPublishingSet`."""
49 secure_copies = []
50-
51 for binary in binaries:
52- binarypackagerelease = binary.binarypackagerelease
53-
54- # XXX 2010-09-28 Julian bug=649859
55- # This piece of code duplicates the logic in
56- # PackageUploadBuild.publish(), it needs to be refactored.
57-
58- if binarypackagerelease.architecturespecific:
59- # If the binary is architecture specific and the target
60- # distroseries does not include the architecture then we
61- # skip the binary and continue.
62- try:
63- # For safety, we use the architecture the binary was
64- # built, and not the one it is published, coping with
65- # single arch-indep publications for architectures that
66- # do not exist in the destination series.
67- # See #387589 for more information.
68- target_architecture = distroseries[
69- binarypackagerelease.build.arch_tag]
70- except NotFoundError:
71- continue
72- destination_architectures = [target_architecture]
73- else:
74- destination_architectures = [
75- arch for arch in distroseries.architectures
76- if arch.enabled]
77-
78- for distroarchseries in destination_architectures:
79-
80- # We only copy the binary if it doesn't already exist
81- # in the destination.
82- binary_in_destination = archive.getAllPublishedBinaries(
83- name=binarypackagerelease.name, exact_match=True,
84- version=binarypackagerelease.version,
85- status=active_publishing_status, pocket=pocket,
86- distroarchseries=distroarchseries)
87-
88- if not bool(binary_in_destination):
89- pub = self.newBinaryPublication(
90- archive, binarypackagerelease, distroarchseries,
91- binary.component, binary.section, binary.priority,
92- pocket)
93- secure_copies.append(pub)
94-
95+ # This will go wrong if nominatedarchindep gets deleted in a
96+ # future series -- it will attempt to retrieve i386 from the
97+ # new series, fail, and skip the publication instead of
98+ # publishing the remaining archs.
99+ try:
100+ build = binary.binarypackagerelease.build
101+ target_architecture = distroseries[
102+ build.distro_arch_series.architecturetag]
103+ except NotFoundError:
104+ continue
105+ if not target_architecture.enabled:
106+ continue
107+ secure_copies.extend(
108+ getUtility(IPublishingSet).publishBinary(
109+ archive, binary.binarypackagerelease, target_architecture,
110+ binary.component, binary.section, binary.priority,
111+ pocket))
112 return secure_copies
113
114+ def publishBinary(self, archive, binarypackagerelease, distroarchseries,
115+ component, section, priority, pocket):
116+ """See `IPublishingSet`."""
117+ if not binarypackagerelease.architecturespecific:
118+ target_archs = distroarchseries.distroseries.enabled_architectures
119+ else:
120+ target_archs = [distroarchseries]
121+
122+ # DDEBs targeted to the PRIMARY archive are published in the
123+ # corresponding DEBUG archive.
124+ if binarypackagerelease.binpackageformat == BinaryPackageFormat.DDEB:
125+ debug_archive = archive.debug_archive
126+ if debug_archive is None:
127+ raise QueueInconsistentStateError(
128+ "Could not find the corresponding DEBUG archive "
129+ "for %s" % (archive.displayname))
130+ archive = debug_archive
131+
132+ published_binaries = []
133+ for arch in target_archs:
134+ # We only publish the binary if it doesn't already exist in
135+ # the destination. Note that this means we don't support
136+ # override changes on their own.
137+ binaries_in_destination = archive.getAllPublishedBinaries(
138+ name=binarypackagerelease.name, exact_match=True,
139+ version=binarypackagerelease.version,
140+ status=active_publishing_status, pocket=pocket,
141+ distroarchseries=distroarchseries)
142+ if not bool(binaries_in_destination):
143+ published_binaries.append(
144+ getUtility(IPublishingSet).newBinaryPublication(
145+ archive, binarypackagerelease, arch, component,
146+ section, priority, pocket))
147+ return published_binaries
148+
149 def newBinaryPublication(self, archive, binarypackagerelease,
150 distroarchseries, component, section, priority,
151 pocket):
152 """See `IPublishingSet`."""
153- pub = BinaryPackagePublishingHistory(
154+ assert distroarchseries.enabled, (
155+ "Will not create new publications in a disabled architecture.")
156+ return BinaryPackagePublishingHistory(
157 archive=archive,
158 binarypackagerelease=binarypackagerelease,
159 distroarchseries=distroarchseries,
160@@ -1321,8 +1332,6 @@
161 datecreated=UTC_NOW,
162 pocket=pocket)
163
164- return pub
165-
166 def newSourcePublication(self, archive, sourcepackagerelease,
167 distroseries, component, section, pocket,
168 ancestor=None):
169
170=== modified file 'lib/lp/soyuz/model/queue.py'
171--- lib/lp/soyuz/model/queue.py 2011-01-12 13:43:02 +0000
172+++ lib/lp/soyuz/model/queue.py 2011-01-15 07:18:47 +0000
173@@ -1448,54 +1448,24 @@
174 target_das.distroseries.name,
175 build_archtag))
176
177- # Get the other enabled distroarchseries for this
178- # distroseries. If the binary is architecture independent then
179- # we need to publish it in all of those too.
180-
181- # XXX Julian 2010-09-28 bug=649859
182- # This logic is duplicated in
183- # PackagePublishingSet.copyBinariesTo() and should be
184- # refactored.
185- other_das = set(
186- arch for arch in self.packageupload.distroseries.architectures
187- if arch.enabled)
188- other_das = other_das - set([target_das])
189 # First up, publish everything in this build into that dar.
190 published_binaries = []
191 for binary in self.build.binarypackages:
192- target_dars = set([target_das])
193- if not binary.architecturespecific:
194- target_dars = target_dars.union(other_das)
195- debug(logger, "... %s/%s (Arch Independent)" % (
196- binary.binarypackagename.name,
197- binary.version))
198- else:
199- debug(logger, "... %s/%s (Arch Specific)" % (
200- binary.binarypackagename.name,
201- binary.version))
202-
203-
204- archive = self.packageupload.archive
205- # DDEBs targeted to the PRIMARY archive are published in the
206- # corresponding DEBUG archive.
207- if binary.binpackageformat == BinaryPackageFormat.DDEB:
208- debug_archive = archive.debug_archive
209- if debug_archive is None:
210- raise QueueInconsistentStateError(
211- "Could not find the corresponding DEBUG archive "
212- "for %s" % (archive.displayname))
213- archive = debug_archive
214-
215- for each_target_dar in target_dars:
216- bpph = getUtility(IPublishingSet).newBinaryPublication(
217- archive=archive,
218+ debug(
219+ logger, "... %s/%s (Arch %s)" % (
220+ binary.binarypackagename.name,
221+ binary.version,
222+ 'Specific' if binary.architecturespecific else 'Independent',
223+ ))
224+ published_binaries.extend(
225+ getUtility(IPublishingSet).publishBinary(
226+ archive=self.packageupload.archive,
227 binarypackagerelease=binary,
228- distroarchseries=each_target_dar,
229+ distroarchseries=target_das,
230 component=binary.component,
231 section=binary.section,
232 priority=binary.priority,
233- pocket=self.packageupload.pocket)
234- published_binaries.append(bpph)
235+ pocket=self.packageupload.pocket))
236 return published_binaries
237
238
239
240=== modified file 'lib/lp/soyuz/scripts/packagecopier.py'
241--- lib/lp/soyuz/scripts/packagecopier.py 2011-01-12 15:36:59 +0000
242+++ lib/lp/soyuz/scripts/packagecopier.py 2011-01-15 07:18:47 +0000
243@@ -25,6 +25,7 @@
244
245 from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
246 from canonical.librarian.utils import copy_and_close
247+from lp.app.errors import NotFoundError
248 from lp.buildmaster.enums import BuildStatus
249 from lp.soyuz.adapters.packagelocation import build_package_location
250 from lp.soyuz.enums import ArchivePurpose
251@@ -635,7 +636,14 @@
252 # If binaries are included in the copy we include binary custom files.
253 if include_binaries:
254 for build in source.getBuilds():
255- if build.status != BuildStatus.FULLYBUILT:
256+ # Don't copy builds that aren't yet done, or those without a
257+ # corresponding enabled architecture in the new series.
258+ try:
259+ target_arch = series[build.arch_tag]
260+ except NotFoundError:
261+ continue
262+ if (not target_arch.enabled or
263+ build.status != BuildStatus.FULLYBUILT):
264 continue
265 delayed_copy.addBuild(build)
266 original_build_upload = build.package_upload
267
268=== modified file 'lib/lp/soyuz/scripts/tests/test_copypackage.py'
269--- lib/lp/soyuz/scripts/tests/test_copypackage.py 2010-12-23 01:02:00 +0000
270+++ lib/lp/soyuz/scripts/tests/test_copypackage.py 2011-01-15 07:18:47 +0000
271@@ -929,15 +929,100 @@
272 self.assertFalse(checked_copy.delayed)
273
274
275-class DoDirectCopyTestCase(TestCaseWithFactory):
276+class BaseDoCopyTests:
277
278 layer = LaunchpadZopelessLayer
279
280+ def createNobby(self, archs):
281+ """Create a new 'nobby' series with the given architecture tags.
282+
283+ The first is used as nominatedarchindep.
284+ """
285+ nobby = self.factory.makeDistroSeries(
286+ distribution=self.test_publisher.ubuntutest, name='nobby')
287+ for arch in archs:
288+ pf = self.factory.makeProcessorFamily(name='my_%s' % arch)
289+ self.factory.makeDistroArchSeries(
290+ distroseries=nobby, architecturetag=arch,
291+ processorfamily=pf)
292+ nobby.nominatedarchindep = nobby[archs[0]]
293+ self.test_publisher.addFakeChroots(nobby)
294+ return nobby
295+
296+ def assertCopied(self, copies, series, arch_tags):
297+ raise NotImplementedError
298+
299+ def doCopy(self, source, archive, series, pocket, include_binaries):
300+ raise NotImplementedError
301+
302+ def test_does_not_copy_disabled_arches(self):
303+ # When copying binaries to a new series, we must not copy any
304+ # into disabled architectures.
305+
306+ # Make a new architecture-specific source and binary.
307+ archive = self.factory.makeArchive(
308+ distribution=self.test_publisher.ubuntutest, virtualized=False)
309+ source = self.test_publisher.getPubSource(
310+ archive=archive, architecturehintlist='any')
311+ [bin_i386, bin_hppa] = self.test_publisher.getPubBinaries(
312+ pub_source=source)
313+
314+ # Now make a new distroseries with two architectures, one of
315+ # which is disabled.
316+ nobby = self.createNobby(('i386', 'hppa'))
317+ nobby['hppa'].enabled = False
318+
319+ # Now we can copy the package with binaries.
320+ target_archive = self.factory.makeArchive(
321+ distribution=self.test_publisher.ubuntutest, virtualized=False)
322+ copies = self.doCopy(
323+ source, target_archive, nobby, source.pocket, True)
324+
325+ # The binary should not be published for hppa.
326+ self.assertCopied(copies, nobby, ('i386',))
327+
328+ def test_does_not_copy_removed_arches(self):
329+ # When copying binaries to a new series, we must not try to copy
330+ # any into architectures that no longer exist.
331+
332+ # Make a new architecture-specific source and binary.
333+ archive = self.factory.makeArchive(
334+ distribution=self.test_publisher.ubuntutest, virtualized=False)
335+ source = self.test_publisher.getPubSource(
336+ archive=archive, architecturehintlist='any')
337+ [bin_i386, bin_hppa] = self.test_publisher.getPubBinaries(
338+ pub_source=source)
339+
340+ # Now make a new distroseries with only i386.
341+ nobby = self.createNobby(('i386',))
342+
343+ # Now we can copy the package with binaries.
344+ target_archive = self.factory.makeArchive(
345+ distribution=self.test_publisher.ubuntutest, virtualized=False)
346+ copies = self.doCopy(
347+ source, target_archive, nobby, source.pocket, True)
348+
349+ # The copy succeeds, and no hppa publication is present.
350+ self.assertCopied(copies, nobby, ('i386',))
351+
352+
353+class TestDoDirectCopy(TestCaseWithFactory, BaseDoCopyTests):
354+
355 def setUp(self):
356- super(DoDirectCopyTestCase, self).setUp()
357+ super(TestDoDirectCopy, self).setUp()
358 self.test_publisher = SoyuzTestPublisher()
359 self.test_publisher.prepareBreezyAutotest()
360
361+ def assertCopied(self, copies, series, arch_tags):
362+ self.assertEquals(
363+ [u'foo 666 in %s' % series.name] +
364+ [u'foo-bin 666 in %s %s' % (series.name, arch_tag)
365+ for arch_tag in arch_tags],
366+ [copy.displayname for copy in copies])
367+
368+ def doCopy(self, source, archive, series, pocket, include_binaries):
369+ return _do_direct_copy(source, archive, series, pocket, True)
370+
371 def testCanCopyArchIndependentBinariesBuiltInAnUnsupportedArch(self):
372 # _do_direct_copy() uses the binary candidate build architecture,
373 # instead of the publish one, in other to check if it's
374@@ -968,14 +1053,11 @@
375 self.layer.txn.commit()
376
377 # Copy succeeds.
378- copies = _do_direct_copy(
379- source, source.archive, hoary_test, source.pocket, True)
380- self.assertEquals(
381- ['foo 666 in hoary-test',
382- 'foo-bin 666 in hoary-test amd64',
383- 'foo-bin 666 in hoary-test i386',
384- ],
385- [copy.displayname for copy in copies])
386+ target_archive = self.factory.makeArchive(
387+ distribution=self.test_publisher.ubuntutest, virtualized=False)
388+ copies = self.doCopy(
389+ source, target_archive, hoary_test, source.pocket, True)
390+ self.assertCopied(copies, hoary_test, ('amd64', 'i386'))
391
392 def test_copying_arch_indep_binaries_with_disabled_arches(self):
393 # When copying an arch-indep binary to a new series, we must not
394@@ -991,42 +1073,31 @@
395
396 # Now make a new distroseries with two architectures, one of
397 # which is disabled.
398- nobby = self.factory.makeDistroSeries(
399- distribution=self.test_publisher.ubuntutest, name='nobby')
400- i386_pf = self.factory.makeProcessorFamily(name='my_i386')
401- nobby_i386 = self.factory.makeDistroArchSeries(
402- distroseries=nobby, architecturetag='i386',
403- processorfamily=i386_pf)
404- hppa_pf = self.factory.makeProcessorFamily(name='my_hppa')
405- nobby_hppa = self.factory.makeDistroArchSeries(
406- distroseries=nobby, architecturetag='hppa',
407- processorfamily=hppa_pf)
408- nobby_hppa.enabled = False
409- nobby.nominatedarchindep = nobby_i386
410- self.test_publisher.addFakeChroots(nobby)
411+ nobby = self.createNobby(('i386', 'hppa'))
412+ nobby['hppa'].enabled = False
413
414 # Now we can copy the package with binaries.
415- copies = _do_direct_copy(
416- source, source.archive, nobby, source.pocket, True)
417+ target_archive = self.factory.makeArchive(
418+ distribution=self.test_publisher.ubuntutest, virtualized=False)
419+ copies = self.doCopy(
420+ source, target_archive, nobby, source.pocket, True)
421
422 # The binary should not be published for hppa.
423- self.assertEquals(
424- [u'foo 666 in nobby',
425- u'foo-bin 666 in nobby i386',],
426- [copy.displayname for copy in copies])
427-
428-
429-class DoDelayedCopyTestCase(TestCaseWithFactory):
430+ self.assertCopied(copies, nobby, ('i386',))
431+
432+
433+class TestDoDelayedCopy(TestCaseWithFactory, BaseDoCopyTests):
434
435 layer = LaunchpadZopelessLayer
436 dbuser = config.archivepublisher.dbuser
437
438 def setUp(self):
439- super(DoDelayedCopyTestCase, self).setUp()
440+ super(TestDoDelayedCopy, self).setUp()
441+
442 self.test_publisher = SoyuzTestPublisher()
443+ self.test_publisher.prepareBreezyAutotest()
444
445 # Setup to copy into the main archive security pocket
446- self.test_publisher.prepareBreezyAutotest()
447 self.copy_archive = self.test_publisher.ubuntutest.main_archive
448 self.copy_series = self.test_publisher.distroseries
449 self.copy_pocket = PackagePublishingPocket.SECURITY
450@@ -1036,6 +1107,17 @@
451 self.test_publisher.breezy_autotest.status = (
452 SeriesStatus.CURRENT)
453
454+ def assertCopied(self, copy, series, arch_tags):
455+ self.assertEquals(
456+ copy.sources[0].sourcepackagerelease.title,
457+ 'foo - 666')
458+ self.assertEquals(
459+ sorted(arch_tags),
460+ sorted([pub.build.arch_tag for pub in copy.builds]))
461+
462+ def doCopy(self, source, archive, series, pocket, include_binaries):
463+ return _do_delayed_copy(source, archive, series, pocket, True)
464+
465 def createDelayedCopyContext(self):
466 """Create a context to allow delayed-copies test.
467
468
469=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
470--- lib/lp/soyuz/tests/test_publishing.py 2010-12-23 10:21:21 +0000
471+++ lib/lp/soyuz/tests/test_publishing.py 2011-01-15 07:18:47 +0000
472@@ -1344,7 +1344,8 @@
473 """Publications in other series shouldn't be found."""
474 bins = self.getPubBinaries(architecturespecific=False)
475 series = self.factory.makeDistroSeries()
476- arch = self.factory.makeDistroArchSeries(distroseries=series)
477+ arch = self.factory.makeDistroArchSeries(
478+ distroseries=series, architecturetag='i386')
479 foreign_bins = bins[0].copyTo(
480 series, bins[0].pocket, bins[0].archive)
481 self.checkOtherPublications(bins[0], bins)