Merge lp:~julian-edwards/launchpad/async-copying-part-2 into lp:launchpad
- async-copying-part-2
- Merge into devel
Proposed by
Julian Edwards
Status: | Merged |
---|---|
Approved by: | Julian Edwards |
Approved revision: | no longer in the source branch. |
Merged at revision: | 13459 |
Proposed branch: | lp:~julian-edwards/launchpad/async-copying-part-2 |
Merge into: | lp:launchpad |
Prerequisite: | lp:~julian-edwards/launchpad/async-copying-bug-809805 |
Diff against target: |
979 lines (+359/-45) 16 files modified
database/schema/security.cfg (+1/-0) lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+2/-0) lib/lp/registry/browser/distroseries.py (+1/-1) lib/lp/services/job/interfaces/job.py (+7/-0) lib/lp/services/job/model/job.py (+13/-3) lib/lp/services/job/tests/test_job.py (+24/-4) lib/lp/soyuz/browser/archive.py (+2/-1) lib/lp/soyuz/browser/tests/test_archive_webservice.py (+31/-5) lib/lp/soyuz/browser/tests/test_package_copying_mixin.py (+3/-3) lib/lp/soyuz/interfaces/archive.py (+45/-0) lib/lp/soyuz/interfaces/packagecopyjob.py (+4/-2) lib/lp/soyuz/model/archive.py (+45/-0) lib/lp/soyuz/model/packagecopyjob.py (+13/-8) lib/lp/soyuz/tests/test_archive.py (+88/-1) lib/lp/soyuz/tests/test_packagecopyjob.py (+75/-15) lib/lp/testing/factory.py (+5/-2) |
To merge this branch: | bzr merge lp:~julian-edwards/launchpad/async-copying-part-2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
j.c.sackett (community) | Approve | ||
Review via email: mp+67989@code.launchpad.net |
Commit message
Expose a simple webservice API to copy packages asynchronously
Description of the change
This is the second part of my copyPackage(s) change for asynchronous package copying. This part adds copyPackages() which does multiple packages in one go similar to syncSources().
See the pre-requisite at https:/
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'database/schema/security.cfg' |
2 | --- database/schema/security.cfg 2011-07-14 10:05:11 +0000 |
3 | +++ database/schema/security.cfg 2011-07-18 08:59:28 +0000 |
4 | @@ -1058,6 +1058,7 @@ |
5 | public.sourcepackagepublishinghistory = SELECT, INSERT |
6 | public.sourcepackagerelease = SELECT |
7 | public.sourcepackagereleasefile = SELECT, INSERT, UPDATE |
8 | +public.teamparticipation = SELECT |
9 | type=user |
10 | |
11 | [distroseriesdifferencejob] |
12 | |
13 | === modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py' |
14 | --- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-07-18 08:59:27 +0000 |
15 | +++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-07-18 08:59:28 +0000 |
16 | @@ -405,6 +405,8 @@ |
17 | patch_plain_parameter_type(IArchive, 'syncSources', 'from_archive', IArchive) |
18 | patch_plain_parameter_type(IArchive, 'syncSource', 'from_archive', IArchive) |
19 | patch_plain_parameter_type(IArchive, 'copyPackage', 'from_archive', IArchive) |
20 | +patch_plain_parameter_type( |
21 | + IArchive, 'copyPackages', 'from_archive', IArchive) |
22 | patch_entry_return_type(IArchive, 'newSubscription', IArchiveSubscriber) |
23 | patch_plain_parameter_type( |
24 | IArchive, 'getArchiveDependency', 'dependency', IArchive) |
25 | |
26 | === modified file 'lib/lp/registry/browser/distroseries.py' |
27 | --- lib/lp/registry/browser/distroseries.py 2011-07-08 09:06:24 +0000 |
28 | +++ lib/lp/registry/browser/distroseries.py 2011-07-18 08:59:28 +0000 |
29 | @@ -1161,7 +1161,7 @@ |
30 | ) |
31 | for dsd in self.getUpgrades()] |
32 | getUtility(IPlainPackageCopyJobSource).createMultiple( |
33 | - target_distroseries, copies, |
34 | + target_distroseries, copies, self.user, |
35 | copy_policy=PackageCopyPolicy.MASS_SYNC) |
36 | |
37 | self.request.response.addInfoNotification( |
38 | |
39 | === modified file 'lib/lp/services/job/interfaces/job.py' |
40 | --- lib/lp/services/job/interfaces/job.py 2011-07-06 14:02:54 +0000 |
41 | +++ lib/lp/services/job/interfaces/job.py 2011-07-18 08:59:28 +0000 |
42 | @@ -22,6 +22,7 @@ |
43 | DBEnumeratedType, |
44 | DBItem, |
45 | ) |
46 | +from lazr.restful.fields import Reference |
47 | from zope.interface import ( |
48 | Attribute, |
49 | Interface, |
50 | @@ -35,6 +36,7 @@ |
51 | ) |
52 | |
53 | from canonical.launchpad import _ |
54 | +from lp.registry.interfaces.person import IPerson |
55 | |
56 | |
57 | class SuspendJobException(Exception): |
58 | @@ -109,6 +111,11 @@ |
59 | max_retries = Int(title=_( |
60 | 'The number of retries permitted before this job permanently fails.')) |
61 | |
62 | + requester = Reference( |
63 | + IPerson, title=_("The person who requested the job"), |
64 | + required=False, readonly=True |
65 | + ) |
66 | + |
67 | is_pending = Bool( |
68 | title=_("Whether or not this job's status is such that it " |
69 | "could eventually complete.")) |
70 | |
71 | === modified file 'lib/lp/services/job/model/job.py' |
72 | --- lib/lp/services/job/model/job.py 2011-07-06 14:02:54 +0000 |
73 | +++ lib/lp/services/job/model/job.py 2011-07-18 08:59:28 +0000 |
74 | @@ -21,6 +21,10 @@ |
75 | Or, |
76 | Select, |
77 | ) |
78 | +from storm.locals import ( |
79 | + Int, |
80 | + Reference, |
81 | + ) |
82 | from zope.interface import implements |
83 | |
84 | from canonical.database.constants import UTC_NOW |
85 | @@ -73,6 +77,9 @@ |
86 | |
87 | max_retries = IntCol(default=0) |
88 | |
89 | + requester_id = Int(name='requester', allow_none=True) |
90 | + requester = Reference(requester_id, 'Person.id') |
91 | + |
92 | # Mapping of valid target states from a given state. |
93 | _valid_transitions = { |
94 | JobStatus.WAITING: |
95 | @@ -108,16 +115,19 @@ |
96 | return self.status in self.PENDING_STATUSES |
97 | |
98 | @classmethod |
99 | - def createMultiple(self, store, num_jobs): |
100 | + def createMultiple(self, store, num_jobs, requester=None): |
101 | """Create multiple `Job`s at once. |
102 | |
103 | :param store: `Store` to ceate the jobs in. |
104 | :param num_jobs: Number of `Job`s to create. |
105 | + :param request: The `IPerson` requesting the jobs. |
106 | :return: An iterable of `Job.id` values for the new jobs. |
107 | """ |
108 | - job_contents = ["(%s)" % quote(JobStatus.WAITING)] * num_jobs |
109 | + job_contents = [ |
110 | + "(%s, %s)" % ( |
111 | + quote(JobStatus.WAITING), quote(requester))] * num_jobs |
112 | result = store.execute(""" |
113 | - INSERT INTO Job (status) |
114 | + INSERT INTO Job (status, requester) |
115 | VALUES %s |
116 | RETURNING id |
117 | """ % ", ".join(job_contents)) |
118 | |
119 | === modified file 'lib/lp/services/job/tests/test_job.py' |
120 | --- lib/lp/services/job/tests/test_job.py 2011-07-06 14:02:54 +0000 |
121 | +++ lib/lp/services/job/tests/test_job.py 2011-07-18 08:59:28 +0000 |
122 | @@ -22,10 +22,13 @@ |
123 | Job, |
124 | LeaseHeld, |
125 | ) |
126 | -from lp.testing import TestCase |
127 | - |
128 | - |
129 | -class TestJob(TestCase): |
130 | +from lp.testing import ( |
131 | + TestCase, |
132 | + TestCaseWithFactory, |
133 | + ) |
134 | + |
135 | + |
136 | +class TestJob(TestCaseWithFactory): |
137 | """Ensure Job behaves as intended.""" |
138 | |
139 | layer = ZopelessDatabaseLayer |
140 | @@ -39,6 +42,12 @@ |
141 | job = Job() |
142 | self.assertEqual(job.status, JobStatus.WAITING) |
143 | |
144 | + def test_stores_requester(self): |
145 | + job = Job() |
146 | + random_joe = self.factory.makePerson() |
147 | + job.requester = random_joe |
148 | + self.assertEqual(random_joe, job.requester) |
149 | + |
150 | def test_createMultiple_creates_requested_number_of_jobs(self): |
151 | job_ids = list(Job.createMultiple(IStore(Job), 3)) |
152 | self.assertEqual(3, len(job_ids)) |
153 | @@ -55,6 +64,17 @@ |
154 | job = store.get(Job, Job.createMultiple(store, 1)[0]) |
155 | self.assertEqual(JobStatus.WAITING, job.status) |
156 | |
157 | + def test_createMultiple_sets_requester(self): |
158 | + store = IStore(Job) |
159 | + requester = self.factory.makePerson() |
160 | + job = store.get(Job, Job.createMultiple(store, 1, requester)[0]) |
161 | + self.assertEqual(requester, job.requester) |
162 | + |
163 | + def test_createMultiple_defaults_requester_to_None(self): |
164 | + store = IStore(Job) |
165 | + job = store.get(Job, Job.createMultiple(store, 1)[0]) |
166 | + self.assertEqual(None, job.requester) |
167 | + |
168 | def test_start(self): |
169 | """Job.start should update the object appropriately. |
170 | |
171 | |
172 | === modified file 'lib/lp/soyuz/browser/archive.py' |
173 | --- lib/lp/soyuz/browser/archive.py 2011-06-29 21:49:36 +0000 |
174 | +++ lib/lp/soyuz/browser/archive.py 2011-07-18 08:59:28 +0000 |
175 | @@ -1284,7 +1284,8 @@ |
176 | spph.source_package_name, spph.archive, dest_archive, dest_series, |
177 | dest_pocket, include_binaries=include_binaries, |
178 | package_version=spph.sourcepackagerelease.version, |
179 | - copy_policy=PackageCopyPolicy.INSECURE) |
180 | + copy_policy=PackageCopyPolicy.INSECURE, |
181 | + requester=person) |
182 | |
183 | return structured(""" |
184 | <p>Requested sync of %s packages.</p> |
185 | |
186 | === modified file 'lib/lp/soyuz/browser/tests/test_archive_webservice.py' |
187 | --- lib/lp/soyuz/browser/tests/test_archive_webservice.py 2011-07-18 08:59:27 +0000 |
188 | +++ lib/lp/soyuz/browser/tests/test_archive_webservice.py 2011-07-18 08:59:28 +0000 |
189 | @@ -19,7 +19,10 @@ |
190 | from canonical.testing.layers import DatabaseFunctionalLayer |
191 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
192 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
193 | -from lp.soyuz.enums import ArchivePurpose |
194 | +from lp.soyuz.enums import ( |
195 | + ArchivePurpose, |
196 | + PackagePublishingStatus, |
197 | + ) |
198 | from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJobSource |
199 | from lp.soyuz.interfaces.processor import IProcessorFamilySet |
200 | from lp.testing import ( |
201 | @@ -311,17 +314,16 @@ |
202 | |
203 | |
204 | class TestCopyPackage(WebServiceTestCase): |
205 | - """Webservice test cases for the copyPackage method""" |
206 | + """Webservice test cases for the copyPackage/copyPackages methods""" |
207 | |
208 | - def test_copyPackage(self): |
209 | - """Basic smoke test""" |
210 | + def setup_data(self): |
211 | self.ws_version = "devel" |
212 | uploader_dude = self.factory.makePerson() |
213 | source_archive = self.factory.makeArchive() |
214 | target_archive = self.factory.makeArchive( |
215 | purpose=ArchivePurpose.PRIMARY) |
216 | source = self.factory.makeSourcePackagePublishingHistory( |
217 | - archive=source_archive) |
218 | + archive=source_archive, status=PackagePublishingStatus.PUBLISHED) |
219 | source_name = source.source_package_name |
220 | version = source.source_package_version |
221 | to_pocket = PackagePublishingPocket.RELEASE |
222 | @@ -330,6 +332,13 @@ |
223 | with person_logged_in(target_archive.owner): |
224 | target_archive.newComponentUploader(uploader_dude, "universe") |
225 | transaction.commit() |
226 | + return (source_archive, source_name, target_archive, to_pocket, |
227 | + to_series, uploader_dude, version) |
228 | + |
229 | + def test_copyPackage(self): |
230 | + """Basic smoke test""" |
231 | + (source_archive, source_name, target_archive, to_pocket, to_series, |
232 | + uploader_dude, version) = self.setup_data() |
233 | |
234 | ws_target_archive = self.wsObject(target_archive, user=uploader_dude) |
235 | ws_source_archive = self.wsObject(source_archive) |
236 | @@ -344,6 +353,23 @@ |
237 | copy_job = job_source.getActiveJobs(target_archive).one() |
238 | self.assertEqual(target_archive, copy_job.target_archive) |
239 | |
240 | + def test_copyPackages(self): |
241 | + """Basic smoke test""" |
242 | + (source_archive, source_name, target_archive, to_pocket, to_series, |
243 | + uploader_dude, version) = self.setup_data() |
244 | + |
245 | + ws_target_archive = self.wsObject(target_archive, user=uploader_dude) |
246 | + ws_source_archive = self.wsObject(source_archive) |
247 | + |
248 | + ws_target_archive.copyPackages( |
249 | + source_names=[source_name], from_archive=ws_source_archive, |
250 | + to_pocket=to_pocket.name, to_series=to_series.name, |
251 | + include_binaries=False) |
252 | + transaction.commit() |
253 | + |
254 | + job_source = getUtility(IPlainPackageCopyJobSource) |
255 | + copy_job = job_source.getActiveJobs(target_archive).one() |
256 | + self.assertEqual(target_archive, copy_job.target_archive) |
257 | |
258 | def test_suite(): |
259 | return unittest.TestLoader().loadTestsFromName(__name__) |
260 | |
261 | === modified file 'lib/lp/soyuz/browser/tests/test_package_copying_mixin.py' |
262 | --- lib/lp/soyuz/browser/tests/test_package_copying_mixin.py 2011-06-02 08:16:03 +0000 |
263 | +++ lib/lp/soyuz/browser/tests/test_package_copying_mixin.py 2011-07-18 08:59:28 +0000 |
264 | @@ -219,7 +219,7 @@ |
265 | pocket = self.factory.getAnyPocket() |
266 | copy_asynchronously( |
267 | [spph], archive, dest_series, pocket, include_binaries=False, |
268 | - check_permissions=False) |
269 | + check_permissions=False, person=self.factory.makePerson()) |
270 | self.assertEqual(None, find_spph_copy(archive, spph)) |
271 | |
272 | def test_copy_synchronously_lists_packages(self): |
273 | @@ -243,7 +243,7 @@ |
274 | archive = dest_series.distribution.main_archive |
275 | copy_asynchronously( |
276 | [spph], archive, dest_series, pocket, include_binaries=False, |
277 | - check_permissions=False) |
278 | + check_permissions=False, person=self.factory.makePerson()) |
279 | jobs = list(getUtility(IPlainPackageCopyJobSource).getActiveJobs( |
280 | archive)) |
281 | self.assertEqual(1, len(jobs)) |
282 | @@ -263,7 +263,7 @@ |
283 | view.canCopySynchronously = FakeMethod(result=False) |
284 | view.do_copy( |
285 | 'selected_differences', [spph], archive, dest_series, pocket, |
286 | - False, check_permissions=False) |
287 | + False, check_permissions=False, person=self.factory.makePerson()) |
288 | jobs = list(getUtility(IPlainPackageCopyJobSource).getActiveJobs( |
289 | archive)) |
290 | self.assertNotEqual([], jobs) |
291 | |
292 | === modified file 'lib/lp/soyuz/interfaces/archive.py' |
293 | --- lib/lp/soyuz/interfaces/archive.py 2011-07-18 08:59:27 +0000 |
294 | +++ lib/lp/soyuz/interfaces/archive.py 2011-07-18 08:59:28 +0000 |
295 | @@ -1258,6 +1258,51 @@ |
296 | :raises CannotCopy: if there is a problem copying. |
297 | """ |
298 | |
299 | + @call_with(person=REQUEST_USER) |
300 | + @operation_parameters( |
301 | + source_names=List( |
302 | + title=_("Source package names"), |
303 | + value_type=TextLine()), |
304 | + from_archive=Reference(schema=Interface), |
305 | + #Really IArchive, see below |
306 | + to_pocket=TextLine(title=_("Pocket name")), |
307 | + to_series=TextLine(title=_("Distroseries name"), required=False), |
308 | + include_binaries=Bool( |
309 | + title=_("Include Binaries"), |
310 | + description=_("Whether or not to copy binaries already built for" |
311 | + " this source"), |
312 | + required=False)) |
313 | + @export_write_operation() |
314 | + @operation_for_version('devel') |
315 | + def copyPackages(source_names, from_archive, to_pocket, person, |
316 | + to_series=None, include_binaries=False): |
317 | + """Atomically copy multiple named sources into this archive from another. |
318 | + |
319 | + Asynchronously copy the most recent PUBLISHED versions of the named |
320 | + sources to the destination archive if necessary. Calls to this |
321 | + method will return immediately if the copy passes basic security |
322 | + checks and the copy will happen sometime later with full checking. |
323 | + |
324 | + This operation will only succeed when all requested packages |
325 | + are synchronised between the archives. If any of the requested |
326 | + copies cannot be performed, the whole operation will fail. There |
327 | + will be no partial changes of the destination archive. |
328 | + |
329 | + :param source_names: a list of string names of packages to copy. |
330 | + :param from_archive: the source archive from which to copy. |
331 | + :param to_pocket: the target pocket (as a string). |
332 | + :param to_series: the target distroseries (as a string). |
333 | + :param include_binaries: optional boolean, controls whether or not |
334 | + the published binaries for each given source should also be |
335 | + copied along with the source. |
336 | + :param person: the `IPerson` who requests the sync. |
337 | + |
338 | + :raises NoSuchSourcePackageName: if the source name is invalid |
339 | + :raises PocketNotFound: if the pocket name is invalid |
340 | + :raises NoSuchDistroSeries: if the distro series name is invalid |
341 | + :raises CannotCopy: if there is a problem copying. |
342 | + """ |
343 | + |
344 | |
345 | class IArchiveAppend(Interface): |
346 | """Archive interface for operations restricted by append privilege.""" |
347 | |
348 | === modified file 'lib/lp/soyuz/interfaces/packagecopyjob.py' |
349 | --- lib/lp/soyuz/interfaces/packagecopyjob.py 2011-07-12 14:23:40 +0000 |
350 | +++ lib/lp/soyuz/interfaces/packagecopyjob.py 2011-07-18 08:59:28 +0000 |
351 | @@ -126,7 +126,7 @@ |
352 | def create(package_name, source_archive, |
353 | target_archive, target_distroseries, target_pocket, |
354 | include_binaries=False, package_version=None, |
355 | - copy_policy=PackageCopyPolicy.INSECURE): |
356 | + copy_policy=PackageCopyPolicy.INSECURE, requester=None): |
357 | """Create a new `IPlainPackageCopyJob`. |
358 | |
359 | :param package_name: The name of the source package to copy. |
360 | @@ -141,9 +141,10 @@ |
361 | :param package_version: The version string for the package version |
362 | that is to be copied. |
363 | :param copy_policy: Applicable `PackageCopyPolicy`. |
364 | + :param requester: The user requesting the copy. |
365 | """ |
366 | |
367 | - def createMultiple(target_distroseries, copy_tasks, |
368 | + def createMultiple(target_distroseries, copy_tasks, requester, |
369 | copy_policy=PackageCopyPolicy.INSECURE, |
370 | include_binaries=False): |
371 | """Create multiple new `IPlainPackageCopyJob`s at once. |
372 | @@ -153,6 +154,7 @@ |
373 | :param copy_tasks: A list of tuples describing the copies to be |
374 | performed: (package name, package version, source archive, |
375 | target archive, target pocket). |
376 | + :param requester: The user requesting the copy. |
377 | :param copy_policy: Applicable `PackageCopyPolicy`. |
378 | :param include_binaries: As in `do_copy`. |
379 | :return: An iterable of `PackageCopyJob` ids. |
380 | |
381 | === modified file 'lib/lp/soyuz/model/archive.py' |
382 | --- lib/lp/soyuz/model/archive.py 2011-07-18 08:59:27 +0000 |
383 | +++ lib/lp/soyuz/model/archive.py 2011-07-18 08:59:28 +0000 |
384 | @@ -102,6 +102,7 @@ |
385 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
386 | from lp.registry.model.sourcepackagename import SourcePackageName |
387 | from lp.registry.model.teammembership import TeamParticipation |
388 | +from lp.services.database.bulk import load_related |
389 | from lp.services.job.interfaces.job import JobStatus |
390 | from lp.services.propertycache import ( |
391 | cachedproperty, |
392 | @@ -1550,6 +1551,47 @@ |
393 | package_version=version, include_binaries=include_binaries, |
394 | copy_policy=PackageCopyPolicy.INSECURE) |
395 | |
396 | + def copyPackages(self, source_names, from_archive, to_pocket, |
397 | + person, to_series=None, include_binaries=None): |
398 | + """See `IArchive`.""" |
399 | + sources = self._collectLatestPublishedSources( |
400 | + from_archive, source_names) |
401 | + if not sources: |
402 | + raise CannotCopy( |
403 | + "None of the supplied package names are published") |
404 | + |
405 | + # Bulk-load the sourcepackagereleases so that the list |
406 | + # comprehension doesn't generate additional queries. The |
407 | + # sourcepackagenames themselves will already have been loaded when |
408 | + # generating the list of source publications in "sources". |
409 | + load_related( |
410 | + SourcePackageRelease, sources, ["sourcepackagereleaseID"]) |
411 | + sourcepackagenames = [source.sourcepackagerelease.sourcepackagename |
412 | + for source in sources] |
413 | + |
414 | + # Now do a mass check of permissions. |
415 | + pocket = self._text_to_pocket(to_pocket) |
416 | + series = self._text_to_series(to_series) |
417 | + check_copy_permissions( |
418 | + person, self, series, pocket, sourcepackagenames) |
419 | + |
420 | + # If we get this far then we can create the PackageCopyJob. |
421 | + copy_tasks = [] |
422 | + for source in sources: |
423 | + task = ( |
424 | + source.sourcepackagerelease.sourcepackagename, |
425 | + source.sourcepackagerelease.version, |
426 | + from_archive, |
427 | + self, |
428 | + PackagePublishingPocket.RELEASE |
429 | + ) |
430 | + copy_tasks.append(task) |
431 | + |
432 | + job_source = getUtility(IPlainPackageCopyJobSource) |
433 | + job_source.createMultiple( |
434 | + series, copy_tasks, copy_policy=PackageCopyPolicy.INSECURE, |
435 | + include_binaries=include_binaries) |
436 | + |
437 | def _collectLatestPublishedSources(self, from_archive, source_names): |
438 | """Private helper to collect the latest published sources for an |
439 | archive. |
440 | @@ -1557,6 +1599,9 @@ |
441 | :raises NoSuchSourcePackageName: If any of the source_names do not |
442 | exist. |
443 | """ |
444 | + # XXX bigjools bug=810421 |
445 | + # This code is inefficient. It should try to bulk load all the |
446 | + # sourcepackagenames and publications instead of iterating. |
447 | sources = [] |
448 | for name in source_names: |
449 | # Check to see if the source package exists. This will raise |
450 | |
451 | === modified file 'lib/lp/soyuz/model/packagecopyjob.py' |
452 | --- lib/lp/soyuz/model/packagecopyjob.py 2011-07-08 09:06:24 +0000 |
453 | +++ lib/lp/soyuz/model/packagecopyjob.py 2011-07-18 08:59:28 +0000 |
454 | @@ -131,9 +131,11 @@ |
455 | return cls.wrap(IStore(PackageCopyJob).get(PackageCopyJob, pcj_id)) |
456 | |
457 | def __init__(self, source_archive, target_archive, target_distroseries, |
458 | - job_type, metadata, package_name=None, copy_policy=None): |
459 | + job_type, metadata, requester, package_name=None, |
460 | + copy_policy=None): |
461 | super(PackageCopyJob, self).__init__() |
462 | self.job = Job() |
463 | + self.job.requester = requester |
464 | self.job_type = job_type |
465 | self.source_archive = source_archive |
466 | self.target_archive = target_archive |
467 | @@ -250,9 +252,10 @@ |
468 | def create(cls, package_name, source_archive, |
469 | target_archive, target_distroseries, target_pocket, |
470 | include_binaries=False, package_version=None, |
471 | - copy_policy=PackageCopyPolicy.INSECURE): |
472 | + copy_policy=PackageCopyPolicy.INSECURE, requester=None): |
473 | """See `IPlainPackageCopyJobSource`.""" |
474 | assert package_version is not None, "No package version specified." |
475 | + assert requester is not None, "No requester specified." |
476 | metadata = cls._makeMetadata( |
477 | target_pocket, package_version, include_binaries) |
478 | job = PackageCopyJob( |
479 | @@ -262,7 +265,8 @@ |
480 | target_distroseries=target_distroseries, |
481 | package_name=package_name, |
482 | copy_policy=copy_policy, |
483 | - metadata=metadata) |
484 | + metadata=metadata, |
485 | + requester=requester) |
486 | IMasterStore(PackageCopyJob).add(job) |
487 | return cls(job) |
488 | |
489 | @@ -292,12 +296,12 @@ |
490 | return format_string % sqlvalues(*data) |
491 | |
492 | @classmethod |
493 | - def createMultiple(cls, target_distroseries, copy_tasks, |
494 | + def createMultiple(cls, target_distroseries, copy_tasks, requester, |
495 | copy_policy=PackageCopyPolicy.INSECURE, |
496 | include_binaries=False): |
497 | """See `IPlainPackageCopyJobSource`.""" |
498 | store = IMasterStore(Job) |
499 | - job_ids = Job.createMultiple(store, len(copy_tasks)) |
500 | + job_ids = Job.createMultiple(store, len(copy_tasks), requester) |
501 | job_contents = [ |
502 | cls._composeJobInsertionTuple( |
503 | target_distroseries, copy_policy, include_binaries, job_id, |
504 | @@ -478,13 +482,14 @@ |
505 | do_copy( |
506 | sources=[source_package], archive=self.target_archive, |
507 | series=self.target_distroseries, pocket=self.target_pocket, |
508 | - include_binaries=self.include_binaries, check_permissions=False, |
509 | - overrides=[override], send_email=send_email) |
510 | + include_binaries=self.include_binaries, check_permissions=True, |
511 | + person=self.requester, overrides=[override], |
512 | + send_email=send_email) |
513 | |
514 | if pu is not None: |
515 | # A PackageUpload will only exist if the copy job had to be |
516 | # held in the queue because of policy/ancestry checks. If one |
517 | - # does exist we need to make sure |
518 | + # does exist we need to make sure it gets moved to DONE. |
519 | pu.setDone() |
520 | |
521 | def abort(self): |
522 | |
523 | === modified file 'lib/lp/soyuz/tests/test_archive.py' |
524 | --- lib/lp/soyuz/tests/test_archive.py 2011-07-18 08:59:27 +0000 |
525 | +++ lib/lp/soyuz/tests/test_archive.py 2011-07-18 08:59:28 +0000 |
526 | @@ -1975,7 +1975,7 @@ |
527 | source_archive = self.factory.makeArchive() |
528 | target_archive = self.factory.makeArchive(purpose=target_purpose) |
529 | source = self.factory.makeSourcePackagePublishingHistory( |
530 | - archive=source_archive) |
531 | + archive=source_archive, status=PackagePublishingStatus.PUBLISHED) |
532 | source_name = source.source_package_name |
533 | version = source.source_package_version |
534 | to_pocket = PackagePublishingPocket.RELEASE |
535 | @@ -2068,3 +2068,90 @@ |
536 | to_pocket.name, to_series=to_series.name, include_binaries=False, |
537 | person=target_archive.owner) |
538 | |
539 | + def test_copyPackages_with_single_package(self): |
540 | + (source, source_archive, source_name, target_archive, to_pocket, |
541 | + to_series, version) = self._setup_copy_data() |
542 | + |
543 | + with person_logged_in(target_archive.owner): |
544 | + target_archive.copyPackages( |
545 | + [source_name], source_archive, to_pocket.name, |
546 | + to_series=to_series.name, include_binaries=False, |
547 | + person=target_archive.owner) |
548 | + |
549 | + # The source should not be published yet in the target_archive. |
550 | + published = target_archive.getPublishedSources( |
551 | + name=source.source_package_name).any() |
552 | + self.assertIs(None, published) |
553 | + |
554 | + # There should be one copy job. |
555 | + job_source = getUtility(IPlainPackageCopyJobSource) |
556 | + copy_job = job_source.getActiveJobs(target_archive).one() |
557 | + self.assertEqual(target_archive, copy_job.target_archive) |
558 | + |
559 | + def test_copyPackages_with_multiple_packages(self): |
560 | + (source, source_archive, source_name, target_archive, to_pocket, |
561 | + to_series, version) = self._setup_copy_data() |
562 | + sources = [source] |
563 | + sources.append(self.factory.makeSourcePackagePublishingHistory( |
564 | + archive=source_archive, |
565 | + status=PackagePublishingStatus.PUBLISHED)) |
566 | + sources.append(self.factory.makeSourcePackagePublishingHistory( |
567 | + archive=source_archive, |
568 | + status=PackagePublishingStatus.PUBLISHED)) |
569 | + names = [source.sourcepackagerelease.sourcepackagename.name |
570 | + for source in sources] |
571 | + |
572 | + with person_logged_in(target_archive.owner): |
573 | + target_archive.copyPackages( |
574 | + names, source_archive, to_pocket.name, |
575 | + to_series=to_series.name, include_binaries=False, |
576 | + person=target_archive.owner) |
577 | + |
578 | + # Make sure three copy jobs exist. |
579 | + job_source = getUtility(IPlainPackageCopyJobSource) |
580 | + copy_jobs = job_source.getActiveJobs(target_archive) |
581 | + self.assertEqual(3, copy_jobs.count()) |
582 | + |
583 | + def test_copyPackages_disallows_non_primary_archive_uploaders(self): |
584 | + # If copying to a primary archive and you're not an uploader for |
585 | + # the package then you can't copy. |
586 | + (source, source_archive, source_name, target_archive, to_pocket, |
587 | + to_series, version) = self._setup_copy_data( |
588 | + target_purpose=ArchivePurpose.PRIMARY) |
589 | + person = self.factory.makePerson() |
590 | + self.assertRaises( |
591 | + CannotCopy, |
592 | + target_archive.copyPackages, [source_name], source_archive, |
593 | + to_pocket.name, to_series=to_series.name, include_binaries=False, |
594 | + person=person) |
595 | + |
596 | + def test_copyPackages_allows_primary_archive_uploaders(self): |
597 | + # Copying to a primary archive if you're already an uploader is OK. |
598 | + (source, source_archive, source_name, target_archive, to_pocket, |
599 | + to_series, version) = self._setup_copy_data( |
600 | + target_purpose=ArchivePurpose.PRIMARY) |
601 | + person = self.factory.makePerson() |
602 | + with person_logged_in(target_archive.owner): |
603 | + target_archive.newComponentUploader(person, "universe") |
604 | + target_archive.copyPackages( |
605 | + [source_name], source_archive, to_pocket.name, |
606 | + to_series=to_series.name, include_binaries=False, |
607 | + person=person) |
608 | + |
609 | + # There should be one copy job. |
610 | + job_source = getUtility(IPlainPackageCopyJobSource) |
611 | + copy_job = job_source.getActiveJobs(target_archive).one() |
612 | + self.assertEqual(target_archive, copy_job.target_archive) |
613 | + |
614 | + def test_copyPackages_disallows_non_PPA_owners(self): |
615 | + # Only people with launchpad.Append are allowed to call copyPackage. |
616 | + (source, source_archive, source_name, target_archive, to_pocket, |
617 | + to_series, version) = self._setup_copy_data() |
618 | + person = self.factory.makePerson() |
619 | + self.assertTrue(target_archive.is_ppa) |
620 | + self.assertRaises( |
621 | + CannotCopy, |
622 | + target_archive.copyPackages, [source_name], source_archive, |
623 | + to_pocket.name, to_series=to_series.name, include_binaries=False, |
624 | + person=person) |
625 | + |
626 | |
627 | === modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py' |
628 | --- lib/lp/soyuz/tests/test_packagecopyjob.py 2011-07-12 14:52:52 +0000 |
629 | +++ lib/lp/soyuz/tests/test_packagecopyjob.py 2011-07-18 08:59:28 +0000 |
630 | @@ -3,6 +3,7 @@ |
631 | |
632 | """Tests for sync package jobs.""" |
633 | |
634 | +import operator |
635 | from testtools.content import text_content |
636 | from testtools.matchers import ( |
637 | Equals, |
638 | @@ -92,9 +93,10 @@ |
639 | target_archive = dsd.derived_series.main_archive |
640 | target_distroseries = dsd.derived_series |
641 | target_pocket = self.factory.getAnyPocket() |
642 | + requester = self.factory.makePerson() |
643 | return getUtility(IPlainPackageCopyJobSource).create( |
644 | dsd.source_package_name.name, source_archive, target_archive, |
645 | - target_distroseries, target_pocket, |
646 | + target_distroseries, target_pocket, requester=requester, |
647 | package_version=dsd.parent_source_version, **kwargs) |
648 | |
649 | def runJob(self, job): |
650 | @@ -122,13 +124,15 @@ |
651 | distroseries = self.factory.makeDistroSeries() |
652 | archive1 = self.factory.makeArchive(distroseries.distribution) |
653 | archive2 = self.factory.makeArchive(distroseries.distribution) |
654 | + requester = self.factory.makePerson() |
655 | source = getUtility(IPlainPackageCopyJobSource) |
656 | job = source.create( |
657 | package_name="foo", source_archive=archive1, |
658 | target_archive=archive2, target_distroseries=distroseries, |
659 | target_pocket=PackagePublishingPocket.RELEASE, |
660 | package_version="1.0-1", include_binaries=False, |
661 | - copy_policy=PackageCopyPolicy.MASS_SYNC) |
662 | + copy_policy=PackageCopyPolicy.MASS_SYNC, |
663 | + requester=requester) |
664 | self.assertProvides(job, IPackageCopyJob) |
665 | self.assertEquals(archive1.id, job.source_archive_id) |
666 | self.assertEquals(archive1, job.source_archive) |
667 | @@ -140,6 +144,7 @@ |
668 | self.assertEqual("1.0-1", job.package_version) |
669 | self.assertEquals(False, job.include_binaries) |
670 | self.assertEquals(PackageCopyPolicy.MASS_SYNC, job.copy_policy) |
671 | + self.assertEqual(requester, job.requester) |
672 | |
673 | def test_createMultiple_creates_one_job_per_copy(self): |
674 | mother = self.factory.makeDistroSeriesParent() |
675 | @@ -148,6 +153,7 @@ |
676 | derived_series=derived_series) |
677 | mother_package = self.factory.makeSourcePackageName() |
678 | father_package = self.factory.makeSourcePackageName() |
679 | + requester = self.factory.makePerson() |
680 | job_source = getUtility(IPlainPackageCopyJobSource) |
681 | copy_tasks = [ |
682 | ( |
683 | @@ -166,7 +172,8 @@ |
684 | ), |
685 | ] |
686 | job_ids = list( |
687 | - job_source.createMultiple(mother.derived_series, copy_tasks)) |
688 | + job_source.createMultiple(mother.derived_series, copy_tasks, |
689 | + requester)) |
690 | jobs = list(job_source.getActiveJobs(derived_series.main_archive)) |
691 | self.assertContentEqual(job_ids, [job.id for job in jobs]) |
692 | self.assertEqual(len(copy_tasks), len(set([job.job for job in jobs]))) |
693 | @@ -185,17 +192,24 @@ |
694 | for job in jobs] |
695 | self.assertEqual(copy_tasks, requested_copies) |
696 | |
697 | + # The passed requester should be the same on all jobs. |
698 | + actual_requester = set(job.requester for job in jobs) |
699 | + self.assertEqual(1, len(actual_requester)) |
700 | + self.assertEqual(requester, jobs[0].requester) |
701 | + |
702 | def test_getActiveJobs(self): |
703 | # getActiveJobs() can retrieve all active jobs for an archive. |
704 | distroseries = self.factory.makeDistroSeries() |
705 | archive1 = self.factory.makeArchive(distroseries.distribution) |
706 | archive2 = self.factory.makeArchive(distroseries.distribution) |
707 | source = getUtility(IPlainPackageCopyJobSource) |
708 | + requester = self.factory.makePerson() |
709 | job = source.create( |
710 | package_name="foo", source_archive=archive1, |
711 | target_archive=archive2, target_distroseries=distroseries, |
712 | target_pocket=PackagePublishingPocket.RELEASE, |
713 | - package_version="1.0-1", include_binaries=False) |
714 | + package_version="1.0-1", include_binaries=False, |
715 | + requester=requester) |
716 | self.assertContentEqual([job], source.getActiveJobs(archive2)) |
717 | |
718 | def test_getActiveJobs_gets_oldest_first(self): |
719 | @@ -249,12 +263,14 @@ |
720 | distroseries = self.factory.makeDistroSeries() |
721 | archive1 = self.factory.makeArchive(distroseries.distribution) |
722 | archive2 = self.factory.makeArchive(distroseries.distribution) |
723 | + requester = self.factory.makePerson() |
724 | job_source = getUtility(IPlainPackageCopyJobSource) |
725 | job = job_source.create( |
726 | package_name="foo", source_archive=archive1, |
727 | target_archive=archive2, target_distroseries=distroseries, |
728 | target_pocket=PackagePublishingPocket.RELEASE, |
729 | - package_version="1.0-1", include_binaries=False) |
730 | + package_version="1.0-1", include_binaries=False, |
731 | + requester=requester) |
732 | naked_job = removeSecurityProxy(job) |
733 | naked_job.reportFailure = FakeMethod() |
734 | |
735 | @@ -268,12 +284,14 @@ |
736 | package = self.factory.makeSourcePackageName() |
737 | archive1 = self.factory.makeArchive(distroseries.distribution) |
738 | archive2 = self.factory.makeArchive(distroseries.distribution) |
739 | + requester = self.factory.makePerson() |
740 | source = getUtility(IPlainPackageCopyJobSource) |
741 | job = source.create( |
742 | package_name=package.name, source_archive=archive1, |
743 | target_archive=archive2, target_distroseries=distroseries, |
744 | target_pocket=PackagePublishingPocket.UPDATES, |
745 | - include_binaries=False, package_version='1.0') |
746 | + include_binaries=False, package_version='1.0', |
747 | + requester=requester) |
748 | |
749 | naked_job = removeSecurityProxy(job) |
750 | naked_job.reportFailure = FakeMethod() |
751 | @@ -315,12 +333,16 @@ |
752 | archive=target_archive) |
753 | |
754 | source = getUtility(IPlainPackageCopyJobSource) |
755 | + requester = self.factory.makePerson() |
756 | + with person_logged_in(target_archive.owner): |
757 | + target_archive.newComponentUploader(requester, "main") |
758 | job = source.create( |
759 | package_name="libc", |
760 | source_archive=breezy_archive, target_archive=target_archive, |
761 | target_distroseries=target_series, |
762 | target_pocket=PackagePublishingPocket.RELEASE, |
763 | - package_version="2.8-1", include_binaries=False) |
764 | + package_version="2.8-1", include_binaries=False, |
765 | + requester=requester) |
766 | self.assertEqual("libc", job.package_name) |
767 | self.assertEqual("2.8-1", job.package_version) |
768 | |
769 | @@ -348,12 +370,14 @@ |
770 | distroseries = self.factory.makeDistroSeries() |
771 | archive1 = self.factory.makeArchive(distroseries.distribution) |
772 | archive2 = self.factory.makeArchive(distroseries.distribution) |
773 | + requester = self.factory.makePerson() |
774 | source = getUtility(IPlainPackageCopyJobSource) |
775 | job = source.create( |
776 | package_name="foo", source_archive=archive1, |
777 | target_archive=archive2, target_distroseries=distroseries, |
778 | target_pocket=PackagePublishingPocket.RELEASE, |
779 | - package_version="1.0-1", include_binaries=False) |
780 | + package_version="1.0-1", include_binaries=False, |
781 | + requester=requester) |
782 | oops_vars = job.getOopsVars() |
783 | naked_job = removeSecurityProxy(job) |
784 | self.assertIn( |
785 | @@ -374,6 +398,7 @@ |
786 | distroseries = publisher.breezy_autotest |
787 | archive1 = self.factory.makeArchive(distroseries.distribution) |
788 | archive2 = self.factory.makeArchive(distroseries.distribution) |
789 | + requester = self.factory.makePerson() |
790 | publisher.getPubSource( |
791 | distroseries=distroseries, sourcename="libc", |
792 | version="2.8-1", status=PackagePublishingStatus.PUBLISHED, |
793 | @@ -382,7 +407,10 @@ |
794 | package_name="libc", source_archive=archive1, |
795 | target_archive=archive2, target_distroseries=distroseries, |
796 | target_pocket=PackagePublishingPocket.RELEASE, |
797 | - package_version="2.8-1", include_binaries=False) |
798 | + package_version="2.8-1", include_binaries=False, |
799 | + requester=requester) |
800 | + with person_logged_in(archive2.owner): |
801 | + archive2.newComponentUploader(requester, "main") |
802 | transaction.commit() |
803 | |
804 | out, err, exit_code = run_script( |
805 | @@ -401,12 +429,14 @@ |
806 | distroseries = self.factory.makeDistroSeries() |
807 | archive1 = self.factory.makeArchive(distroseries.distribution) |
808 | archive2 = self.factory.makeArchive(distroseries.distribution) |
809 | + requester = self.factory.makePerson() |
810 | source = getUtility(IPlainPackageCopyJobSource) |
811 | job = source.create( |
812 | package_name="foo", source_archive=archive1, |
813 | target_archive=archive2, target_distroseries=distroseries, |
814 | target_pocket=PackagePublishingPocket.RELEASE, |
815 | - package_version="1.0-1", include_binaries=True) |
816 | + package_version="1.0-1", include_binaries=True, |
817 | + requester=requester) |
818 | self.assertEqual( |
819 | ("<PlainPackageCopyJob to copy package foo from " |
820 | "{distroseries.distribution.name}/{archive1.name} to " |
821 | @@ -519,6 +549,9 @@ |
822 | # target_archive. |
823 | |
824 | source = getUtility(IPlainPackageCopyJobSource) |
825 | + requester = self.factory.makePerson() |
826 | + with person_logged_in(target_archive.owner): |
827 | + target_archive.newComponentUploader(requester, "restricted") |
828 | job = source.create( |
829 | package_name="libc", |
830 | package_version="2.8-1", |
831 | @@ -526,7 +559,8 @@ |
832 | target_archive=target_archive, |
833 | target_distroseries=distroseries, |
834 | target_pocket=PackagePublishingPocket.RELEASE, |
835 | - include_binaries=False) |
836 | + include_binaries=False, |
837 | + requester=requester) |
838 | |
839 | self.runJob(job) |
840 | |
841 | @@ -556,6 +590,9 @@ |
842 | # Now, run the copy job, which should raise an error because |
843 | # there's no ancestry. |
844 | source = getUtility(IPlainPackageCopyJobSource) |
845 | + requester = self.factory.makePerson() |
846 | + with person_logged_in(target_archive.owner): |
847 | + target_archive.newComponentUploader(requester, "main") |
848 | job = source.create( |
849 | package_name="copyme", |
850 | package_version="2.8-1", |
851 | @@ -563,7 +600,8 @@ |
852 | target_archive=target_archive, |
853 | target_distroseries=distroseries, |
854 | target_pocket=PackagePublishingPocket.RELEASE, |
855 | - include_binaries=False) |
856 | + include_binaries=False, |
857 | + requester=requester) |
858 | |
859 | self.assertRaises(SuspendJobException, self.runJob, job) |
860 | # Simulate the job runner suspending after getting a |
861 | @@ -619,6 +657,7 @@ |
862 | # Now, run the copy job. |
863 | |
864 | source = getUtility(IPlainPackageCopyJobSource) |
865 | + requester = self.factory.makePerson() |
866 | job = source.create( |
867 | package_name="copyme", |
868 | package_version="2.8-1", |
869 | @@ -626,7 +665,8 @@ |
870 | target_archive=target_archive, |
871 | target_distroseries=distroseries, |
872 | target_pocket=PackagePublishingPocket.RELEASE, |
873 | - include_binaries=False) |
874 | + include_binaries=False, |
875 | + requester=requester) |
876 | |
877 | # The job should be suspended and there's a PackageUpload with |
878 | # its package_copy_job set. |
879 | @@ -672,6 +712,7 @@ |
880 | |
881 | # Now, run the copy job. |
882 | source = getUtility(IPlainPackageCopyJobSource) |
883 | + requester = self.factory.makePerson() |
884 | job = source.create( |
885 | package_name="copyme", |
886 | package_version="2.8-1", |
887 | @@ -679,7 +720,8 @@ |
888 | target_archive=target_archive, |
889 | target_distroseries=distroseries, |
890 | target_pocket=PackagePublishingPocket.RELEASE, |
891 | - include_binaries=False) |
892 | + include_binaries=False, |
893 | + requester=requester) |
894 | |
895 | # The job should be suspended and there's a PackageUpload with |
896 | # its package_copy_job set in the UNAPPROVED queue. |
897 | @@ -703,6 +745,7 @@ |
898 | publisher = SoyuzTestPublisher() |
899 | publisher.prepareBreezyAutotest() |
900 | distroseries = publisher.breezy_autotest |
901 | + distroseries.changeslist = "changes@example.com" |
902 | |
903 | target_archive = self.factory.makeArchive( |
904 | distroseries.distribution, purpose=ArchivePurpose.PRIMARY) |
905 | @@ -715,6 +758,9 @@ |
906 | archive=source_archive) |
907 | |
908 | source = getUtility(IPlainPackageCopyJobSource) |
909 | + requester = self.factory.makePerson(email="requester@example.com") |
910 | + with person_logged_in(target_archive.owner): |
911 | + target_archive.newComponentUploader(requester, "main") |
912 | job = source.create( |
913 | package_name="copyme", |
914 | package_version="2.8-1", |
915 | @@ -722,7 +768,8 @@ |
916 | target_archive=target_archive, |
917 | target_distroseries=distroseries, |
918 | target_pocket=PackagePublishingPocket.RELEASE, |
919 | - include_binaries=False) |
920 | + include_binaries=False, |
921 | + requester=requester) |
922 | |
923 | # Run the job so it gains a PackageUpload. |
924 | self.assertRaises(SuspendJobException, self.runJob, job) |
925 | @@ -736,6 +783,9 @@ |
926 | pu = getUtility(IPackageUploadSet).getByPackageCopyJobIDs( |
927 | [removeSecurityProxy(job).context.id]).one() |
928 | pu.acceptFromQueue() |
929 | + # Clear existing emails so we can see only the ones the job |
930 | + # generates later. |
931 | + pop_notifications() |
932 | self.runJob(job) |
933 | |
934 | # The job should have set the PU status to DONE: |
935 | @@ -745,6 +795,16 @@ |
936 | existing_sources = target_archive.getPublishedSources(name='copyme') |
937 | self.assertIsNot(None, existing_sources.any()) |
938 | |
939 | + # It would be nice to test emails in a separate test but it would |
940 | + # require all of the same setup as above again so we might as well |
941 | + # do it here. |
942 | + emails = pop_notifications(sort_key=operator.itemgetter('To')) |
943 | + |
944 | + # We expect an uploader email and an announcement to the changeslist. |
945 | + self.assertEquals(2, len(emails)) |
946 | + self.assertIn("requester@example.com", emails[0]['To']) |
947 | + self.assertIn("changes@example.com", emails[1]['To']) |
948 | + |
949 | def test_findMatchingDSDs_matches_all_DSDs_for_job(self): |
950 | # findMatchingDSDs finds matching DSDs for any of the packages |
951 | # in the job. |
952 | |
953 | === modified file 'lib/lp/testing/factory.py' |
954 | --- lib/lp/testing/factory.py 2011-07-13 20:55:34 +0000 |
955 | +++ lib/lp/testing/factory.py 2011-07-18 08:59:28 +0000 |
956 | @@ -4165,7 +4165,8 @@ |
957 | |
958 | def makePlainPackageCopyJob( |
959 | self, package_name=None, package_version=None, source_archive=None, |
960 | - target_archive=None, target_distroseries=None, target_pocket=None): |
961 | + target_archive=None, target_distroseries=None, target_pocket=None, |
962 | + requester=None): |
963 | """Create a new `PlainPackageCopyJob`.""" |
964 | if package_name is None and package_version is None: |
965 | package_name = self.makeSourcePackageName().name |
966 | @@ -4178,10 +4179,12 @@ |
967 | target_distroseries = self.makeDistroSeries() |
968 | if target_pocket is None: |
969 | target_pocket = self.getAnyPocket() |
970 | + if requester is None: |
971 | + requester = self.makePerson() |
972 | return getUtility(IPlainPackageCopyJobSource).create( |
973 | package_name, source_archive, target_archive, |
974 | target_distroseries, target_pocket, |
975 | - package_version=package_version) |
976 | + package_version=package_version, requester=requester) |
977 | |
978 | |
979 | # Some factory methods return simple Python types. We don't add |
Looks good to me.