Merge lp:~julian-edwards/launchpad/publish-copy-archives-bug-520520-getPubConfig-proc-accept into lp:launchpad

Proposed by Julian Edwards on 2010-02-23
Status: Merged
Merged at revision: not available
Proposed branch: lp:~julian-edwards/launchpad/publish-copy-archives-bug-520520-getPubConfig-proc-accept
Merge into: lp:launchpad
Diff against target: 438 lines (+234/-110)
4 files modified
lib/lp/soyuz/scripts/processaccepted.py (+112/-2)
lib/lp/soyuz/tests/test_processaccepted.py (+85/-0)
lib/lp/soyuz/tests/test_publishing.py (+29/-6)
scripts/process-accepted.py (+8/-102)
To merge this branch: bzr merge lp:~julian-edwards/launchpad/publish-copy-archives-bug-520520-getPubConfig-proc-accept
Reviewer Review Type Date Requested Status
Graham Binns (community) code 2010-02-23 Approve on 2010-02-23
Review via email: mp+19955@code.launchpad.net
To post a comment you must log in.
Julian Edwards (julian-edwards) wrote :

= Summary =
Make process-accepted.py cope with rebuild archives

== Proposed fix ==
As part of publishing rebuild archives, we need to make process-accepted work
for them because when builds get uploaded, they are not auto-accepted.

== Pre-implementation notes ==
This is a simple fix, but the existing tests are a total shambles. Therefore
I decided to convert the script into a LaunchpadScript which makes it easier
to unit test. This change forms 95% of the diff.

== Implementation details ==
There's a new test file which does the Right Thing in regards to unit testing.
(i.e. it doesn't use Popen like some of the existing tests)

The test calls the script object and passes in the new option --copy-archives
and makes sure that it only accepts those, not anything in the main archive.

I also had to change SoyuzTestPublisher so that it creates packages in a pre-
accepted state.

There's also some bits of lint fixed.

== Tests ==
bin/test -cvv test_processaccepted

== Demo and Q/A ==
Nothing for this branch, the final feature will be QA'ed.

= Launchpad lint =

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

Linting changed files:
  lib/lp/soyuz/tests/test_publishing.py
  lib/lp/soyuz/scripts/processaccepted.py
  lib/lp/soyuz/tests/test_processaccepted.py
  scripts/process-accepted.py

Graham Binns (gmb) wrote :
Download full text (4.2 KiB)

Hi Julian,

Couple of minor nitpicks, but the branch looks good.

> === modified file 'lib/lp/soyuz/tests/test_publishing.py'
> --- lib/lp/soyuz/tests/test_publishing.py 2009-10-09 11:13:30 +0000
> +++ lib/lp/soyuz/tests/test_publishing.py 2010-02-23 11:20:39 +0000
> @@ -35,6 +35,7 @@
> from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet
> from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat
> from lp.soyuz.interfaces.build import BuildStatus
> +from lp.soyuz.interfaces.queue import PackageUploadStatus
> from lp.soyuz.interfaces.publishing import (
> PackagePublishingPriority, PackagePublishingStatus)
> from canonical.launchpad.scripts import FakeLogger
> @@ -130,12 +131,20 @@
> def addPackageUpload(self, archive, distroseries,
> pocket=PackagePublishingPocket.RELEASE,
> changes_file_name="foo_666_source.changes",
> - changes_file_content="fake changes file content"):
> + changes_file_content="fake changes file content",
> + upload_status=PackageUploadStatus.DONE):
> signing_key = self.person.gpgkeys[0]
> package_upload = distroseries.createQueueEntry(
> pocket, changes_file_name, changes_file_content, archive,
> signing_key)
> - package_upload.setDone()
> +
> + status_to_method = {
> + PackageUploadStatus.DONE: 'setDone',
> + PackageUploadStatus.ACCEPTED: 'setAccepted'
> + }

Minor nit here: our coding guidelines say that the closing brace should
be indented to the same level as the dict contents. Also, the last item
in the dict should have a comma at the end of it.

