Merge lp:~cjwatson/launchpad/queue-api-accept-reject into lp:launchpad

Proposed by Colin Watson on 2012-05-30
Status: Merged
Approved by: Steve Kowalik on 2012-05-30
Approved revision: no longer in the source branch.
Merged at revision: 15331
Proposed branch: lp:~cjwatson/launchpad/queue-api-accept-reject
Merge into: lp:launchpad
Diff against target: 232 lines (+120/-7)
4 files modified
lib/lp/soyuz/interfaces/queue.py (+14/-3)
lib/lp/soyuz/stories/webservice/xx-packageupload.txt (+1/-0)
lib/lp/soyuz/tests/test_packageupload.py (+103/-2)
lib/lp/testing/factory.py (+2/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/queue-api-accept-reject
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code 2012-05-30 Approve on 2012-05-30
Review via email: mp+107894@code.launchpad.net

Commit Message

Export PackageUpload.id, PackageUpload.acceptFromQueue, and PackageUpload.rejectFromQueue.

Description of the Change

== Summary ==

This is a small first step towards fixing bug 1006173, to provide an API sufficient to replace scripts/ftpmaster-tools/queue.

Most of the rest of the work gets fairly big and complicated, so I split off this initial branch since it has value in itself and I hope it's simple enough to be relatively uncontentious.

== Proposed fix ==

Export PU.acceptFromQueue and PU.rejectFromQueue.

I also exported PU.id while I was there, since that makes it a bit easier to emulate the "queue accept ID" form.

== LOC Rationale ==

+91. I think this is valid because this is part of an arc of work (resourced by Ubuntu Engineering) that will culminate in removing lib/lp/soyuz/scripts/queue.py and scripts/ftpmaster-tools/queue for -862; I expect the whole thing to come in comfortably inside that.

== Tests ==

bin/test -vvct test_packageupload -t xx-packageupload.txt

== Demo and Q/A ==

I have a local implementation of a client-side queue tool that could be used to demo this on (I guess, since I need to run the uploader) dogfood, by accepting and rejecting a couple of uploads. It's fairly easy to do this by hand in lp-shell, though: use DistroSeries.getPackageUploads() to get hold of some PackageUpload objects, and then call .acceptFromQueue() or .rejectFromQueue() on those.

To post a comment you must log in.
Steve Kowalik (stevenk) wrote :

This looks like excellent work and I certainly approve of your overarching plan to nuke scripts/ftpmaster-tools/queue from orbit.

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/queue.py'
2--- lib/lp/soyuz/interfaces/queue.py 2011-12-24 16:54:44 +0000
3+++ lib/lp/soyuz/interfaces/queue.py 2012-05-30 14:16:22 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 # pylint: disable-msg=E0211,E0213
10@@ -22,10 +22,15 @@
11 'QueueStateWriteProtectedError',
12 ]
13
14+import httplib
15+
16 from lazr.enum import DBEnumeratedType
17 from lazr.restful.declarations import (
18+ error_status,
19 export_as_webservice_entry,
20+ export_write_operation,
21 exported,
22+ operation_for_version,
23 )
24 from lazr.restful.fields import Reference
25 from zope.interface import (
26@@ -53,6 +58,7 @@
27 """
28
29
30+@error_status(httplib.BAD_REQUEST)
31 class QueueInconsistentStateError(Exception):
32 """Queue state machine error.
33
34@@ -98,9 +104,10 @@
35
36 export_as_webservice_entry(publish_web_link=False)
37
38- id = Int(
39+ id = exported(
40+ Int(
41 title=_("ID"), required=True, readonly=True,
42- )
43+ ))
44
45 status = exported(
46 Choice(
47@@ -258,6 +265,8 @@
48 has no sources associated to it.
49 """
50
51+ @export_write_operation()
52+ @operation_for_version("devel")
53 def acceptFromQueue(logger=None, dry_run=False):
54 """Call setAccepted, do a syncUpdate, and send notification email.
55
56@@ -266,6 +275,8 @@
57 :raises: AssertionError if the context is a delayed-copy.
58 """
59
60+ @export_write_operation()
61+ @operation_for_version("devel")
62 def rejectFromQueue(logger=None, dry_run=False):
63 """Call setRejected, do a syncUpdate, and send notification email."""
64
65
66=== modified file 'lib/lp/soyuz/stories/webservice/xx-packageupload.txt'
67--- lib/lp/soyuz/stories/webservice/xx-packageupload.txt 2010-10-18 22:24:59 +0000
68+++ lib/lp/soyuz/stories/webservice/xx-packageupload.txt 2012-05-30 14:16:22 +0000
69@@ -25,6 +25,7 @@
70 display_name: u'mozilla-firefox'
71 display_version: u'0.9'
72 distroseries_link: u'http://.../ubuntu/warty'
73+ id: 11
74 pocket: u'Release'
75 resource_type_link: u'http://.../#package_upload'
76 self_link: u'http://.../ubuntu/warty/+upload/11'
77
78=== modified file 'lib/lp/soyuz/tests/test_packageupload.py'
79--- lib/lp/soyuz/tests/test_packageupload.py 2012-05-21 07:34:15 +0000
80+++ lib/lp/soyuz/tests/test_packageupload.py 2012-05-30 14:16:22 +0000
81@@ -8,6 +8,11 @@
82 import os
83 import shutil
84
85+from lazr.restfulclient.errors import (
86+ BadRequest,
87+ Unauthorized,
88+ )
89+import transaction
90 from zope.component import getUtility
91 from zope.security.proxy import removeSecurityProxy
92
93@@ -29,6 +34,7 @@
94 PackageUploadCustomFormat,
95 PackageUploadStatus,
96 )
97+from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
98 from lp.soyuz.interfaces.component import IComponentSet
99 from lp.soyuz.interfaces.queue import (
100 IPackageUploadSet,
101@@ -37,9 +43,18 @@
102 from lp.soyuz.interfaces.section import ISectionSet
103 from lp.soyuz.model.packagecopyjob import IPackageCopyJobSource
104 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
105-from lp.testing import TestCaseWithFactory
106+from lp.testing import (
107+ admin_logged_in,
108+ api_url,
109+ launchpadlib_for,
110+ person_logged_in,
111+ TestCaseWithFactory,
112+ )
113 from lp.testing.dbuser import switch_dbuser
114-from lp.testing.layers import LaunchpadZopelessLayer
115+from lp.testing.layers import (
116+ LaunchpadFunctionalLayer,
117+ LaunchpadZopelessLayer,
118+ )
119 from lp.testing.matchers import Provides
120
121
122@@ -855,3 +870,89 @@
123 pu.changesfile = None
124 pu.rejectFromQueue()
125 self.assertEqual(PackageUploadStatus.REJECTED, pu.status)
126+
127+
128+class TestPackageUploadWebservice(TestCaseWithFactory):
129+ """Test the exposure of queue methods to the web service."""
130+
131+ layer = LaunchpadFunctionalLayer
132+
133+ def setUp(self):
134+ super(TestPackageUploadWebservice, self).setUp()
135+ self.webservice = None
136+
137+ def makeDistroSeries(self):
138+ self.distroseries = self.factory.makeDistroSeries()
139+ self.main = self.factory.makeComponent("main")
140+ self.factory.makeComponentSelection(
141+ distroseries=self.distroseries, component=self.main)
142+
143+ def makeQueueAdmin(self, components):
144+ person = self.factory.makePerson()
145+ for component in components:
146+ getUtility(IArchivePermissionSet).newQueueAdmin(
147+ self.distroseries.main_archive, person, component)
148+ return person
149+
150+ def load(self, obj, person=None):
151+ if person is None:
152+ with admin_logged_in():
153+ person = self.factory.makePerson()
154+ if self.webservice is None:
155+ self.webservice = launchpadlib_for("testing", person)
156+ return self.webservice.load(api_url(obj))
157+
158+ def assertRequiresEdit(self, method_name, **kwargs):
159+ """Test that a web service queue method requires launchpad.Edit."""
160+ with admin_logged_in():
161+ upload = self.factory.makeSourcePackageUpload()
162+ transaction.commit()
163+ ws_upload = self.load(upload)
164+ self.assertRaises(Unauthorized, getattr(ws_upload, method_name),
165+ **kwargs)
166+
167+ def test_edit_permissions(self):
168+ self.assertRequiresEdit("acceptFromQueue")
169+ self.assertRequiresEdit("rejectFromQueue")
170+
171+ def test_acceptFromQueue_archive_admin(self):
172+ # acceptFromQueue as an archive admin accepts the upload.
173+ self.makeDistroSeries()
174+ person = self.makeQueueAdmin([self.main])
175+ with person_logged_in(person):
176+ upload = self.factory.makeSourcePackageUpload(
177+ distroseries=self.distroseries, component=self.main)
178+ transaction.commit()
179+
180+ ws_upload = self.load(upload, person)
181+ self.assertEqual("New", ws_upload.status)
182+ ws_upload.acceptFromQueue()
183+ self.assertEqual("Done", ws_upload.status)
184+
185+ def test_double_accept_raises_BadRequest(self):
186+ # Trying to accept an upload twice returns 400 instead of OOPSing.
187+ self.makeDistroSeries()
188+ person = self.makeQueueAdmin([self.main])
189+ with person_logged_in(person):
190+ upload = self.factory.makeSourcePackageUpload(
191+ distroseries=self.distroseries, component=self.main)
192+ upload.setAccepted()
193+ transaction.commit()
194+
195+ ws_upload = self.load(upload, person)
196+ self.assertEqual("Accepted", ws_upload.status)
197+ self.assertRaises(BadRequest, ws_upload.acceptFromQueue)
198+
199+ def test_rejectFromQueue_archive_admin(self):
200+ # rejectFromQueue as an archive admin rejects the upload.
201+ self.makeDistroSeries()
202+ person = self.makeQueueAdmin([self.main])
203+ with person_logged_in(person):
204+ upload = self.factory.makeSourcePackageUpload(
205+ distroseries=self.distroseries, component=self.main)
206+ transaction.commit()
207+
208+ ws_upload = self.load(upload, person)
209+ self.assertEqual("New", ws_upload.status)
210+ ws_upload.rejectFromQueue()
211+ self.assertEqual("Rejected", ws_upload.status)
212
213=== modified file 'lib/lp/testing/factory.py'
214--- lib/lp/testing/factory.py 2012-05-30 00:36:29 +0000
215+++ lib/lp/testing/factory.py 2012-05-30 14:16:22 +0000
216@@ -3511,14 +3511,14 @@
217 return package_upload
218
219 def makeSourcePackageUpload(self, distroseries=None,
220- sourcepackagename=None):
221+ sourcepackagename=None, component=None):
222 """Make a `PackageUpload` with a `PackageUploadSource` attached."""
223 if distroseries is None:
224 distroseries = self.makeDistroSeries()
225 upload = self.makePackageUpload(
226 distroseries=distroseries, archive=distroseries.main_archive)
227 upload.addSource(self.makeSourcePackageRelease(
228- sourcepackagename=sourcepackagename))
229+ sourcepackagename=sourcepackagename, component=component))
230 return upload
231
232 def makeBuildPackageUpload(self, distroseries=None,