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

Proposed by Colin Watson
Status: Rejected
Rejected by: Colin Watson
Proposed branch: lp:~cjwatson/launchpad/queue-api
Merge into: lp:launchpad
Diff against target: 1647 lines (+802/-187)
16 files modified
lib/lp/archiveuploader/tests/nascentupload-ddebs.txt (+2/-1)
lib/lp/registry/interfaces/distroseries.py (+7/-0)
lib/lp/soyuz/browser/queue.py (+7/-3)
lib/lp/soyuz/configure.zcml (+6/-0)
lib/lp/soyuz/doc/distroseriesqueue.txt (+22/-26)
lib/lp/soyuz/interfaces/archive.py (+13/-1)
lib/lp/soyuz/interfaces/binarypackagerelease.py (+8/-1)
lib/lp/soyuz/interfaces/queue.py (+133/-31)
lib/lp/soyuz/model/binarypackagerelease.py (+13/-2)
lib/lp/soyuz/model/queue.py (+241/-36)
lib/lp/soyuz/scripts/queue.py (+3/-3)
lib/lp/soyuz/stories/webservice/xx-packageupload.txt (+13/-0)
lib/lp/soyuz/tests/test_distroseriesqueue_ddtp_tarball.py (+0/-28)
lib/lp/soyuz/tests/test_distroseriesqueue_dist_upgrader.py (+4/-27)
lib/lp/soyuz/tests/test_packageupload.py (+327/-26)
lib/lp/testing/factory.py (+3/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/queue-api
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+108967@code.launchpad.net

Commit message

Export enough of PackageUpload and DistroSeries.getPackageUploads to allow implementing an API version of the queue tool.

Description of the change

== Summary ==

Implement the next stage of https://code.launchpad.net/~cjwatson/launchpad/queue-api-accept-reject/+merge/107894, by exporting substantially more of PackageUpload.

== Proposed fix ==

Export enough of PackageUpload and DistroSeries.getPackageUploads to allow implementing an API client that replaces scripts/ftpmaster-tools/queue.

== Pre-implementation notes ==

I've gone round a few times with various people, particularly William Grant, on the exact way to export all of this stuff, because I gather that we want to avoid exposing the current data model in order that it can be rearranged in the future. This has led to the following design choices:

 * Everything is on devel. The only clients for this should be tools such as those in lp:ubuntu-archive-tools, which can be kept up to date if there's a need to change these interfaces.
 * Even though some of the underlying methods are on other objects, all the new exported methods are on PackageUpload rather than exporting anything else.
 * There are source packages with lots of binaries that sometimes need to be overridden individually (e.g. linux) and API requests aren't especially fast. I've therefore arranged for properties (including overrides) of all binaries in an upload to come back as a list of dicts in a single JSON response, and I've amended Archive.overrideBinaries to take a similar list of dicts as a "changes" parameter, allowing many override changes to be made in a single request.

== LOC Rationale ==

+615, on top of a previous branch that was +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 at least -862. While it's possible there'll be one or two more bits and pieces, they shouldn't amount to any more than +156, so this whole arc will be LoC-negative.

== Tests ==

bin/test -vvct nascentupload-ddebs.txt -t distroseriesqueue.txt -t xx-packageupload.txt -t test_distroseriesqueue -t test_packageupload

== Demo and Q/A ==

http://paste.ubuntu.com/1026996/ is my prototype client; I plan to walk through all its functionality against qastaging (or dogfood if I need to make new uploads).

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

It is tough to give an appropriate amount of attention to a branch this big. I suggest breaking it into two or more branches that can get closer to our target of an 800 line diff (https://dev.launchpad.net/PreMergeReviews#line-38).

Perhaps one branch that does the refactoring needed for the interface and another that does the exposing, or maybe separate branches for exposing different subsets of the API.

Thanks.

Revision history for this message
Colin Watson (cjwatson) wrote :

You're probably right. I shall get splitting ...

Revision history for this message
Colin Watson (cjwatson) wrote :

This has now all been merged in smaller pieces.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/archiveuploader/tests/nascentupload-ddebs.txt'
--- lib/lp/archiveuploader/tests/nascentupload-ddebs.txt 2012-01-20 16:11:11 +0000
+++ lib/lp/archiveuploader/tests/nascentupload-ddebs.txt 2012-06-08 14:30:37 +0000
@@ -89,7 +89,8 @@
8989
90 >>> switch_dbuser('launchpad')90 >>> switch_dbuser('launchpad')
9191
92 >>> bin.queue_root.overrideBinaries(main, devel, None, [main, universe])92 >>> bin.queue_root.overrideBinaries(
93 ... [{"component": main, "section": devel}], [main, universe])
93 True94 True
94 >>> bin.queue_root.acceptFromQueue()95 >>> bin.queue_root.acceptFromQueue()
9596
9697
=== modified file 'lib/lp/registry/interfaces/distroseries.py'
--- lib/lp/registry/interfaces/distroseries.py 2012-01-10 09:55:24 +0000
+++ lib/lp/registry/interfaces/distroseries.py 2012-06-08 14:30:37 +0000
@@ -547,6 +547,13 @@
547 description=_("Return only items with custom files of this "547 description=_("Return only items with custom files of this "
548 "type."),548 "type."),
549 required=False),549 required=False),
550 name=TextLine(title=_("Package or file name"), required=False),
551 version=TextLine(title=_("Package version"), required=False),
552 exact_match=Bool(
553 title=_("Exact match"),
554 description=_("Whether to filter name and version by exact "
555 "matching."),
556 required=False),
550 )557 )
551 # Really IPackageUpload, patched in _schema_circular_imports.py558 # Really IPackageUpload, patched in _schema_circular_imports.py
552 @operation_returns_collection_of(Interface)559 @operation_returns_collection_of(Interface)
553560
=== modified file 'lib/lp/soyuz/browser/queue.py'
--- lib/lp/soyuz/browser/queue.py 2012-01-01 02:58:52 +0000
+++ lib/lp/soyuz/browser/queue.py 2012-06-08 14:30:37 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Browser views for package queue."""4"""Browser views for package queue."""
@@ -385,9 +385,13 @@
385 try:385 try:
386 source_overridden = queue_item.overrideSource(386 source_overridden = queue_item.overrideSource(
387 new_component, new_section, allowed_components)387 new_component, new_section, allowed_components)
388 binary_changes = [{
389 "component": new_component,
390 "section": new_section,
391 "priority": new_priority,
392 }]
388 binary_overridden = queue_item.overrideBinaries(393 binary_overridden = queue_item.overrideBinaries(
389 new_component, new_section, new_priority,394 binary_changes, allowed_components)
390 allowed_components)
391 except QueueInconsistentStateError, info:395 except QueueInconsistentStateError, info:
392 failure.append("FAILED: %s (%s)" %396 failure.append("FAILED: %s (%s)" %
393 (queue_item.displayname, info))397 (queue_item.displayname, info))
394398
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2012-06-07 20:35:53 +0000
+++ lib/lp/soyuz/configure.zcml 2012-06-08 14:30:37 +0000
@@ -157,18 +157,24 @@
157 distroseries157 distroseries
158 pocket158 pocket
159 changesfile159 changesfile
160 changes_file_url
160 signing_key161 signing_key
161 archive162 archive
162 sources163 sources
164 sourceFileUrls
163 builds165 builds
166 binaryFileUrls
164 customfiles167 customfiles
165 custom_file_urls168 custom_file_urls
169 customFileUrls
170 getBinaryProperties
166 date_created171 date_created
167 sourcepackagerelease172 sourcepackagerelease
168 component_name173 component_name
169 concrete_package_copy_job174 concrete_package_copy_job
170 contains_source175 contains_source
171 contains_build176 contains_build
177 contains_copy
172 contains_translation178 contains_translation
173 contains_installer179 contains_installer
174 contains_upgrader180 contains_upgrader
175181
=== modified file 'lib/lp/soyuz/doc/distroseriesqueue.txt'
--- lib/lp/soyuz/doc/distroseriesqueue.txt 2012-01-06 11:08:30 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue.txt 2012-06-08 14:30:37 +0000
@@ -648,7 +648,7 @@
648In addition to these parameters, you must also supply648In addition to these parameters, you must also supply
649"allowed_components", which is a sequence of IComponent. Any overrides649"allowed_components", which is a sequence of IComponent. Any overrides
650must have the existing and new component in this sequence otherwise650must have the existing and new component in this sequence otherwise
651QueueInconsistentStateError is raised.651QueueAdminUnauthorizedError is raised.
652652
653The alsa-utils source is already in the queue with component "main"653The alsa-utils source is already in the queue with component "main"
654and section "base".654and section "base".
@@ -673,7 +673,7 @@
673 ... allowed_components=(universe,))673 ... allowed_components=(universe,))
674 Traceback (most recent call last):674 Traceback (most recent call last):
675 ...675 ...
676 QueueInconsistentStateError: No rights to override to restricted676 QueueAdminUnauthorizedError: No rights to override to restricted
677677
678Allowing "restricted" still won't work because the original component678Allowing "restricted" still won't work because the original component
679is "main":679is "main":
@@ -683,7 +683,7 @@
683 ... allowed_components=(restricted,))683 ... allowed_components=(restricted,))
684 Traceback (most recent call last):684 Traceback (most recent call last):
685 ...685 ...
686 QueueInconsistentStateError: No rights to override from main686 QueueAdminUnauthorizedError: No rights to override from main
687687
688Specifying both main and restricted allows the override to restricted/web.688Specifying both main and restricted allows the override to restricted/web.
689overrideSource() returns True if it completed the task.689overrideSource() returns True if it completed the task.
@@ -710,29 +710,25 @@
710 main/base/Important710 main/base/Important
711711
712 >>> from lp.soyuz.enums import PackagePublishingPriority712 >>> from lp.soyuz.enums import PackagePublishingPriority
713 >>> print item.overrideBinaries(713 >>> binary_changes = [{
714 ... new_component=restricted,714 ... "component": restricted,
715 ... new_section=web,715 ... "section": web,
716 ... new_priority=PackagePublishingPriority.EXTRA,716 ... "priority": PackagePublishingPriority.EXTRA,
717 ... allowed_components=(universe,))717 ... }]
718 Traceback (most recent call last):718 >>> print item.overrideBinaries(
719 ...719 ... binary_changes, allowed_components=(universe,))
720 QueueInconsistentStateError: No rights to override to restricted720 Traceback (most recent call last):
721721 ...
722 >>> print item.overrideBinaries(722 QueueAdminUnauthorizedError: No rights to override to restricted
723 ... new_component=restricted,723
724 ... new_section=web,724 >>> print item.overrideBinaries(
725 ... new_priority=PackagePublishingPriority.EXTRA,725 ... binary_changes, allowed_components=(restricted,))
726 ... allowed_components=(restricted,))726 Traceback (most recent call last):
727 Traceback (most recent call last):727 ...
728 ...728 QueueAdminUnauthorizedError: No rights to override from main
729 QueueInconsistentStateError: No rights to override from main729
730730 >>> print item.overrideBinaries(
731 >>> print item.overrideBinaries(731 ... binary_changes, allowed_components=(main,restricted))
732 ... new_component=restricted,
733 ... new_section=web,
734 ... new_priority=PackagePublishingPriority.EXTRA,
735 ... allowed_components=(main,restricted))
736 True732 True
737 >>> print "%s/%s/%s" % (733 >>> print "%s/%s/%s" % (
738 ... binary_package.component.name,734 ... binary_package.component.name,
739735
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2012-06-06 21:24:57 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2012-06-08 14:30:37 +0000
@@ -43,6 +43,8 @@
43 'NoSuchPPA',43 'NoSuchPPA',
44 'NoTokensForTeams',44 'NoTokensForTeams',
45 'PocketNotFound',45 'PocketNotFound',
46 'PriorityNotFound',
47 'SectionNotFound',
46 'VersionRequiresName',48 'VersionRequiresName',
47 'default_name_by_purpose',49 'default_name_by_purpose',
48 'validate_external_dependencies',50 'validate_external_dependencies',
@@ -156,7 +158,7 @@
156158
157159
158class ComponentNotFound(NameLookupFailed):160class ComponentNotFound(NameLookupFailed):
159 """Invalid source name."""161 """Invalid component name."""
160 _message_prefix = 'No such component'162 _message_prefix = 'No such component'
161163
162164
@@ -165,6 +167,16 @@
165 """Invalid component name."""167 """Invalid component name."""
166168
167169
170class SectionNotFound(NameLookupFailed):
171 """Invalid section name."""
172 _message_prefix = "No such section"
173
174
175class PriorityNotFound(NameLookupFailed):
176 """Invalid priority name."""
177 _message_prefix = "No such priority"
178
179
168class NoSuchPPA(NameLookupFailed):180class NoSuchPPA(NameLookupFailed):
169 """Raised when we try to look up an PPA that doesn't exist."""181 """Raised when we try to look up an PPA that doesn't exist."""
170 _message_prefix = "No such ppa"182 _message_prefix = "No such ppa"
171183
=== modified file 'lib/lp/soyuz/interfaces/binarypackagerelease.py'
--- lib/lp/soyuz/interfaces/binarypackagerelease.py 2011-12-24 16:54:44 +0000
+++ lib/lp/soyuz/interfaces/binarypackagerelease.py 2012-06-08 14:30:37 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0211,E02134# pylint: disable-msg=E0211,E0213
@@ -98,6 +98,13 @@
98 description=_("True if there binary version was never published for "98 description=_("True if there binary version was never published for "
99 "the architeture it was built for. False otherwise."))99 "the architeture it was built for. False otherwise."))
100100
101 def properties():
102 """Returns the properties of this binary.
103
104 For fast retrieval over the webservice, this is returned as a
105 dictionary.
106 """
107
101 def addFile(file):108 def addFile(file):
102 """Create a BinaryPackageFile record referencing this build109 """Create a BinaryPackageFile record referencing this build
103 and attach the provided library file alias (file).110 and attach the provided library file alias (file).
104111
=== modified file 'lib/lp/soyuz/interfaces/queue.py'
--- lib/lp/soyuz/interfaces/queue.py 2012-05-30 08:50:50 +0000
+++ lib/lp/soyuz/interfaces/queue.py 2012-06-08 14:30:37 +0000
@@ -16,6 +16,7 @@
16 'IPackageUploadCustom',16 'IPackageUploadCustom',
17 'IPackageUploadSet',17 'IPackageUploadSet',
18 'NonBuildableSourceUploadError',18 'NonBuildableSourceUploadError',
19 'QueueAdminUnauthorizedError',
19 'QueueBuildAcceptError',20 'QueueBuildAcceptError',
20 'QueueInconsistentStateError',21 'QueueInconsistentStateError',
21 'QueueSourceAcceptError',22 'QueueSourceAcceptError',
@@ -26,11 +27,15 @@
2627
27from lazr.enum import DBEnumeratedType28from lazr.enum import DBEnumeratedType
28from lazr.restful.declarations import (29from lazr.restful.declarations import (
30 call_with,
29 error_status,31 error_status,
30 export_as_webservice_entry,32 export_as_webservice_entry,
33 export_read_operation,
31 export_write_operation,34 export_write_operation,
32 exported,35 exported,
33 operation_for_version,36 operation_for_version,
37 operation_parameters,
38 REQUEST_USER,
34 )39 )
35from lazr.restful.fields import Reference40from lazr.restful.fields import Reference
36from zope.interface import (41from zope.interface import (
@@ -38,12 +43,15 @@
38 Interface,43 Interface,
39 )44 )
40from zope.schema import (45from zope.schema import (
46 Bool,
41 Choice,47 Choice,
42 Datetime,48 Datetime,
49 Dict,
43 Int,50 Int,
44 List,51 List,
45 TextLine,52 TextLine,
46 )53 )
54from zope.security.interfaces import Unauthorized
4755
48from lp import _56from lp import _
49from lp.soyuz.enums import PackageUploadStatus57from lp.soyuz.enums import PackageUploadStatus
@@ -67,6 +75,10 @@
67 """75 """
6876
6977
78class QueueAdminUnauthorizedError(Unauthorized):
79 """User not permitted to perform a queue administration operation."""
80
81
70class NonBuildableSourceUploadError(QueueInconsistentStateError):82class NonBuildableSourceUploadError(QueueInconsistentStateError):
71 """Source upload will not result in any build record.83 """Source upload will not result in any build record.
7284
@@ -141,6 +153,14 @@
141153
142 changesfile = Attribute("The librarian alias for the changes file "154 changesfile = Attribute("The librarian alias for the changes file "
143 "associated with this upload")155 "associated with this upload")
156 changes_file_url = exported(
157 TextLine(
158 title=_("Changes file URL"),
159 description=_("Librarian URL for the changes file associated with "
160 "this upload. Will be None if the upload was copied "
161 "from another series."),
162 required=False, readonly=True),
163 as_of="devel")
144164
145 signing_key = Attribute("Changesfile Signing Key.")165 signing_key = Attribute("Changesfile Signing Key.")
146166
@@ -162,17 +182,18 @@
162 title=_("Archive"), required=True, readonly=True))182 title=_("Archive"), required=True, readonly=True))
163 sources = Attribute("The queue sources associated with this queue item")183 sources = Attribute("The queue sources associated with this queue item")
164 builds = Attribute("The queue builds associated with the queue item")184 builds = Attribute("The queue builds associated with the queue item")
185
165 customfiles = Attribute("Custom upload files associated with this "186 customfiles = Attribute("Custom upload files associated with this "
166 "queue item")187 "queue item")
167
168 custom_file_urls = exported(188 custom_file_urls = exported(
169 List(189 List(
170 title=_("Custom File URLs"),190 title=_("Custom file URLs"),
171 description=_("Librarian URLs for all the custom files attached "191 description=_("Librarian URLs for all the custom files attached "
172 "to this upload."),192 "to this upload."),
173 value_type=TextLine(),193 value_type=TextLine(),
174 required=False,194 required=False,
175 readonly=True))195 readonly=True),
196 ("devel", dict(exported=False)), exported=True)
176197
177 displayname = exported(198 displayname = exported(
178 TextLine(199 TextLine(
@@ -191,17 +212,39 @@
191 sourcepackagerelease = Attribute(212 sourcepackagerelease = Attribute(
192 "The source package release for this item")213 "The source package release for this item")
193214
194 package_name = TextLine(215 package_name = exported(
195 title=_("Name of the uploaded source package"), readonly=True)216 TextLine(
196217 title=_("Name of the uploaded source package"), readonly=True),
197 package_version = TextLine(218 as_of="devel")
198 title=_("Source package version"), readonly=True)219
199220 package_version = exported(
200 component_name = TextLine(221 TextLine(title=_("Source package version"), readonly=True),
201 title=_("Source package component name"), readonly=True)222 as_of="devel")
202223
203 contains_source = Attribute("whether or not this upload contains sources")224 component_name = exported(
204 contains_build = Attribute("whether or not this upload contains binaries")225 TextLine(title=_("Source package component name"), readonly=True),
226 as_of="devel")
227
228 section_name = exported(
229 TextLine(title=_("Source package section name"), readonly=True),
230 as_of="devel")
231
232 contains_source = exported(
233 Bool(
234 title=_("Whether or not this upload contains sources"),
235 readonly=True),
236 as_of="devel")
237 contains_build = exported(
238 Bool(
239 title=_("Whether or not this upload contains binaries"),
240 readonly=True),
241 as_of="devel")
242 contains_copy = exported(
243 Bool(
244 title=_("Whether or not this upload contains a copy from another "
245 "series."),
246 readonly=True),
247 as_of="devel")
205 contains_installer = Attribute(248 contains_installer = Attribute(
206 "whether or not this upload contains installers images")249 "whether or not this upload contains installers images")
207 contains_translation = Attribute(250 contains_translation = Attribute(
@@ -223,8 +266,38 @@
223 on all the binarypackagerelease records arising from the build.266 on all the binarypackagerelease records arising from the build.
224 """)267 """)
225268
226 section_name = TextLine(269 @export_read_operation()
227 title=_("Source package sectio name"), readonly=True)270 @operation_for_version("devel")
271 def sourceFileUrls():
272 """URLs for all the source files attached to this upload.
273
274 :return: A collection of URLs for this upload.
275 """
276
277 @export_read_operation()
278 @operation_for_version("devel")
279 def binaryFileUrls():
280 """URLs for all the binary files attached to this upload.
281
282 :return: A collection of URLs for this upload.
283 """
284
285 @export_read_operation()
286 @operation_for_version("devel")
287 def customFileUrls():
288 """URLs for all the custom files attached to this upload.
289
290 :return: A collection of URLs for this upload.
291 """
292
293 @export_read_operation()
294 @operation_for_version("devel")
295 def getBinaryProperties():
296 """The properties of the binaries associated with this queue item.
297
298 :return: A list of dictionaries, each containing the properties of a
299 single binary.
300 """
228301
229 def setNew():302 def setNew():
230 """Set queue state to NEW."""303 """Set queue state to NEW."""
@@ -329,7 +402,14 @@
329 :param logger: Specify a logger object if required. Mainly for tests.402 :param logger: Specify a logger object if required. Mainly for tests.
330 """403 """
331404
332 def overrideSource(new_component, new_section, allowed_components):405 @operation_parameters(
406 new_component=TextLine(title=u"The new component name."),
407 new_section=TextLine(title=u"The new section name."))
408 @call_with(allowed_components=None, user=REQUEST_USER)
409 @export_write_operation()
410 @operation_for_version('devel')
411 def overrideSource(new_component=None, new_section=None,
412 allowed_components=None, user=None):
333 """Override the source package contained in this queue item.413 """Override the source package contained in this queue item.
334414
335 :param new_component: An IComponent to replace the existing one415 :param new_component: An IComponent to replace the existing one
@@ -338,6 +418,8 @@
338 in the upload's source.418 in the upload's source.
339 :param allowed_components: A sequence of components that the419 :param allowed_components: A sequence of components that the
340 callsite is allowed to override from and to.420 callsite is allowed to override from and to.
421 :param user: The user requesting the override change, used if
422 allowed_components is None.
341423
342 :raises QueueInconsistentStateError: if either the existing424 :raises QueueInconsistentStateError: if either the existing
343 or the new_component are not in the allowed_components425 or the new_component are not in the allowed_components
@@ -349,27 +431,40 @@
349 :return: True if the source was overridden.431 :return: True if the source was overridden.
350 """432 """
351433
352 def overrideBinaries(new_component, new_section, new_priority,434 @operation_parameters(
353 allowed_components):435 changes=List(
354 """Override all the binaries in a binary queue item.436 title=u"A sequence of changes to apply.",
437 description=(
438 u"Each item may have a 'name' item which specifies the binary "
439 "package name to override; otherwise, the change applies to "
440 "all binaries in the upload. It may also have 'component', "
441 "'section', and 'priority' items which replace the "
442 "corresponding existing one in the upload's overridden "
443 "binaries."),
444 value_type=Dict(key_type=TextLine())))
445 @call_with(allowed_components=None, user=REQUEST_USER)
446 @export_write_operation()
447 @operation_for_version('devel')
448 def overrideBinaries(changes, allowed_components=None, user=None):
449 """Override binary packages in a binary queue item.
355450
356 :param new_component: An IComponent to replace the existing one451 :param changes: A sequence of mappings of changes to apply. Each
357 in the upload's source.452 change mapping may have a "name" item which specifies the binary
358 :param new_section: An ISection to replace the existing one453 package name to override; otherwise, the change applies to all
359 in the upload's source.454 binaries in the upload. It may also have "component", "section",
360 :param new_priority: A valid PackagePublishingPriority to replace455 and "priority" items which replace the corresponding existing
361 the existing one in the upload's binaries.456 one in the upload's overridden binaries. Any missing items are
457 left unchanged.
362 :param allowed_components: A sequence of components that the458 :param allowed_components: A sequence of components that the
363 callsite is allowed to override from and to.459 callsite is allowed to override from and to.
460 :param user: The user requesting the override change, used if
461 allowed_components is None.
364462
365 :raises QueueInconsistentStateError: if either the existing463 :raises QueueInconsistentStateError: if either the existing
366 or the new_component are not in the allowed_components464 or the new_component are not in the allowed_components
367 sequence.465 sequence.
368466
369 The override values may be None, in which case they are not467 :return: True if any binaries were overridden.
370 changed.
371
372 :return: True if the binaries were overridden.
373 """468 """
374469
375470
@@ -382,13 +477,20 @@
382477
383 packageupload = Int(478 packageupload = Int(
384 title=_("PackageUpload"), required=True,479 title=_("PackageUpload"), required=True,
385 readonly=False,480 readonly=True,
386 )481 )
387482
388 build = Int(483 build = Int(
389 title=_("The related build"), required=True, readonly=False,484 title=_("The related build"), required=True, readonly=False,
390 )485 )
391486
487 def binaries():
488 """Returns the properties of the binaries in this build.
489
490 For fast retrieval over the webservice, these are returned as a list
491 of dictionaries, one per binary.
492 """
493
392 def publish(logger=None):494 def publish(logger=None):
393 """Publish this queued source in the distroseries referred to by495 """Publish this queued source in the distroseries referred to by
394 the parent queue item.496 the parent queue item.
395497
=== modified file 'lib/lp/soyuz/model/binarypackagerelease.py'
--- lib/lp/soyuz/model/binarypackagerelease.py 2012-04-16 23:02:44 +0000
+++ lib/lp/soyuz/model/binarypackagerelease.py 2012-06-08 14:30:37 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0611,W02124# pylint: disable-msg=E0611,W0212
@@ -131,6 +131,18 @@
131 self.binarypackagename)131 self.binarypackagename)
132 return distroarchseries_binary_package.currentrelease is None132 return distroarchseries_binary_package.currentrelease is None
133133
134 @property
135 def properties(self):
136 return {
137 "name": self.name,
138 "version": self.version,
139 "is_new": self.is_new,
140 "architecture": self.build.arch_tag,
141 "component": self.component.name,
142 "section": self.section.name,
143 "priority": self.priority.name,
144 }
145
134 @cachedproperty146 @cachedproperty
135 def files(self):147 def files(self):
136 return list(148 return list(
@@ -201,4 +213,3 @@
201 def binary_package_version(self):213 def binary_package_version(self):
202 """See `IBinaryPackageReleaseDownloadCount`."""214 """See `IBinaryPackageReleaseDownloadCount`."""
203 return self.binary_package_release.version215 return self.binary_package_release.version
204
205216
=== modified file 'lib/lp/soyuz/model/queue.py'
--- lib/lp/soyuz/model/queue.py 2012-05-25 15:31:50 +0000
+++ lib/lp/soyuz/model/queue.py 2012-06-08 14:30:37 +0000
@@ -13,6 +13,7 @@
13 'PackageUploadSet',13 'PackageUploadSet',
14 ]14 ]
1515
16from itertools import chain
16import os17import os
17import shutil18import shutil
18import StringIO19import StringIO
@@ -50,8 +51,10 @@
50from lp.registry.interfaces.pocket import PackagePublishingPocket51from lp.registry.interfaces.pocket import PackagePublishingPocket
51from lp.registry.model.sourcepackagename import SourcePackageName52from lp.registry.model.sourcepackagename import SourcePackageName
52from lp.services.config import config53from lp.services.config import config
54from lp.services.database.bulk import load_referencing
53from lp.services.database.constants import UTC_NOW55from lp.services.database.constants import UTC_NOW
54from lp.services.database.datetimecol import UtcDateTimeCol56from lp.services.database.datetimecol import UtcDateTimeCol
57from lp.services.database.decoratedresultset import DecoratedResultSet
55from lp.services.database.enumcol import EnumCol58from lp.services.database.enumcol import EnumCol
56from lp.services.database.lpstorm import (59from lp.services.database.lpstorm import (
57 IMasterStore,60 IMasterStore,
@@ -61,22 +64,34 @@
61 SQLBase,64 SQLBase,
62 sqlvalues,65 sqlvalues,
63 )66 )
67from lp.services.librarian.browser import ProxiedLibraryFileAlias
64from lp.services.librarian.interfaces.client import DownloadFailed68from lp.services.librarian.interfaces.client import DownloadFailed
65from lp.services.librarian.model import LibraryFileAlias69from lp.services.librarian.model import LibraryFileAlias
66from lp.services.librarian.utils import copy_and_close70from lp.services.librarian.utils import copy_and_close
67from lp.services.mail.signedmessage import strip_pgp_signature71from lp.services.mail.signedmessage import strip_pgp_signature
68from lp.services.propertycache import cachedproperty72from lp.services.propertycache import (
73 cachedproperty,
74 get_property_cache,
75 )
69from lp.soyuz.adapters.notification import notify76from lp.soyuz.adapters.notification import notify
70from lp.soyuz.adapters.overrides import SourceOverride77from lp.soyuz.adapters.overrides import SourceOverride
71from lp.soyuz.enums import (78from lp.soyuz.enums import (
72 PackageUploadCustomFormat,79 PackageUploadCustomFormat,
73 PackageUploadStatus,80 PackageUploadStatus,
74 )81 )
75from lp.soyuz.interfaces.archive import MAIN_ARCHIVE_PURPOSES82from lp.soyuz.interfaces.archive import (
83 ComponentNotFound,
84 MAIN_ARCHIVE_PURPOSES,
85 PriorityNotFound,
86 SectionNotFound,
87 )
88from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
89from lp.soyuz.interfaces.component import IComponentSet
76from lp.soyuz.interfaces.packagecopyjob import IPackageCopyJobSource90from lp.soyuz.interfaces.packagecopyjob import IPackageCopyJobSource
77from lp.soyuz.interfaces.publishing import (91from lp.soyuz.interfaces.publishing import (
78 IPublishingSet,92 IPublishingSet,
79 ISourcePackagePublishingHistory,93 ISourcePackagePublishingHistory,
94 name_priority_map,
80 )95 )
81from lp.soyuz.interfaces.queue import (96from lp.soyuz.interfaces.queue import (
82 IPackageUpload,97 IPackageUpload,
@@ -86,11 +101,13 @@
86 IPackageUploadSet,101 IPackageUploadSet,
87 IPackageUploadSource,102 IPackageUploadSource,
88 NonBuildableSourceUploadError,103 NonBuildableSourceUploadError,
104 QueueAdminUnauthorizedError,
89 QueueBuildAcceptError,105 QueueBuildAcceptError,
90 QueueInconsistentStateError,106 QueueInconsistentStateError,
91 QueueSourceAcceptError,107 QueueSourceAcceptError,
92 QueueStateWriteProtectedError,108 QueueStateWriteProtectedError,
93 )109 )
110from lp.soyuz.interfaces.section import ISectionSet
94from lp.soyuz.model.binarypackagename import BinaryPackageName111from lp.soyuz.model.binarypackagename import BinaryPackageName
95from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease112from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
96from lp.soyuz.pas import BuildDaemonPackagesArchSpecific113from lp.soyuz.pas import BuildDaemonPackagesArchSpecific
@@ -230,11 +247,44 @@
230247
231 # Join this table to the PackageUploadBuild and the248 # Join this table to the PackageUploadBuild and the
232 # PackageUploadSource objects which are related.249 # PackageUploadSource objects which are related.
233 sources = SQLMultipleJoin('PackageUploadSource',250 _sources = SQLMultipleJoin('PackageUploadSource',
251 joinColumn='packageupload')
252 # Does not include source builds.
253 _builds = SQLMultipleJoin('PackageUploadBuild',
234 joinColumn='packageupload')254 joinColumn='packageupload')
235 # Does not include source builds.255
236 builds = SQLMultipleJoin('PackageUploadBuild',256 @cachedproperty
237 joinColumn='packageupload')257 def sources(self):
258 return list(self._sources)
259
260 def sourceFileUrls(self):
261 """See `IPackageUpload`."""
262 if self.contains_source:
263 return [
264 ProxiedLibraryFileAlias(
265 file.libraryfile, self.archive).http_url
266 for file in self.sourcepackagerelease.files]
267 else:
268 return []
269
270 @cachedproperty
271 def builds(self):
272 return list(self._builds)
273
274 def binaryFileUrls(self):
275 """See `IPackageUpload`."""
276 return [
277 ProxiedLibraryFileAlias(file.libraryfile, self.archive).http_url
278 for build in self.builds
279 for bpr in build.build.binarypackages
280 for file in bpr.files]
281
282 @property
283 def changes_file_url(self):
284 if self.changesfile is not None:
285 return self.changesfile.getURL()
286 else:
287 return None
238288
239 def getSourceBuild(self):289 def getSourceBuild(self):
240 #avoid circular import290 #avoid circular import
@@ -250,8 +300,12 @@
250 PackageUploadSource.packageupload == self.id).one()300 PackageUploadSource.packageupload == self.id).one()
251301
252 # Also the custom files associated with the build.302 # Also the custom files associated with the build.
253 customfiles = SQLMultipleJoin('PackageUploadCustom',303 _customfiles = SQLMultipleJoin('PackageUploadCustom',
254 joinColumn='packageupload')304 joinColumn='packageupload')
305
306 @cachedproperty
307 def customfiles(self):
308 return list(self._customfiles)
255309
256 @property310 @property
257 def custom_file_urls(self):311 def custom_file_urls(self):
@@ -259,6 +313,18 @@
259 return tuple(313 return tuple(
260 file.libraryfilealias.getURL() for file in self.customfiles)314 file.libraryfilealias.getURL() for file in self.customfiles)
261315
316 def customFileUrls(self):
317 """See `IPackageUpload`."""
318 return [
319 ProxiedLibraryFileAlias(
320 file.libraryfilealias, self.archive).http_url
321 for file in self.customfiles]
322
323 def getBinaryProperties(self):
324 """See `IPackageUpload`."""
325 return list(chain.from_iterable(
326 build.binaries for build in self.builds))
327
262 def setNew(self):328 def setNew(self):
263 """See `IPackageUpload`."""329 """See `IPackageUpload`."""
264 if self.status == PackageUploadStatus.NEW:330 if self.status == PackageUploadStatus.NEW:
@@ -544,7 +610,7 @@
544 def acceptFromCopy(self):610 def acceptFromCopy(self):
545 """See `IPackageUpload`."""611 """See `IPackageUpload`."""
546 assert self.is_delayed_copy, 'Can only process delayed-copies.'612 assert self.is_delayed_copy, 'Can only process delayed-copies.'
547 assert self.sources.count() == 1, (613 assert self._sources.count() == 1, (
548 'Source is mandatory for delayed copies.')614 'Source is mandatory for delayed copies.')
549 self.setAccepted()615 self.setAccepted()
550616
@@ -581,7 +647,7 @@
581647
582 def _isSingleSourceUpload(self):648 def _isSingleSourceUpload(self):
583 """Return True if this upload contains only a single source."""649 """Return True if this upload contains only a single source."""
584 return ((self.sources.count() == 1) and650 return ((self._sources.count() == 1) and
585 (not bool(self.builds)) and651 (not bool(self.builds)) and
586 (not bool(self.customfiles)))652 (not bool(self.customfiles)))
587653
@@ -590,12 +656,17 @@
590 @cachedproperty656 @cachedproperty
591 def contains_source(self):657 def contains_source(self):
592 """See `IPackageUpload`."""658 """See `IPackageUpload`."""
593 return self.sources659 return bool(self.sources)
594660
595 @cachedproperty661 @cachedproperty
596 def contains_build(self):662 def contains_build(self):
597 """See `IPackageUpload`."""663 """See `IPackageUpload`."""
598 return self.builds664 return bool(self.builds)
665
666 @cachedproperty
667 def contains_copy(self):
668 """See `IPackageUpload`."""
669 return self.package_copy_job_id is not None
599670
600 @cachedproperty671 @cachedproperty
601 def from_build(self):672 def from_build(self):
@@ -804,18 +875,21 @@
804875
805 def addSource(self, spr):876 def addSource(self, spr):
806 """See `IPackageUpload`."""877 """See `IPackageUpload`."""
878 del get_property_cache(self).sources
807 return PackageUploadSource(879 return PackageUploadSource(
808 packageupload=self,880 packageupload=self,
809 sourcepackagerelease=spr.id)881 sourcepackagerelease=spr.id)
810882
811 def addBuild(self, build):883 def addBuild(self, build):
812 """See `IPackageUpload`."""884 """See `IPackageUpload`."""
885 del get_property_cache(self).builds
813 return PackageUploadBuild(886 return PackageUploadBuild(
814 packageupload=self,887 packageupload=self,
815 build=build.id)888 build=build.id)
816889
817 def addCustom(self, library_file, custom_type):890 def addCustom(self, library_file, custom_type):
818 """See `IPackageUpload`."""891 """See `IPackageUpload`."""
892 del get_property_cache(self).customfiles
819 return PackageUploadCustom(893 return PackageUploadCustom(
820 packageupload=self,894 packageupload=self,
821 libraryfilealias=library_file.id,895 libraryfilealias=library_file.id,
@@ -915,6 +989,33 @@
915 """See `IPackageUpload`."""989 """See `IPackageUpload`."""
916 return getUtility(IPackageCopyJobSource).wrap(self.package_copy_job)990 return getUtility(IPackageCopyJobSource).wrap(self.package_copy_job)
917991
992 def _nameToComponent(self, component):
993 """Helper to convert a possible string component to IComponent."""
994 try:
995 if isinstance(component, basestring):
996 component = getUtility(IComponentSet)[component]
997 return component
998 except NotFoundError:
999 raise ComponentNotFound(component)
1000
1001 def _nameToSection(self, section):
1002 """Helper to convert a possible string section to ISection."""
1003 try:
1004 if isinstance(section, basestring):
1005 section = getUtility(ISectionSet)[section]
1006 return section
1007 except NotFoundError:
1008 raise SectionNotFound(section)
1009
1010 def _nameToPriority(self, priority):
1011 """Helper to convert a possible string priority to its enum."""
1012 try:
1013 if isinstance(priority, basestring):
1014 priority = name_priority_map[priority]
1015 return priority
1016 except KeyError:
1017 raise PriorityNotFound(priority)
1018
918 def _overrideSyncSource(self, new_component, new_section,1019 def _overrideSyncSource(self, new_component, new_section,
919 allowed_components):1020 allowed_components):
920 """Override source on the upload's `PackageCopyJob`, if any."""1021 """Override source on the upload's `PackageCopyJob`, if any."""
@@ -925,7 +1026,7 @@
925 allowed_component_names = [1026 allowed_component_names = [
926 component.name for component in allowed_components]1027 component.name for component in allowed_components]
927 if copy_job.component_name not in allowed_component_names:1028 if copy_job.component_name not in allowed_component_names:
928 raise QueueInconsistentStateError(1029 raise QueueAdminUnauthorizedError(
929 "No rights to override from %s" % copy_job.component_name)1030 "No rights to override from %s" % copy_job.component_name)
930 copy_job.addSourceOverride(SourceOverride(1031 copy_job.addSourceOverride(SourceOverride(
931 copy_job.package_name, new_component, new_section))1032 copy_job.package_name, new_component, new_section))
@@ -942,7 +1043,7 @@
942 if old_component not in allowed_components:1043 if old_component not in allowed_components:
943 # The old component is not in the list of allowed components1044 # The old component is not in the list of allowed components
944 # to override.1045 # to override.
945 raise QueueInconsistentStateError(1046 raise QueueAdminUnauthorizedError(
946 "No rights to override from %s" % old_component.name)1047 "No rights to override from %s" % old_component.name)
947 source.sourcepackagerelease.override(1048 source.sourcepackagerelease.override(
948 component=new_component, section=new_section)1049 component=new_component, section=new_section)
@@ -955,14 +1056,29 @@
9551056
956 return made_changes1057 return made_changes
9571058
958 def overrideSource(self, new_component, new_section, allowed_components):1059 def overrideSource(self, new_component=None, new_section=None,
1060 allowed_components=None, user=None):
959 """See `IPackageUpload`."""1061 """See `IPackageUpload`."""
960 if new_component is None and new_section is None:1062 if new_component is None and new_section is None:
961 # Nothing needs overriding, bail out.1063 # Nothing needs overriding, bail out.
962 return False1064 return False
9631065
1066 new_component = self._nameToComponent(new_component)
1067 new_section = self._nameToSection(new_section)
1068
1069 if allowed_components is None and user is not None:
1070 # Get a list of components for which the user has rights to
1071 # override to or from.
1072 permission_set = getUtility(IArchivePermissionSet)
1073 permissions = permission_set.componentsForQueueAdmin(
1074 self.distroseries.main_archive, user)
1075 allowed_components = set(
1076 permission.component for permission in permissions)
1077 assert allowed_components is not None, (
1078 "Must provide allowed_components for non-webservice calls.")
1079
964 if new_component not in list(allowed_components) + [None]:1080 if new_component not in list(allowed_components) + [None]:
965 raise QueueInconsistentStateError(1081 raise QueueAdminUnauthorizedError(
966 "No rights to override to %s" % new_component.name)1082 "No rights to override to %s" % new_component.name)
9671083
968 return (1084 return (
@@ -971,35 +1087,95 @@
971 self._overrideNonSyncSource(1087 self._overrideNonSyncSource(
972 new_component, new_section, allowed_components))1088 new_component, new_section, allowed_components))
9731089
974 def overrideBinaries(self, new_component, new_section, new_priority,1090 def _filterBinaryChanges(self, changes):
975 allowed_components):1091 """Process a binary changes mapping into a more convenient form."""
1092 changes_by_name = {}
1093 changes_for_all = None
1094
1095 for change in changes:
1096 filtered_change = {}
1097 if "component" in change:
1098 filtered_change["component"] = self._nameToComponent(
1099 change["component"])
1100 if "section" in change:
1101 filtered_change["section"] = self._nameToSection(
1102 change["section"])
1103 if "priority" in change:
1104 filtered_change["priority"] = self._nameToPriority(
1105 change["priority"])
1106
1107 if "name" in change:
1108 changes_by_name[change["name"]] = filtered_change
1109 else:
1110 # Changes with no "name" item provide a default for all
1111 # binaries.
1112 changes_for_all = filtered_change
1113
1114 return changes_by_name, changes_for_all
1115
1116 def overrideBinaries(self, changes, allowed_components=None, user=None):
976 """See `IPackageUpload`."""1117 """See `IPackageUpload`."""
977 if not self.contains_build:1118 if not self.contains_build:
978 return False1119 return False
9791120
980 if (new_component is None and new_section is None and1121 if not changes:
981 new_priority is None):
982 # Nothing needs overriding, bail out.1122 # Nothing needs overriding, bail out.
983 return False1123 return False
9841124
985 if new_component not in allowed_components:1125 if allowed_components is None and user is not None:
986 raise QueueInconsistentStateError(1126 # Get a list of components for which the user has rights to
987 "No rights to override to %s" % new_component.name)1127 # override to or from.
9881128 permission_set = getUtility(IArchivePermissionSet)
1129 permissions = permission_set.componentsForQueueAdmin(
1130 self.distroseries.main_archive, user)
1131 allowed_components = set(
1132 permission.component for permission in permissions)
1133 assert allowed_components is not None, (
1134 "Must provide allowed_components for non-webservice calls.")
1135
1136 changes_by_name, changes_for_all = self._filterBinaryChanges(changes)
1137
1138 new_components = set()
1139 for change in changes_by_name.values():
1140 if "component" in change:
1141 new_components.add(change["component"])
1142 if changes_for_all is not None and "component" in changes_for_all:
1143 new_components.add(changes_for_all["component"])
1144 disallowed_components = sorted(
1145 component.name
1146 for component in new_components.difference(allowed_components))
1147 if disallowed_components:
1148 raise QueueAdminUnauthorizedError(
1149 "No rights to override to %s" %
1150 ", ".join(disallowed_components))
1151
1152 made_changes = False
989 for build in self.builds:1153 for build in self.builds:
1154 # See if the new component requires a new archive on the build.
1155 for component in new_components:
1156 distroarchseries = build.build.distro_arch_series
1157 distribution = distroarchseries.distroseries.distribution
1158 new_archive = distribution.getArchiveByComponent(
1159 component.name)
1160 if new_archive != build.build.archive:
1161 raise QueueInconsistentStateError(
1162 "Overriding component to '%s' failed because it "
1163 "would require a new archive." % component.name)
1164
990 for binarypackage in build.build.binarypackages:1165 for binarypackage in build.build.binarypackages:
991 if binarypackage.component not in allowed_components:1166 change = changes_by_name.get(
992 # The old or the new component is not in the list of1167 binarypackage.name, changes_for_all)
993 # allowed components to override.1168 if change is not None:
994 raise QueueInconsistentStateError(1169 if binarypackage.component not in allowed_components:
995 "No rights to override from %s" % (1170 # The old component is not in the list of allowed
996 binarypackage.component.name))1171 # components to override.
997 binarypackage.override(1172 raise QueueAdminUnauthorizedError(
998 component=new_component,1173 "No rights to override from %s" % (
999 section=new_section,1174 binarypackage.component.name))
1000 priority=new_priority)1175 binarypackage.override(**change)
1176 made_changes = True
10011177
1002 return bool(self.builds)1178 return made_changes
10031179
10041180
1005class PackageUploadBuild(SQLBase):1181class PackageUploadBuild(SQLBase):
@@ -1014,6 +1190,12 @@
10141190
1015 build = ForeignKey(dbName='build', foreignKey='BinaryPackageBuild')1191 build = ForeignKey(dbName='build', foreignKey='BinaryPackageBuild')
10161192
1193 @property
1194 def binaries(self):
1195 """See `IPackageUploadBuild`."""
1196 for binary in self.build.binarypackages:
1197 yield binary.properties
1198
1017 def checkComponentAndSection(self):1199 def checkComponentAndSection(self):
1018 """See `IPackageUploadBuild`."""1200 """See `IPackageUploadBuild`."""
1019 distroseries = self.packageupload.distroseries1201 distroseries = self.packageupload.distroseries
@@ -1622,7 +1804,30 @@
1622 PackageUpload.distroseries == distroseries,1804 PackageUpload.distroseries == distroseries,
1623 *conditions)1805 *conditions)
1624 query = query.order_by(Desc(PackageUpload.id))1806 query = query.order_by(Desc(PackageUpload.id))
1625 return query.config(distinct=True)1807 query = query.config(distinct=True)
1808
1809 def preload_hook(rows):
1810 puses = load_referencing(
1811 PackageUploadSource, rows, ["packageuploadID"])
1812 pubs = load_referencing(
1813 PackageUploadBuild, rows, ["packageuploadID"])
1814 pucs = load_referencing(
1815 PackageUploadCustom, rows, ["packageuploadID"])
1816
1817 for pu in rows:
1818 cache = get_property_cache(pu)
1819 cache.sources = []
1820 cache.builds = []
1821 cache.customfiles = []
1822
1823 for pus in puses:
1824 get_property_cache(pus.packageupload).sources.append(pus)
1825 for pub in pubs:
1826 get_property_cache(pub.packageupload).builds.append(pub)
1827 for puc in pucs:
1828 get_property_cache(puc.packageupload).customfiles.append(puc)
1829
1830 return DecoratedResultSet(query, pre_iter_hook=preload_hook)
16261831
1627 def getBuildByBuildIDs(self, build_ids):1832 def getBuildByBuildIDs(self, build_ids):
1628 """See `IPackageUploadSet`."""1833 """See `IPackageUploadSet`."""
16291834
=== modified file 'lib/lp/soyuz/scripts/queue.py'
--- lib/lp/soyuz/scripts/queue.py 2012-02-10 10:50:03 +0000
+++ lib/lp/soyuz/scripts/queue.py 2012-06-08 14:30:37 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=W02314# pylint: disable-msg=W0231
@@ -260,8 +260,8 @@
260 False: '-',260 False: '-',
261 }261 }
262 return (262 return (
263 source_tag[bool(queue_item.contains_source)] +263 source_tag[queue_item.contains_source] +
264 binary_tag[bool(queue_item.contains_build)])264 binary_tag[queue_item.contains_build])
265265
266 def displayItem(self, queue_item):266 def displayItem(self, queue_item):
267 """Display one line summary of the queue item provided."""267 """Display one line summary of the queue item provided."""
268268
=== modified file 'lib/lp/soyuz/stories/webservice/xx-packageupload.txt'
--- lib/lp/soyuz/stories/webservice/xx-packageupload.txt 2012-05-30 14:12:44 +0000
+++ lib/lp/soyuz/stories/webservice/xx-packageupload.txt 2012-06-08 14:30:37 +0000
@@ -31,6 +31,19 @@
31 self_link: u'http://.../ubuntu/warty/+upload/11'31 self_link: u'http://.../ubuntu/warty/+upload/11'
32 status: u'Done'32 status: u'Done'
3333
34getPackageUploads can filter on package names.
35
36 >>> uploads = webservice.named_get(
37 ... warty['self_link'], 'getPackageUploads',
38 ... name='mozilla').jsonBody()
39 >>> len(uploads['entries'])
40 1
41 >>> uploads = webservice.named_get(
42 ... warty['self_link'], 'getPackageUploads',
43 ... name='missing').jsonBody()
44 >>> len(uploads['entries'])
45 0
46
3447
35Retrieving Static Translation Files48Retrieving Static Translation Files
36===================================49===================================
3750
=== modified file 'lib/lp/soyuz/tests/test_distroseriesqueue_ddtp_tarball.py'
--- lib/lp/soyuz/tests/test_distroseriesqueue_ddtp_tarball.py 2012-05-25 13:28:31 +0000
+++ lib/lp/soyuz/tests/test_distroseriesqueue_ddtp_tarball.py 2012-06-08 14:30:37 +0000
@@ -27,10 +27,6 @@
27 getPolicy,27 getPolicy,
28 )28 )
29from lp.services.log.logger import DevNullLogger29from lp.services.log.logger import DevNullLogger
30from lp.soyuz.scripts.queue import (
31 CommandRunner,
32 name_queue_map,
33 )
34from lp.soyuz.tests.test_publishing import TestNativePublishingBase30from lp.soyuz.tests.test_publishing import TestNativePublishingBase
35from lp.testing.gpgkeys import import_public_test_keys31from lp.testing.gpgkeys import import_public_test_keys
3632
@@ -68,30 +64,6 @@
68 def test_accepts_correct_upload(self):64 def test_accepts_correct_upload(self):
69 self.uploadTestData("20060728")65 self.uploadTestData("20060728")
7066
71 def runQueueCommand(self, queue_name, args):
72 def null_display(text):
73 pass
74
75 queue = name_queue_map[queue_name]
76 runner = CommandRunner(
77 queue, "ubuntutest", "breezy-autotest", True, None, None, None,
78 display=null_display)
79 runner.execute(args)
80
81 def test_queue_tool_behaviour(self):
82 # The queue tool can fetch ddtp-tarball uploads.
83 self.uploadTestData("20060728")
84 # Make sure that we can use the librarian files.
85 transaction.commit()
86 # Fetch upload into a temporary directory.
87 self.useTempDir()
88 self.runQueueCommand("accepted", ["fetch", "trans"])
89 expected_entries = [
90 "translations-main_20060728_all.changes",
91 "translations_main_20060728.tar.gz",
92 ]
93 self.assertContentEqual(expected_entries, os.listdir("."))
94
95 def test_publish(self):67 def test_publish(self):
96 upload = self.uploadTestData("20060728")68 upload = self.uploadTestData("20060728")
97 transaction.commit()69 transaction.commit()
9870
=== modified file 'lib/lp/soyuz/tests/test_distroseriesqueue_dist_upgrader.py'
--- lib/lp/soyuz/tests/test_distroseriesqueue_dist_upgrader.py 2012-05-25 13:27:41 +0000
+++ lib/lp/soyuz/tests/test_distroseriesqueue_dist_upgrader.py 2012-06-08 14:30:37 +0000
@@ -23,10 +23,6 @@
23 )23 )
24from lp.services.config import config24from lp.services.config import config
25from lp.services.log.logger import DevNullLogger25from lp.services.log.logger import DevNullLogger
26from lp.soyuz.scripts.queue import (
27 CommandRunner,
28 name_queue_map,
29 )
30from lp.soyuz.tests.test_publishing import TestNativePublishingBase26from lp.soyuz.tests.test_publishing import TestNativePublishingBase
31from lp.testing.gpgkeys import import_public_test_keys27from lp.testing.gpgkeys import import_public_test_keys
3228
@@ -69,37 +65,18 @@
69 def test_accepts_correct_upload(self):65 def test_accepts_correct_upload(self):
70 self.uploadTestData("20060302.0120")66 self.uploadTestData("20060302.0120")
7167
72 def runQueueCommand(self, queue_name, args):68 def test_accept_reject(self):
73 def null_display(text):69 # We can accept and reject dist-upgrader uploads.
74 pass
75
76 queue = name_queue_map[queue_name]
77 runner = CommandRunner(
78 queue, "ubuntutest", "breezy-autotest", True, None, None, None,
79 display=null_display)
80 runner.execute(args)
81
82 def test_queue_tool_behaviour(self):
83 # The queue tool can accept, reject, and fetch dist-upgrader
84 # uploads. See bug #54649.
85 upload = self.uploadTestData("20060302.0120")70 upload = self.uploadTestData("20060302.0120")
86 # Make sure that we can use the librarian files.71 # Make sure that we can use the librarian files.
87 transaction.commit()72 transaction.commit()
88 # Reject from accepted queue (unlikely, would normally be from73 # Reject from accepted queue (unlikely, would normally be from
89 # unapproved or new).74 # unapproved or new).
90 self.runQueueCommand("accepted", ["reject", "dist"])75 upload.queue_root.rejectFromQueue(logger=self.logger)
91 self.assertEqual("REJECTED", upload.queue_root.status.name)76 self.assertEqual("REJECTED", upload.queue_root.status.name)
92 # Accept from rejected queue (also unlikely, but only for testing).77 # Accept from rejected queue (also unlikely, but only for testing).
93 self.runQueueCommand("rejected", ["accept", "dist"])78 upload.queue_root.acceptFromQueue(logger=self.logger)
94 self.assertEqual("ACCEPTED", upload.queue_root.status.name)79 self.assertEqual("ACCEPTED", upload.queue_root.status.name)
95 # Fetch upload into a temporary directory.
96 self.useTempDir()
97 self.runQueueCommand("accepted", ["fetch", "dist"])
98 expected_entries = [
99 "dist-upgrader_20060302.0120_all.changes",
100 "dist-upgrader_20060302.0120_all.tar.gz",
101 ]
102 self.assertContentEqual(expected_entries, os.listdir("."))
10380
104 def test_bad_upload_remains_in_accepted(self):81 def test_bad_upload_remains_in_accepted(self):
105 # Bad dist-upgrader uploads remain in ACCEPTED.82 # Bad dist-upgrader uploads remain in ACCEPTED.
10683
=== modified file 'lib/lp/soyuz/tests/test_packageupload.py'
--- lib/lp/soyuz/tests/test_packageupload.py 2012-05-30 08:50:50 +0000
+++ lib/lp/soyuz/tests/test_packageupload.py 2012-06-08 14:30:37 +0000
@@ -12,9 +12,11 @@
12 BadRequest,12 BadRequest,
13 Unauthorized,13 Unauthorized,
14 )14 )
15from testtools.matchers import Equals
15import transaction16import transaction
16from zope.component import getUtility17from zope.component import getUtility
17from zope.security.proxy import removeSecurityProxy18from zope.security.proxy import removeSecurityProxy
19from zope.schema import getFields
1820
19from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet21from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
20from lp.archiveuploader.tests import datadir22from lp.archiveuploader.tests import datadir
@@ -25,6 +27,7 @@
25from lp.services.config import config27from lp.services.config import config
26from lp.services.database.lpstorm import IStore28from lp.services.database.lpstorm import IStore
27from lp.services.job.interfaces.job import JobStatus29from lp.services.job.interfaces.job import JobStatus
30from lp.services.librarian.browser import ProxiedLibraryFileAlias
28from lp.services.log.logger import BufferLogger31from lp.services.log.logger import BufferLogger
29from lp.services.mail import stub32from lp.services.mail import stub
30from lp.soyuz.adapters.overrides import SourceOverride33from lp.soyuz.adapters.overrides import SourceOverride
@@ -37,7 +40,9 @@
37from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet40from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
38from lp.soyuz.interfaces.component import IComponentSet41from lp.soyuz.interfaces.component import IComponentSet
39from lp.soyuz.interfaces.queue import (42from lp.soyuz.interfaces.queue import (
43 IPackageUpload,
40 IPackageUploadSet,44 IPackageUploadSet,
45 QueueAdminUnauthorizedError,
41 QueueInconsistentStateError,46 QueueInconsistentStateError,
42 )47 )
43from lp.soyuz.interfaces.section import ISectionSet48from lp.soyuz.interfaces.section import ISectionSet
@@ -48,6 +53,7 @@
48 api_url,53 api_url,
49 launchpadlib_for,54 launchpadlib_for,
50 person_logged_in,55 person_logged_in,
56 StormStatementRecorder,
51 TestCaseWithFactory,57 TestCaseWithFactory,
52 )58 )
53from lp.testing.dbuser import switch_dbuser59from lp.testing.dbuser import switch_dbuser
@@ -55,7 +61,10 @@
55 LaunchpadFunctionalLayer,61 LaunchpadFunctionalLayer,
56 LaunchpadZopelessLayer,62 LaunchpadZopelessLayer,
57 )63 )
58from lp.testing.matchers import Provides64from lp.testing.matchers import (
65 HasQueryCount,
66 Provides,
67 )
5968
6069
61class PackageUploadTestCase(TestCaseWithFactory):70class PackageUploadTestCase(TestCaseWithFactory):
@@ -440,8 +449,7 @@
440 only_allowed_component = self.factory.makeComponent()449 only_allowed_component = self.factory.makeComponent()
441 section = self.factory.makeSection()450 section = self.factory.makeSection()
442 self.assertRaises(451 self.assertRaises(
443 QueueInconsistentStateError,452 QueueAdminUnauthorizedError, pu.overrideSource,
444 pu.overrideSource,
445 only_allowed_component, section, [only_allowed_component])453 only_allowed_component, section, [only_allowed_component])
446454
447 def test_overrideSource_checks_permission_for_new_component(self):455 def test_overrideSource_checks_permission_for_new_component(self):
@@ -450,8 +458,7 @@
450 disallowed_component = self.factory.makeComponent()458 disallowed_component = self.factory.makeComponent()
451 section = self.factory.makeSection()459 section = self.factory.makeSection()
452 self.assertRaises(460 self.assertRaises(
453 QueueInconsistentStateError,461 QueueAdminUnauthorizedError, pu.overrideSource,
454 pu.overrideSource,
455 disallowed_component, section, [current_component])462 disallowed_component, section, [current_component])
456463
457 def test_overrideSource_ignores_None_component_change(self):464 def test_overrideSource_ignores_None_component_change(self):
@@ -864,6 +871,20 @@
864 list(reversed(ordered_uploads)),871 list(reversed(ordered_uploads)),
865 list(getUtility(IPackageUploadSet).getAll(series)))872 list(getUtility(IPackageUploadSet).getAll(series)))
866873
874 def test_getAll_can_preload_exported_properties(self):
875 # getAll preloads everything exported on the webservice.
876 distroseries = self.factory.makeDistroSeries()
877 self.factory.makeSourcePackageUpload(distroseries=distroseries)
878 self.factory.makeBuildPackageUpload(distroseries=distroseries)
879 self.factory.makeCustomPackageUpload(distroseries=distroseries)
880 uploads = list(getUtility(IPackageUploadSet).getAll(distroseries))
881 with StormStatementRecorder() as recorder:
882 for name, field in getFields(IPackageUpload).items():
883 if field.queryTaggedValue("lazr.restful.exported") is not None:
884 for upload in uploads:
885 getattr(upload, name)
886 self.assertThat(recorder, HasQueryCount(Equals(0)))
887
867 def test_rejectFromQueue_no_changes_file(self):888 def test_rejectFromQueue_no_changes_file(self):
868 # If the PackageUpload has no changesfile, we can still reject it.889 # If the PackageUpload has no changesfile, we can still reject it.
869 pu = self.factory.makePackageUpload()890 pu = self.factory.makePackageUpload()
@@ -880,12 +901,13 @@
880 def setUp(self):901 def setUp(self):
881 super(TestPackageUploadWebservice, self).setUp()902 super(TestPackageUploadWebservice, self).setUp()
882 self.webservice = None903 self.webservice = None
883
884 def makeDistroSeries(self):
885 self.distroseries = self.factory.makeDistroSeries()904 self.distroseries = self.factory.makeDistroSeries()
886 self.main = self.factory.makeComponent("main")905 self.main = self.factory.makeComponent("main")
887 self.factory.makeComponentSelection(906 self.factory.makeComponentSelection(
888 distroseries=self.distroseries, component=self.main)907 distroseries=self.distroseries, component=self.main)
908 self.universe = self.factory.makeComponent("universe")
909 self.factory.makeComponentSelection(
910 distroseries=self.distroseries, component=self.universe)
889911
890 def makeQueueAdmin(self, components):912 def makeQueueAdmin(self, components):
891 person = self.factory.makePerson()913 person = self.factory.makePerson()
@@ -902,57 +924,336 @@
902 self.webservice = launchpadlib_for("testing", person)924 self.webservice = launchpadlib_for("testing", person)
903 return self.webservice.load(api_url(obj))925 return self.webservice.load(api_url(obj))
904926
927 def makeSourcePackageUpload(self, person, **kwargs):
928 with person_logged_in(person):
929 upload = self.factory.makeSourcePackageUpload(
930 distroseries=self.distroseries, **kwargs)
931 transaction.commit()
932 spr = upload.sourcepackagerelease
933 for extension in ("dsc", "tar.gz"):
934 filename = "%s_%s.%s" % (spr.name, spr.version, extension)
935 lfa = self.factory.makeLibraryFileAlias(filename=filename)
936 spr.addFile(lfa)
937 transaction.commit()
938 return upload, self.load(upload, person)
939
940 def makeBinaryPackageUpload(self, person, binarypackagename=None,
941 component=None):
942 with person_logged_in(person):
943 upload = self.factory.makeBuildPackageUpload(
944 distroseries=self.distroseries,
945 binarypackagename=binarypackagename, component=component)
946 self.factory.makeBinaryPackageRelease(
947 build=upload.builds[0].build, component=component)
948 transaction.commit()
949 for build in upload.builds:
950 for bpr in build.build.binarypackages:
951 filename = "%s_%s_%s.deb" % (
952 bpr.name, bpr.version, bpr.build.arch_tag)
953 lfa = self.factory.makeLibraryFileAlias(filename=filename)
954 bpr.addFile(lfa)
955 transaction.commit()
956 return upload, self.load(upload, person)
957
958 def makeCustomPackageUpload(self, person, **kwargs):
959 with person_logged_in(person):
960 upload = self.factory.makeCustomPackageUpload(
961 distroseries=self.distroseries, **kwargs)
962 transaction.commit()
963 return upload, self.load(upload, person)
964
905 def assertRequiresEdit(self, method_name, **kwargs):965 def assertRequiresEdit(self, method_name, **kwargs):
906 """Test that a web service queue method requires launchpad.Edit."""966 """Test that a web service queue method requires launchpad.Edit."""
907 with admin_logged_in():967 with admin_logged_in():
908 upload = self.factory.makeSourcePackageUpload()968 upload = self.factory.makeSourcePackageUpload()
909 transaction.commit()969 transaction.commit()
910 ws_upload = self.load(upload)970 ws_upload = self.load(upload)
911 self.assertRaises(Unauthorized, getattr(ws_upload, method_name),971 self.assertRaises(
912 **kwargs)972 Unauthorized, getattr(ws_upload, method_name), **kwargs)
913973
914 def test_edit_permissions(self):974 def test_edit_permissions(self):
915 self.assertRequiresEdit("acceptFromQueue")975 self.assertRequiresEdit("acceptFromQueue")
916 self.assertRequiresEdit("rejectFromQueue")976 self.assertRequiresEdit("rejectFromQueue")
977 self.assertRequiresEdit("overrideSource", new_component="main")
978 self.assertRequiresEdit(
979 "overrideBinaries", changes=[{"component": "main"}])
917980
918 def test_acceptFromQueue_archive_admin(self):981 def test_acceptFromQueue_archive_admin(self):
919 # acceptFromQueue as an archive admin accepts the upload.982 # acceptFromQueue as an archive admin accepts the upload.
920 self.makeDistroSeries()
921 person = self.makeQueueAdmin([self.main])983 person = self.makeQueueAdmin([self.main])
922 with person_logged_in(person):984 upload, ws_upload = self.makeSourcePackageUpload(
923 upload = self.factory.makeSourcePackageUpload(985 person, component=self.main)
924 distroseries=self.distroseries, component=self.main)
925 transaction.commit()
926986
927 ws_upload = self.load(upload, person)
928 self.assertEqual("New", ws_upload.status)987 self.assertEqual("New", ws_upload.status)
929 ws_upload.acceptFromQueue()988 ws_upload.acceptFromQueue()
930 self.assertEqual("Done", ws_upload.status)989 self.assertEqual("Done", ws_upload.status)
931990
932 def test_double_accept_raises_BadRequest(self):991 def test_double_accept_raises_BadRequest(self):
933 # Trying to accept an upload twice returns 400 instead of OOPSing.992 # Trying to accept an upload twice returns 400 instead of OOPSing.
934 self.makeDistroSeries()
935 person = self.makeQueueAdmin([self.main])993 person = self.makeQueueAdmin([self.main])
994 upload, _ = self.makeSourcePackageUpload(person, component=self.main)
995
936 with person_logged_in(person):996 with person_logged_in(person):
937 upload = self.factory.makeSourcePackageUpload(
938 distroseries=self.distroseries, component=self.main)
939 upload.setAccepted()997 upload.setAccepted()
940 transaction.commit()
941
942 ws_upload = self.load(upload, person)998 ws_upload = self.load(upload, person)
943 self.assertEqual("Accepted", ws_upload.status)999 self.assertEqual("Accepted", ws_upload.status)
944 self.assertRaises(BadRequest, ws_upload.acceptFromQueue)1000 self.assertRaises(BadRequest, ws_upload.acceptFromQueue)
9451001
946 def test_rejectFromQueue_archive_admin(self):1002 def test_rejectFromQueue_archive_admin(self):
947 # rejectFromQueue as an archive admin rejects the upload.1003 # rejectFromQueue as an archive admin rejects the upload.
948 self.makeDistroSeries()
949 person = self.makeQueueAdmin([self.main])1004 person = self.makeQueueAdmin([self.main])
950 with person_logged_in(person):1005 upload, ws_upload = self.makeSourcePackageUpload(
951 upload = self.factory.makeSourcePackageUpload(1006 person, component=self.main)
952 distroseries=self.distroseries, component=self.main)
953 transaction.commit()
9541007
955 ws_upload = self.load(upload, person)
956 self.assertEqual("New", ws_upload.status)1008 self.assertEqual("New", ws_upload.status)
957 ws_upload.rejectFromQueue()1009 ws_upload.rejectFromQueue()
958 self.assertEqual("Rejected", ws_upload.status)1010 self.assertEqual("Rejected", ws_upload.status)
1011
1012 def test_source_info(self):
1013 # API clients can inspect properties of source uploads.
1014 person = self.makeQueueAdmin([self.universe])
1015 upload, ws_upload = self.makeSourcePackageUpload(
1016 person, sourcepackagename="hello", component=self.universe)
1017
1018 self.assertTrue(ws_upload.contains_source)
1019 self.assertFalse(ws_upload.contains_build)
1020 self.assertFalse(ws_upload.contains_copy)
1021 self.assertEqual("hello", ws_upload.display_name)
1022 self.assertEqual(upload.package_version, ws_upload.display_version)
1023 self.assertEqual("source", ws_upload.display_arches)
1024 self.assertEqual("hello", ws_upload.package_name)
1025 self.assertEqual(upload.package_version, ws_upload.package_version)
1026 self.assertEqual("universe", ws_upload.component_name)
1027 self.assertEqual(upload.section_name, ws_upload.section_name)
1028
1029 def test_source_fetch(self):
1030 # API clients can fetch files attached to source uploads.
1031 person = self.makeQueueAdmin([self.universe])
1032 upload, ws_upload = self.makeSourcePackageUpload(
1033 person, component=self.universe)
1034 ws_source_file_urls = ws_upload.sourceFileUrls()
1035 self.assertNotEqual(0, len(ws_source_file_urls))
1036 with person_logged_in(person):
1037 source_file_urls = [
1038 ProxiedLibraryFileAlias(
1039 file.libraryfile, upload.archive).http_url
1040 for file in upload.sourcepackagerelease.files]
1041 self.assertContentEqual(source_file_urls, ws_source_file_urls)
1042
1043 def test_overrideSource_limited_component_permissions(self):
1044 # Overriding between two components requires queue admin of both.
1045 person = self.makeQueueAdmin([self.universe])
1046 upload, ws_upload = self.makeSourcePackageUpload(
1047 person, component=self.universe)
1048
1049 self.assertEqual("New", ws_upload.status)
1050 self.assertEqual("universe", ws_upload.component_name)
1051 self.assertRaises(Unauthorized, ws_upload.overrideSource,
1052 new_component="main")
1053
1054 with admin_logged_in():
1055 upload.overrideSource(
1056 new_component=self.main,
1057 allowed_components=[self.main, self.universe])
1058 transaction.commit()
1059 self.assertEqual("main", upload.component_name)
1060 self.assertRaises(Unauthorized, ws_upload.overrideSource,
1061 new_component="universe")
1062
1063 def test_overrideSource_changes_properties(self):
1064 # Running overrideSource changes the corresponding properties.
1065 person = self.makeQueueAdmin([self.main, self.universe])
1066 upload, ws_upload = self.makeSourcePackageUpload(
1067 person, component=self.universe)
1068 with person_logged_in(person):
1069 new_section = self.factory.makeSection()
1070 transaction.commit()
1071
1072 self.assertEqual("New", ws_upload.status)
1073 self.assertEqual("universe", ws_upload.component_name)
1074 self.assertNotEqual(new_section.name, ws_upload.section_name)
1075 ws_upload.overrideSource(
1076 new_component="main", new_section=new_section.name)
1077 self.assertEqual("main", ws_upload.component_name)
1078 self.assertEqual(new_section.name, ws_upload.section_name)
1079 ws_upload.overrideSource(new_component="universe")
1080 self.assertEqual("universe", ws_upload.component_name)
1081
1082 def test_binary_info(self):
1083 # API clients can inspect properties of binary uploads.
1084 person = self.makeQueueAdmin([self.universe])
1085 upload, ws_upload = self.makeBinaryPackageUpload(
1086 person, component=self.universe)
1087 with person_logged_in(person):
1088 arch = upload.builds[0].build.arch_tag
1089 bprs = upload.builds[0].build.binarypackages
1090
1091 self.assertFalse(ws_upload.contains_source)
1092 self.assertTrue(ws_upload.contains_build)
1093 ws_binaries = ws_upload.getBinaryProperties()
1094 self.assertEqual(len(list(bprs)), len(ws_binaries))
1095 for bpr, binary in zip(bprs, ws_binaries):
1096 expected_binary = {
1097 "is_new": True,
1098 "name": bpr.name,
1099 "version": bpr.version,
1100 "architecture": arch,
1101 "component": "universe",
1102 "section": bpr.section.name,
1103 "priority": bpr.priority.name,
1104 }
1105 self.assertContentEqual(expected_binary.keys(), binary.keys())
1106 for key, value in expected_binary.items():
1107 self.assertEqual(value, binary[key])
1108
1109 def test_binary_fetch(self):
1110 # API clients can fetch files attached to binary uploads.
1111 person = self.makeQueueAdmin([self.universe])
1112 upload, ws_upload = self.makeBinaryPackageUpload(
1113 person, component=self.universe)
1114
1115 ws_binary_file_urls = ws_upload.binaryFileUrls()
1116 self.assertNotEqual(0, len(ws_binary_file_urls))
1117 with person_logged_in(person):
1118 binary_file_urls = [
1119 ProxiedLibraryFileAlias(
1120 file.libraryfile, upload.archive).http_url
1121 for bpr in upload.builds[0].build.binarypackages
1122 for file in bpr.files]
1123 self.assertContentEqual(binary_file_urls, ws_binary_file_urls)
1124
1125 def test_overrideBinaries_limited_component_permissions(self):
1126 # Overriding between two components requires queue admin of both.
1127 person = self.makeQueueAdmin([self.universe])
1128 upload, ws_upload = self.makeBinaryPackageUpload(
1129 person, binarypackagename="hello", component=self.universe)
1130
1131 self.assertEqual("New", ws_upload.status)
1132 self.assertEqual(
1133 set(["universe"]),
1134 set(binary["component"]
1135 for binary in ws_upload.getBinaryProperties()))
1136 self.assertRaises(
1137 Unauthorized, ws_upload.overrideBinaries,
1138 changes=[{"component": "main"}])
1139
1140 with admin_logged_in():
1141 upload.overrideBinaries(
1142 [{"component": self.main}],
1143 allowed_components=[self.main, self.universe])
1144 transaction.commit()
1145
1146 self.assertEqual(
1147 set(["main"]),
1148 set(binary["component"]
1149 for binary in ws_upload.getBinaryProperties()))
1150 self.assertRaises(
1151 Unauthorized, ws_upload.overrideBinaries,
1152 changes=[{"component": "universe"}])
1153
1154 def test_overrideBinaries_disallows_new_archive(self):
1155 # overrideBinaries refuses to override the component to something
1156 # that requires a different archive.
1157 partner = self.factory.makeComponent("partner")
1158 self.factory.makeComponentSelection(
1159 distroseries=self.distroseries, component=partner)
1160 person = self.makeQueueAdmin([self.universe, partner])
1161 upload, ws_upload = self.makeBinaryPackageUpload(
1162 person, component=self.universe)
1163
1164 self.assertEqual(
1165 "universe", ws_upload.getBinaryProperties()[0]["component"])
1166 self.assertRaises(
1167 BadRequest, ws_upload.overrideBinaries,
1168 changes=[{"component": "partner"}])
1169
1170 def test_overrideBinaries_without_name_changes_all_properties(self):
1171 # Running overrideBinaries with a change entry containing no "name"
1172 # field changes the corresponding properties of all binaries.
1173 person = self.makeQueueAdmin([self.main, self.universe])
1174 upload, ws_upload = self.makeBinaryPackageUpload(
1175 person, component=self.universe)
1176 with person_logged_in(person):
1177 new_section = self.factory.makeSection()
1178 transaction.commit()
1179
1180 self.assertEqual("New", ws_upload.status)
1181 for binary in ws_upload.getBinaryProperties():
1182 self.assertEqual("universe", binary["component"])
1183 self.assertNotEqual(new_section.name, binary["section"])
1184 self.assertEqual("OPTIONAL", binary["priority"])
1185 changes = [{
1186 "component": "main",
1187 "section": new_section.name,
1188 "priority": "extra",
1189 }]
1190 ws_upload.overrideBinaries(changes=changes)
1191 for binary in ws_upload.getBinaryProperties():
1192 self.assertEqual("main", binary["component"])
1193 self.assertEqual(new_section.name, binary["section"])
1194 self.assertEqual("EXTRA", binary["priority"])
1195
1196 def test_overrideBinaries_with_name_changes_selected_properties(self):
1197 # Running overrideBinaries with change entries containing "name"
1198 # fields changes the corresponding properties of only the selected
1199 # binaries.
1200 person = self.makeQueueAdmin([self.main, self.universe])
1201 upload, ws_upload = self.makeBinaryPackageUpload(
1202 person, component=self.universe)
1203 with person_logged_in(person):
1204 new_section = self.factory.makeSection()
1205 transaction.commit()
1206
1207 self.assertEqual("New", ws_upload.status)
1208 ws_binaries = ws_upload.getBinaryProperties()
1209 for binary in ws_binaries:
1210 self.assertEqual("universe", binary["component"])
1211 self.assertNotEqual(new_section.name, binary["section"])
1212 self.assertEqual("OPTIONAL", binary["priority"])
1213 change_one = {
1214 "name": ws_binaries[0]["name"],
1215 "component": "main",
1216 "priority": "standard",
1217 }
1218 change_two = {
1219 "name": ws_binaries[1]["name"],
1220 "section": new_section.name,
1221 }
1222 ws_upload.overrideBinaries(changes=[change_one, change_two])
1223 ws_binaries = ws_upload.getBinaryProperties()
1224 self.assertEqual("main", ws_binaries[0]["component"])
1225 self.assertNotEqual(new_section.name, ws_binaries[0]["section"])
1226 self.assertEqual("STANDARD", ws_binaries[0]["priority"])
1227 self.assertEqual("universe", ws_binaries[1]["component"])
1228 self.assertEqual(new_section.name, ws_binaries[1]["section"])
1229 self.assertEqual("OPTIONAL", ws_binaries[1]["priority"])
1230
1231 def test_custom_info(self):
1232 # API clients can inspect properties of custom uploads.
1233 person = self.makeQueueAdmin([self.universe])
1234 upload, ws_upload = self.makeCustomPackageUpload(
1235 person, custom_type=PackageUploadCustomFormat.DEBIAN_INSTALLER,
1236 filename="debian-installer-images_1.tar.gz")
1237
1238 self.assertFalse(ws_upload.contains_source)
1239 self.assertFalse(ws_upload.contains_build)
1240 self.assertFalse(ws_upload.contains_copy)
1241 self.assertEqual(
1242 "debian-installer-images_1.tar.gz", ws_upload.display_name)
1243 self.assertEqual("-", ws_upload.display_version)
1244 self.assertEqual("raw-installer", ws_upload.display_arches)
1245
1246 def test_custom_fetch(self):
1247 # API clients can fetch files attached to custom uploads.
1248 person = self.makeQueueAdmin([self.universe])
1249 upload, ws_upload = self.makeCustomPackageUpload(
1250 person, custom_type=PackageUploadCustomFormat.DEBIAN_INSTALLER,
1251 filename="debian-installer-images_1.tar.gz")
1252 ws_custom_file_urls = ws_upload.customFileUrls()
1253 self.assertNotEqual(0, len(ws_custom_file_urls))
1254 with person_logged_in(person):
1255 custom_file_urls = [
1256 ProxiedLibraryFileAlias(
1257 file.libraryfilealias, upload.archive).http_url
1258 for file in upload.customfiles]
1259 self.assertContentEqual(custom_file_urls, ws_custom_file_urls)
9591260
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2012-06-08 06:01:50 +0000
+++ lib/lp/testing/factory.py 2012-06-08 14:30:37 +0000
@@ -3494,7 +3494,7 @@
3494 return upload3494 return upload
34953495
3496 def makeBuildPackageUpload(self, distroseries=None,3496 def makeBuildPackageUpload(self, distroseries=None,
3497 binarypackagename=None):3497 binarypackagename=None, component=None):
3498 """Make a `PackageUpload` with a `PackageUploadBuild` attached."""3498 """Make a `PackageUpload` with a `PackageUploadBuild` attached."""
3499 if distroseries is None:3499 if distroseries is None:
3500 distroseries = self.makeDistroSeries()3500 distroseries = self.makeDistroSeries()
@@ -3503,7 +3503,8 @@
3503 build = self.makeBinaryPackageBuild()3503 build = self.makeBinaryPackageBuild()
3504 upload.addBuild(build)3504 upload.addBuild(build)
3505 self.makeBinaryPackageRelease(3505 self.makeBinaryPackageRelease(
3506 binarypackagename=binarypackagename, build=build)3506 binarypackagename=binarypackagename, build=build,
3507 component=component)
3507 return upload3508 return upload
35083509
3509 def makeCustomPackageUpload(self, distroseries=None, custom_type=None,3510 def makeCustomPackageUpload(self, distroseries=None, custom_type=None,