> + method = getattr(package_upload, status_to_method[upload_status])
> + method()
> +
> return package_upload
>
> def getPubSource(self, sourcename=None, version='666', component='main',
> @@ -152,8 +161,13 @@
> dsc_binaries='foo-bin', build_conflicts=None,
> build_conflicts_indep=None,
> dsc_maintainer_rfc822='Foo Bar <email address hidden>',
> - maintainer=None, creator=None, date_uploaded=UTC_NOW):
> - """Return a mock source publishing record."""
> + maintainer=None, creator=None, date_uploaded=UTC_NOW,
> + spr_only=False):
> + """Return a mock source publishing record.
> +
> + if spr_only is specified, the source is not published and the
> + sourcepackagerelease object is returned instead.
> + """
> if sourcename is None:
> sourcename = self.default_package_name
> spn = getUtility(ISourcePackageNameSet).getOrCreateByName(sourcename)
> @@ -194,12 +208,20 @@
> archive=archive, dateuploaded=date_uploaded)
>
> changes_file_name = "%s_%s_source.changes" % (sourcename, version)
> + if spr_only:
> + upload_status = PackageUploadStatus.ACCEPTED
> + else:
> + upload_status = PackageUploadStatus.DONE
> package_upload = self.addPackageUpload(
> ...

Read more...

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/scripts/processaccepted.py'
2--- lib/lp/soyuz/scripts/processaccepted.py 2009-08-28 06:39:38 +0000
3+++ lib/lp/soyuz/scripts/processaccepted.py 2010-02-23 11:57:23 +0000
4@@ -8,6 +8,7 @@
5 'close_bugs',
6 'close_bugs_for_queue_item',
7 'close_bugs_for_sourcepublication',
8+ 'ProcessAccepted',
9 ]
10
11 from zope.component import getUtility
12@@ -17,9 +18,12 @@
13 from lp.bugs.interfaces.bugtask import BugTaskStatus
14 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
15 from canonical.launchpad.webapp.interfaces import NotFoundError
16-from lp.soyuz.interfaces.archive import ArchivePurpose
17+from lp.soyuz.interfaces.archive import ArchivePurpose, IArchiveSet
18+from lp.registry.interfaces.distribution import IDistributionSet
19 from lp.registry.interfaces.pocket import PackagePublishingPocket
20-from lp.soyuz.interfaces.queue import IPackageUploadSet
21+from lp.soyuz.interfaces.queue import IPackageUploadSet, PackageUploadStatus
22+
23+from lp.services.scripts.base import LaunchpadScript
24
25
26 def get_bugs_from_changes_file(changes_file):
27@@ -160,3 +164,109 @@
28 owner=janitor,
29 subject=bug.followup_subject(),
30 content=content)
31+
32+
33+class ProcessAccepted(LaunchpadScript):
34+ """Queue/Accepted processor.
35+
36+ Given a distribution to run on, obtains all the queue items for the
37+ distribution and then gets on and deals with any accepted items, preparing
38+ them for publishing as appropriate.
39+ """
40+
41+ def add_my_options(self):
42+ """Command line options for this script."""
43+ self.parser.add_option(
44+ "-n", "--dry-run", action="store_true",
45+ dest="dryrun", metavar="DRY_RUN", default=False,
46+ help="Whether to treat this as a dry-run or not.")
47+
48+ self.parser.add_option(
49+ "--ppa", action="store_true",
50+ dest="ppa", metavar="PPA", default=False,
51+ help="Run only over PPA archives.")
52+
53+ self.parser.add_option(
54+ "--copy-archives", action="store_true",
55+ dest="copy_archives", metavar="COPY_ARCHIVES",
56+ default=False, help="Run only over COPY archives.")
57+
58+ @property
59+ def lockfilename(self):
60+ """Override LaunchpadScript's lock file name."""
61+ return "/var/lock/launchpad-upload-queue.lock"
62+
63+ def main(self):
64+ """Entry point for a LaunchpadScript."""
65+ if len(self.args) != 1:
66+ self.logger.error(
67+ "Need to be given exactly one non-option argument. "
68+ "Namely the distribution to process.")
69+ return 1
70+
71+ if self.options.ppa and self.options.copy_archives:
72+ self.logger.error(
73+ "Specify only one of copy archives or ppa archives.")
74+ return 1
75+
76+ distro_name = self.args[0]
77+
78+ processed_queue_ids = []
79+ try:
80+ self.logger.debug("Finding distribution %s." % distro_name)
81+ distribution = getUtility(IDistributionSet).getByName(distro_name)
82+
83+ # target_archives is a tuple of (archive, description).
84+ if self.options.ppa:
85+ target_archives = [
86+ (archive, archive.archive_url)
87+ for archive in distribution.getPendingAcceptancePPAs()]
88+ elif self.options.copy_archives:
89+ copy_archives = getUtility(
90+ IArchiveSet).getArchivesForDistribution(
91+ distribution, purposes=[ArchivePurpose.COPY])
92+ target_archives = [
93+ (archive, archive.displayname)
94+ for archive in copy_archives]
95+ else:
96+ target_archives = [
97+ (archive, archive.purpose.title)
98+ for archive in distribution.all_distro_archives]
99+
100+ for archive, description in target_archives:
101+ for distroseries in distribution.series:
102+
103+ self.logger.debug("Processing queue for %s %s" % (
104+ distroseries.name, description))
105+
106+ queue_items = distroseries.getQueueItems(
107+ PackageUploadStatus.ACCEPTED, archive=archive)
108+ for queue_item in queue_items:
109+ try:
110+ queue_item.realiseUpload(self.logger)
111+ except:
112+ self.logger.error(
113+ "Failure processing queue_item %d" % (
114+ queue_item.id), exc_info=True)
115+ raise
116+ else:
117+ processed_queue_ids.append(queue_item.id)
118+
119+ if not self.options.dryrun:
120+ self.txn.commit()
121+ else:
122+ self.logger.debug("Dry Run mode.")
123+
124+ if not self.options.ppa and not self.options.copy_archives:
125+ self.logger.debug("Closing bugs.")
126+ close_bugs(processed_queue_ids)
127+
128+ if not self.options.dryrun:
129+ self.txn.commit()
130+
131+ finally:
132+ self.logger.debug("Rolling back any remaining transactions.")
133+ self.txn.abort()
134+
135+ return 0
136+
137
138=== added file 'lib/lp/soyuz/tests/test_processaccepted.py'
139--- lib/lp/soyuz/tests/test_processaccepted.py 1970-01-01 00:00:00 +0000
140+++ lib/lp/soyuz/tests/test_processaccepted.py 2010-02-23 11:57:23 +0000
141@@ -0,0 +1,85 @@
142+# Copyright 2010 Canonical Ltd. This software is licensed under the
143+# GNU Affero General Public License version 3 (see the file LICENSE).
144+
145+"""Test process-accepted.py"""
146+
147+from zope.component import getUtility
148+
149+from canonical.config import config
150+from canonical.launchpad.scripts import QuietFakeLogger
151+from canonical.testing import LaunchpadZopelessLayer
152+
153+from lp.registry.interfaces.distribution import IDistributionSet
154+from lp.soyuz.interfaces.archive import ArchivePurpose
155+from lp.soyuz.interfaces.publishing import PackagePublishingStatus
156+from lp.soyuz.scripts.processaccepted import ProcessAccepted
157+from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
158+from lp.testing import TestCaseWithFactory
159+
160+
161+class TestProcessAccepted(TestCaseWithFactory):
162+
163+ layer = LaunchpadZopelessLayer
164+ dbuser = config.uploadqueue.dbuser
165+
166+ def setUp(self):
167+ """Create the Soyuz test publisher."""
168+ TestCaseWithFactory.setUp(self)
169+ self.stp = SoyuzTestPublisher()
170+ self.stp.prepareBreezyAutotest()
171+ self.test_package_name = "accept-test"
172+
173+ def getScript(self, test_args=None):
174+ """Return a ProcessAccepted instance."""
175+ if test_args is None:
176+ test_args = []
177+ test_args.append(self.stp.ubuntutest.name)
178+ script = ProcessAccepted("process accepted", test_args=test_args)
179+ script.logger = QuietFakeLogger()
180+ script.txn = self.layer.txn
181+ return script
182+
183+ def createWaitingAcceptancePackage(self, archive=None):
184+ """Create some pending publications."""
185+ if archive is None:
186+ archive = getUtility(
187+ IDistributionSet).getByName('ubuntu').main_archive
188+ return self.stp.getPubSource(
189+ archive=archive, sourcename=self.test_package_name, spr_only=True)
190+
191+ def testAcceptCopyArchives(self):
192+ """Test that publications in a copy archive are accepted properly."""
193+ # Upload some pending packages in a copy archive.
194+ copy_archive = self.factory.makeArchive(
195+ distribution=self.stp.ubuntutest, purpose=ArchivePurpose.COPY)
196+ copy_source = self.createWaitingAcceptancePackage(
197+ archive=copy_archive)
198+ # Also upload some stuff in the main archive.
199+ main_source = self.createWaitingAcceptancePackage()
200+
201+ # Before accepting, the package should not be published at all.
202+ published_copy = copy_archive.getPublishedSources(
203+ name=self.test_package_name)
204+ # Using .count() until Storm fixes __nonzero__ on SQLObj result
205+ # sets, then we can use bool() which is far more efficient than
206+ # counting.
207+ self.assertEqual(published_copy.count(), 0)
208+
209+ # Accept the packages.
210+ script = self.getScript(['--copy-archives'])
211+ self.layer.txn.commit()
212+ self.layer.switchDbUser(self.dbuser)
213+ script.main()
214+
215+ # Packages in main archive should not be accepted and published.
216+ published_main = self.stp.ubuntutest.main_archive.getPublishedSources(
217+ name=self.test_package_name)
218+ self.assertEqual(published_main.count(), 0)
219+
220+ # Check the copy archive source was accepted.
221+ [published_copy] = copy_archive.getPublishedSources(
222+ name=self.test_package_name)
223+ self.assertEqual(
224+ published_copy.status, PackagePublishingStatus.PENDING)
225+ self.assertEqual(copy_source, published_copy.sourcepackagerelease)
226+
227
228=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
229--- lib/lp/soyuz/tests/test_publishing.py 2009-10-09 11:13:30 +0000
230+++ lib/lp/soyuz/tests/test_publishing.py 2010-02-23 11:57:23 +0000
231@@ -35,6 +35,7 @@
232 from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet
233 from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat
234 from lp.soyuz.interfaces.build import BuildStatus
235+from lp.soyuz.interfaces.queue import PackageUploadStatus
236 from lp.soyuz.interfaces.publishing import (
237 PackagePublishingPriority, PackagePublishingStatus)
238 from canonical.launchpad.scripts import FakeLogger
239@@ -130,12 +131,20 @@
240 def addPackageUpload(self, archive, distroseries,
241 pocket=PackagePublishingPocket.RELEASE,
242 changes_file_name="foo_666_source.changes",
243- changes_file_content="fake changes file content"):
244+ changes_file_content="fake changes file content",
245+ upload_status=PackageUploadStatus.DONE):
246 signing_key = self.person.gpgkeys[0]
247 package_upload = distroseries.createQueueEntry(
248 pocket, changes_file_name, changes_file_content, archive,
249 signing_key)
250- package_upload.setDone()
251+
252+ status_to_method = {
253+ PackageUploadStatus.DONE: 'setDone',
254+ PackageUploadStatus.ACCEPTED: 'setAccepted',
255+ }
256+ method = getattr(package_upload, status_to_method[upload_status])
257+ method()
258+
259 return package_upload
260
261 def getPubSource(self, sourcename=None, version='666', component='main',
262@@ -152,8 +161,13 @@
263 dsc_binaries='foo-bin', build_conflicts=None,
264 build_conflicts_indep=None,
265 dsc_maintainer_rfc822='Foo Bar <foo@bar.com>',
266- maintainer=None, creator=None, date_uploaded=UTC_NOW):
267- """Return a mock source publishing record."""
268+ maintainer=None, creator=None, date_uploaded=UTC_NOW,
269+ spr_only=False):
270+ """Return a mock source publishing record.
271+
272+ if spr_only is specified, the source is not published and the
273+ sourcepackagerelease object is returned instead.
274+ """
275 if sourcename is None:
276 sourcename = self.default_package_name
277 spn = getUtility(ISourcePackageNameSet).getOrCreateByName(sourcename)
278@@ -194,12 +208,20 @@
279 archive=archive, dateuploaded=date_uploaded)
280
281 changes_file_name = "%s_%s_source.changes" % (sourcename, version)
282+ if spr_only:
283+ upload_status = PackageUploadStatus.ACCEPTED
284+ else:
285+ upload_status = PackageUploadStatus.DONE
286 package_upload = self.addPackageUpload(
287 archive, distroseries, pocket,
288 changes_file_name=changes_file_name,
289- changes_file_content=changes_file_content)
290+ changes_file_content=changes_file_content,
291+ upload_status=upload_status)
292 package_upload.addSource(spr)
293
294+ if spr_only:
295+ return spr
296+
297 if filename is None:
298 filename = "%s_%s.dsc" % (sourcename, version)
299 alias = self.addMockFile(
300@@ -389,7 +411,8 @@
301 """File with given name fragment in directory tree starting at top."""
302 for root, dirs, files in os.walk(top, topdown=False):
303 for name in files:
304- if name.endswith('.changes') and name.find(name_fragment) > -1:
305+ if (name.endswith('.changes') and
306+ name.find(name_fragment) > -1):
307 return os.path.join(root, name)
308 return None
309
310
311=== modified file 'scripts/process-accepted.py'
312--- scripts/process-accepted.py 2009-11-17 02:33:27 +0000
313+++ scripts/process-accepted.py 2010-02-23 11:57:23 +0000
314@@ -3,6 +3,9 @@
315 # Copyright 2009 Canonical Ltd. This software is licensed under the
316 # GNU Affero General Public License version 3 (see the file LICENSE).
317
318+# Stop pylint complaining about the _pythonpath relative import.
319+# pylint: disable-msg=W0403
320+
321 """Queue/Accepted processor
322
323 Given a distribution to run on, obtains all the queue items for the
324@@ -12,110 +15,13 @@
325
326 import _pythonpath
327
328-import sys
329-from optparse import OptionParser
330-
331-from zope.component import getUtility
332-
333 from canonical.config import config
334 from canonical.database.sqlbase import ISOLATION_LEVEL_READ_COMMITTED
335-from lp.soyuz.interfaces.queue import PackageUploadStatus
336-from canonical.launchpad.scripts import (
337- execute_zcml_for_scripts, logger, logger_options)
338-from lp.soyuz.scripts.processaccepted import close_bugs
339-from canonical.lp import initZopeless
340-
341-from contrib.glock import GlobalLock
342-
343-def main():
344- # Prevent circular imports.
345- from lp.registry.interfaces.distribution import IDistributionSet
346-
347- # Parse command-line arguments
348- parser = OptionParser()
349- logger_options(parser)
350-
351- parser.add_option("-n", "--dry-run", action="store_true",
352- dest="dryrun", metavar="DRY_RUN", default=False,
353- help="Whether to treat this as a dry-run or not.")
354-
355- parser.add_option("--ppa", action="store_true",
356- dest="ppa", metavar="PPA", default=False,
357- help="Run only over PPA archives.")
358-
359- (options, args) = parser.parse_args()
360-
361- log = logger(options, "process-accepted")
362-
363- if len(args) != 1:
364- log.error("Need to be given exactly one non-option argument. "
365- "Namely the distribution to process.")
366- return 1
367-
368- distro_name = args[0]
369-
370- log.debug("Acquiring lock")
371- lock = GlobalLock('/var/lock/launchpad-upload-queue.lock')
372- lock.acquire(blocking=True)
373-
374- log.debug("Initialising connection.")
375-
376- execute_zcml_for_scripts()
377- ztm = initZopeless(dbuser=config.uploadqueue.dbuser,
378- isolation=ISOLATION_LEVEL_READ_COMMITTED)
379-
380- processed_queue_ids = []
381- try:
382- log.debug("Finding distribution %s." % distro_name)
383- distribution = getUtility(IDistributionSet).getByName(distro_name)
384-
385- # target_archives is a tuple of (archive, description).
386- if options.ppa:
387- target_archives = [
388- (archive, archive.archive_url)
389- for archive in distribution.getPendingAcceptancePPAs()]
390- else:
391- target_archives = [
392- (archive, archive.purpose.title)
393- for archive in distribution.all_distro_archives]
394-
395- for archive, description in target_archives:
396- for distroseries in distribution.series:
397-
398- log.debug("Processing queue for %s %s" % (
399- distroseries.name, description))
400-
401- queue_items = distroseries.getQueueItems(
402- PackageUploadStatus.ACCEPTED, archive=archive)
403- for queue_item in queue_items:
404- try:
405- queue_item.realiseUpload(log)
406- except:
407- log.error("Failure processing queue_item %d"
408- % (queue_item.id), exc_info=True)
409- raise
410- else:
411- processed_queue_ids.append(queue_item.id)
412-
413- if not options.dryrun:
414- ztm.commit()
415- else:
416- log.debug("Dry Run mode.")
417-
418- log.debug("Closing bugs.")
419- close_bugs(processed_queue_ids)
420-
421- if not options.dryrun:
422- ztm.commit()
423-
424- finally:
425- log.debug("Rolling back any remaining transactions.")
426- ztm.abort()
427- log.debug("Releasing lock")
428- lock.release()
429-
430- return 0
431+from lp.soyuz.scripts.processaccepted import ProcessAccepted
432+
433
434 if __name__ == '__main__':
435- sys.exit(main())
436+ script = ProcessAccepted(
437+ "process-accepted", dbuser=config.uploadqueue.dbuser)
438+ script.lock_and_run(isolation=ISOLATION_LEVEL_READ_COMMITTED)
439