Merge lp:~cjwatson/launchpad/always-copy-packages-asynchronously-2 into lp:launchpad
- always-copy-packages-asynchronously-2
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | William Grant | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 16270 | ||||
Proposed branch: | lp:~cjwatson/launchpad/always-copy-packages-asynchronously-2 | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
1049 lines (+210/-422) 8 files modified
lib/lp/registry/browser/distroseries.py (+1/-5) lib/lp/registry/browser/tests/test_distroseries.py (+27/-12) lib/lp/services/features/flags.py (+0/-6) lib/lp/soyuz/browser/archive.py (+39/-123) lib/lp/soyuz/browser/tests/archive-views.txt (+8/-59) lib/lp/soyuz/browser/tests/test_package_copying_mixin.py (+1/-151) lib/lp/soyuz/model/packagecopyjob.py (+8/-2) lib/lp/soyuz/stories/ppa/xx-copy-packages.txt (+126/-64) |
||||
To merge this branch: | bzr merge lp:~cjwatson/launchpad/always-copy-packages-asynchronously-2 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+131928@code.launchpad.net |
Commit message
Remove the old synchronous package copy path in favour of PackageCopyJobs.
Description of the change
== Summary ==
This is my second attempt at https:/
== Proposed fix ==
The complicated bit here was fixing xx-copy-
I also noticed that it was a bit odd that the old synchronous path named the target archive in its notification while the new asynchronous path doesn't, and this inconvenienced xx-copy-
== Tests ==
bin/test -vvct lp.registry.
== Demo and Q/A ==
Verify that copies between PPAs using the web UI still work.
Colin Watson (cjwatson) wrote : | # |
Colin Watson (cjwatson) wrote : | # |
OK, this should be worth reviewing again now.
William Grant (wgrant) wrote : | # |
Unless I'm forgetting something, this eliminates the penultimate delayed copy callsite, the last being the API's Archive.
240 + if dest_url is None:
241 + dest_url = escape(
242 + canonical_
243 +
244 + if dest_display_name is None:
245 + dest_display_name = escape(
This will probably double escape them; structured() escapes parameters itself. I'd also avoid talking about the escaping in the docstring, as one wouldn't normally expect a string to be passed through unescaped unless the docstring explicitly asked for HTML.
708 + for copy in copied_
709 + self.logger.
What does the logging around this look like? AFAICT the job doesn't log much else, so it may seem somewhat of a non sequitur in the output.
Colin Watson (cjwatson) wrote : | # |
On Tue, Nov 13, 2012 at 05:49:20AM -0000, William Grant wrote:
> Unless I'm forgetting something, this eliminates the penultimate
> delayed copy callsite, the last being the API's Archive.
> Do you have a strategy for the final excision of that callsite and the
> significant volume of called code?
Not only a strategy but a pending branch that I'll be ready to submit
shortly after this lands.
> This will probably double escape them; structured() escapes parameters
> itself. I'd also avoid talking about the escaping in the docstring, as
> one wouldn't normally expect a string to be passed through unescaped
> unless the docstring explicitly asked for HTML.
This mistake was because compose_
structured() in a slightly different way (with %-formatting rather than
passing separate replacement arguments, so structured() wouldn't have
handled escaping there). Fixed.
> What does the logging around this look like? AFAICT the job doesn't
> log much else, so it may seem somewhat of a non sequitur in the
> output.
I added an extra line of logging to alleviate confusion. (Of course, if
we actually want to see it routinely we'll need to change the PCJ runner
to log at DEBUG; should this be at INFO instead, or is it fine as it
is?)
Preview Diff
1 | === modified file 'lib/lp/registry/browser/distroseries.py' | |||
2 | --- lib/lp/registry/browser/distroseries.py 2012-07-30 12:11:31 +0000 | |||
3 | +++ lib/lp/registry/browser/distroseries.py 2012-11-13 09:52:27 +0000 | |||
4 | @@ -960,15 +960,11 @@ | |||
5 | 960 | 960 | ||
6 | 961 | sponsored_person = data.get("sponsored_person") | 961 | sponsored_person = data.get("sponsored_person") |
7 | 962 | 962 | ||
8 | 963 | # When syncing we *must* do it asynchronously so that a package | ||
9 | 964 | # copy job is created. This gives the job a chance to inspect | ||
10 | 965 | # the copy and create a PackageUpload if required. | ||
11 | 966 | if self.do_copy( | 963 | if self.do_copy( |
12 | 967 | 'selected_differences', sources, self.context.main_archive, | 964 | 'selected_differences', sources, self.context.main_archive, |
13 | 968 | self.context, destination_pocket, include_binaries=False, | 965 | self.context, destination_pocket, include_binaries=False, |
14 | 969 | dest_url=series_url, dest_display_name=series_title, | 966 | dest_url=series_url, dest_display_name=series_title, |
17 | 970 | person=self.user, force_async=True, | 967 | person=self.user, sponsored_person=sponsored_person): |
16 | 971 | sponsored_person=sponsored_person): | ||
18 | 972 | # The copy worked so we redirect back to show the results. Include | 968 | # The copy worked so we redirect back to show the results. Include |
19 | 973 | # the query string so that the user ends up on the same batch page | 969 | # the query string so that the user ends up on the same batch page |
20 | 974 | # with the same filtering parameters as before. | 970 | # with the same filtering parameters as before. |
21 | 975 | 971 | ||
22 | === modified file 'lib/lp/registry/browser/tests/test_distroseries.py' | |||
23 | --- lib/lp/registry/browser/tests/test_distroseries.py 2012-09-28 06:25:44 +0000 | |||
24 | +++ lib/lp/registry/browser/tests/test_distroseries.py 2012-11-13 09:52:27 +0000 | |||
25 | @@ -2099,9 +2099,7 @@ | |||
26 | 2099 | self.assertEqual(0, len(view.errors)) | 2099 | self.assertEqual(0, len(view.errors)) |
27 | 2100 | notifications = view.request.response.notifications | 2100 | notifications = view.request.response.notifications |
28 | 2101 | self.assertEqual(1, len(notifications)) | 2101 | self.assertEqual(1, len(notifications)) |
32 | 2102 | self.assertIn( | 2102 | self.assertIn("Requested sync of 1 package", notifications[0].message) |
30 | 2103 | "Requested sync of 1 package.", | ||
31 | 2104 | notifications[0].message) | ||
33 | 2105 | # 302 is a redirect back to the same page. | 2103 | # 302 is a redirect back to the same page. |
34 | 2106 | self.assertEqual(302, view.request.response.getStatus()) | 2104 | self.assertEqual(302, view.request.response.getStatus()) |
35 | 2107 | 2105 | ||
36 | @@ -2311,25 +2309,42 @@ | |||
37 | 2311 | self.assertEqual(1, len(view.cached_differences.batch)) | 2309 | self.assertEqual(1, len(view.cached_differences.batch)) |
38 | 2312 | 2310 | ||
39 | 2313 | 2311 | ||
41 | 2314 | class TestCopyAsynchronouslyMessage(TestCase): | 2312 | class TestCopyAsynchronouslyMessage(TestCaseWithFactory): |
42 | 2315 | """Test the helper function `copy_asynchronously_message`.""" | 2313 | """Test the helper function `copy_asynchronously_message`.""" |
43 | 2316 | 2314 | ||
44 | 2315 | layer = DatabaseFunctionalLayer | ||
45 | 2316 | |||
46 | 2317 | def setUp(self): | ||
47 | 2318 | super(TestCopyAsynchronouslyMessage, self).setUp() | ||
48 | 2319 | self.archive = self.factory.makeArchive() | ||
49 | 2320 | self.series = self.factory.makeDistroSeries() | ||
50 | 2321 | self.series_url = canonical_url(self.series) | ||
51 | 2322 | self.series_title = self.series.displayname | ||
52 | 2323 | |||
53 | 2324 | def message(self, source_pubs_count): | ||
54 | 2325 | return copy_asynchronously_message( | ||
55 | 2326 | source_pubs_count, self.archive, dest_url=self.series_url, | ||
56 | 2327 | dest_display_name=self.series_title) | ||
57 | 2328 | |||
58 | 2317 | def test_zero_packages(self): | 2329 | def test_zero_packages(self): |
59 | 2318 | self.assertEqual( | 2330 | self.assertEqual( |
62 | 2319 | "Requested sync of 0 packages.", | 2331 | 'Requested sync of 0 packages to <a href="%s">%s</a>.' % |
63 | 2320 | copy_asynchronously_message(0).escapedtext) | 2332 | (self.series_url, self.series_title), |
64 | 2333 | self.message(0).escapedtext) | ||
65 | 2321 | 2334 | ||
66 | 2322 | def test_one_package(self): | 2335 | def test_one_package(self): |
67 | 2323 | self.assertEqual( | 2336 | self.assertEqual( |
71 | 2324 | "Requested sync of 1 package.<br />Please " | 2337 | 'Requested sync of 1 package to <a href="%s">%s</a>.<br />' |
72 | 2325 | "allow some time for this to be processed.", | 2338 | 'Please allow some time for this to be processed.' % |
73 | 2326 | copy_asynchronously_message(1).escapedtext) | 2339 | (self.series_url, self.series_title), |
74 | 2340 | self.message(1).escapedtext) | ||
75 | 2327 | 2341 | ||
76 | 2328 | def test_multiple_packages(self): | 2342 | def test_multiple_packages(self): |
77 | 2329 | self.assertEqual( | 2343 | self.assertEqual( |
81 | 2330 | "Requested sync of 5 packages.<br />Please " | 2344 | 'Requested sync of 5 packages to <a href="%s">%s</a>.<br />' |
82 | 2331 | "allow some time for these to be processed.", | 2345 | 'Please allow some time for these to be processed.' % |
83 | 2332 | copy_asynchronously_message(5).escapedtext) | 2346 | (self.series_url, self.series_title), |
84 | 2347 | self.message(5).escapedtext) | ||
85 | 2333 | 2348 | ||
86 | 2334 | 2349 | ||
87 | 2335 | class TestDistroSeriesNeedsPackagesView(TestCaseWithFactory): | 2350 | class TestDistroSeriesNeedsPackagesView(TestCaseWithFactory): |
88 | 2336 | 2351 | ||
89 | === modified file 'lib/lp/services/features/flags.py' | |||
90 | --- lib/lp/services/features/flags.py 2012-11-08 06:06:22 +0000 | |||
91 | +++ lib/lp/services/features/flags.py 2012-11-13 09:52:27 +0000 | |||
92 | @@ -154,12 +154,6 @@ | |||
93 | 154 | '', | 154 | '', |
94 | 155 | '', | 155 | '', |
95 | 156 | ''), | 156 | ''), |
96 | 157 | ('soyuz.derived_series.max_synchronous_syncs', | ||
97 | 158 | 'int', | ||
98 | 159 | "How many package syncs may be done directly in a web request.", | ||
99 | 160 | '100', | ||
100 | 161 | '', | ||
101 | 162 | ''), | ||
102 | 163 | ('soyuz.derived_series_sync.enabled', | 157 | ('soyuz.derived_series_sync.enabled', |
103 | 164 | 'boolean', | 158 | 'boolean', |
104 | 165 | 'Enables syncing of packages on derivative distributions pages.', | 159 | 'Enables syncing of packages on derivative distributions pages.', |
105 | 166 | 160 | ||
106 | === modified file 'lib/lp/soyuz/browser/archive.py' | |||
107 | --- lib/lp/soyuz/browser/archive.py 2012-11-01 03:41:36 +0000 | |||
108 | +++ lib/lp/soyuz/browser/archive.py 2012-11-13 09:52:27 +0000 | |||
109 | @@ -92,11 +92,7 @@ | |||
110 | 92 | get_plural_text, | 92 | get_plural_text, |
111 | 93 | get_user_agent_distroseries, | 93 | get_user_agent_distroseries, |
112 | 94 | ) | 94 | ) |
118 | 95 | from lp.services.database.bulk import ( | 95 | from lp.services.database.bulk import load_related |
114 | 96 | load, | ||
115 | 97 | load_related, | ||
116 | 98 | ) | ||
117 | 99 | from lp.services.features import getFeatureFlag | ||
119 | 100 | from lp.services.helpers import english_list | 96 | from lp.services.helpers import english_list |
120 | 101 | from lp.services.job.model.job import Job | 97 | from lp.services.job.model.job import Job |
121 | 102 | from lp.services.librarian.browser import FileNavigationMixin | 98 | from lp.services.librarian.browser import FileNavigationMixin |
122 | @@ -166,21 +162,8 @@ | |||
123 | 166 | Archive, | 162 | Archive, |
124 | 167 | validate_ppa, | 163 | validate_ppa, |
125 | 168 | ) | 164 | ) |
141 | 169 | from lp.soyuz.model.binarypackagename import BinaryPackageName | 165 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory |
142 | 170 | from lp.soyuz.model.publishing import ( | 166 | from lp.soyuz.scripts.packagecopier import check_copy_permissions |
128 | 171 | BinaryPackagePublishingHistory, | ||
129 | 172 | SourcePackagePublishingHistory, | ||
130 | 173 | ) | ||
131 | 174 | from lp.soyuz.scripts.packagecopier import ( | ||
132 | 175 | check_copy_permissions, | ||
133 | 176 | do_copy, | ||
134 | 177 | ) | ||
135 | 178 | |||
136 | 179 | # Feature flag: up to how many package sync requests (inclusive) are to be | ||
137 | 180 | # processed synchronously within the web request? | ||
138 | 181 | # Set to -1 to disable synchronous syncs. | ||
139 | 182 | FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS = ( | ||
140 | 183 | 'soyuz.derived_series.max_synchronous_syncs') | ||
143 | 184 | 167 | ||
144 | 185 | 168 | ||
145 | 186 | class ArchiveBadges(HasBadgeBase): | 169 | class ArchiveBadges(HasBadgeBase): |
146 | @@ -1256,63 +1239,6 @@ | |||
147 | 1256 | _messageNoValue = _("vocabulary-copy-to-same-series", "The same series") | 1239 | _messageNoValue = _("vocabulary-copy-to-same-series", "The same series") |
148 | 1257 | 1240 | ||
149 | 1258 | 1241 | ||
150 | 1259 | def preload_binary_package_names(copies): | ||
151 | 1260 | """Preload `BinaryPackageName`s to speed up display-name construction.""" | ||
152 | 1261 | bpn_ids = [ | ||
153 | 1262 | copy.binarypackagerelease.binarypackagenameID for copy in copies | ||
154 | 1263 | if isinstance(copy, BinaryPackagePublishingHistory)] | ||
155 | 1264 | load(BinaryPackageName, bpn_ids) | ||
156 | 1265 | |||
157 | 1266 | |||
158 | 1267 | def compose_synchronous_copy_feedback(copies, dest_archive, dest_url=None, | ||
159 | 1268 | dest_display_name=None): | ||
160 | 1269 | """Compose human-readable feedback after a synchronous copy.""" | ||
161 | 1270 | if dest_url is None: | ||
162 | 1271 | dest_url = escape( | ||
163 | 1272 | canonical_url(dest_archive) + '/+packages', quote=True) | ||
164 | 1273 | |||
165 | 1274 | if dest_display_name is None: | ||
166 | 1275 | dest_display_name = escape(dest_archive.displayname) | ||
167 | 1276 | |||
168 | 1277 | if len(copies) == 0: | ||
169 | 1278 | return structured( | ||
170 | 1279 | '<p>All packages already copied to <a href="%s">%s</a>.</p>' | ||
171 | 1280 | % (dest_url, dest_display_name)) | ||
172 | 1281 | else: | ||
173 | 1282 | messages = [] | ||
174 | 1283 | messages.append( | ||
175 | 1284 | '<p>Packages copied to <a href="%s">%s</a>:</p>' | ||
176 | 1285 | % (dest_url, dest_display_name)) | ||
177 | 1286 | messages.append('<ul>') | ||
178 | 1287 | messages.append("\n".join([ | ||
179 | 1288 | '<li>%s</li>' % escape(copy) for copy in copies])) | ||
180 | 1289 | messages.append('</ul>') | ||
181 | 1290 | return structured("\n".join(messages)) | ||
182 | 1291 | |||
183 | 1292 | |||
184 | 1293 | def copy_synchronously(source_pubs, dest_archive, dest_series, dest_pocket, | ||
185 | 1294 | include_binaries, dest_url=None, | ||
186 | 1295 | dest_display_name=None, person=None, | ||
187 | 1296 | check_permissions=True): | ||
188 | 1297 | """Copy packages right now. | ||
189 | 1298 | |||
190 | 1299 | :return: A `structured` with human-readable feedback about the | ||
191 | 1300 | operation. | ||
192 | 1301 | :raises CannotCopy: If `check_permissions` is True and the copy is | ||
193 | 1302 | not permitted. | ||
194 | 1303 | """ | ||
195 | 1304 | copies = do_copy( | ||
196 | 1305 | source_pubs, dest_archive, dest_series, dest_pocket, include_binaries, | ||
197 | 1306 | allow_delayed_copies=True, person=person, | ||
198 | 1307 | check_permissions=check_permissions) | ||
199 | 1308 | |||
200 | 1309 | preload_binary_package_names(copies) | ||
201 | 1310 | |||
202 | 1311 | return compose_synchronous_copy_feedback( | ||
203 | 1312 | [copy.displayname for copy in copies], dest_archive, dest_url, | ||
204 | 1313 | dest_display_name) | ||
205 | 1314 | |||
206 | 1315 | |||
207 | 1316 | def copy_asynchronously(source_pubs, dest_archive, dest_series, dest_pocket, | 1242 | def copy_asynchronously(source_pubs, dest_archive, dest_series, dest_pocket, |
208 | 1317 | include_binaries, dest_url=None, | 1243 | include_binaries, dest_url=None, |
209 | 1318 | dest_display_name=None, person=None, | 1244 | dest_display_name=None, person=None, |
210 | @@ -1336,29 +1262,45 @@ | |||
211 | 1336 | dest_pocket, include_binaries=include_binaries, | 1262 | dest_pocket, include_binaries=include_binaries, |
212 | 1337 | package_version=spph.sourcepackagerelease.version, | 1263 | package_version=spph.sourcepackagerelease.version, |
213 | 1338 | copy_policy=PackageCopyPolicy.INSECURE, | 1264 | copy_policy=PackageCopyPolicy.INSECURE, |
220 | 1339 | requester=person, sponsored=sponsored, unembargo=True) | 1265 | requester=person, sponsored=sponsored, unembargo=True, |
221 | 1340 | 1266 | source_distroseries=spph.distroseries, source_pocket=spph.pocket) | |
222 | 1341 | return copy_asynchronously_message(len(source_pubs)) | 1267 | |
223 | 1342 | 1268 | return copy_asynchronously_message( | |
224 | 1343 | 1269 | len(source_pubs), dest_archive, dest_url, dest_display_name) | |
225 | 1344 | def copy_asynchronously_message(source_pubs_count): | 1270 | |
226 | 1271 | |||
227 | 1272 | def copy_asynchronously_message(source_pubs_count, dest_archive, dest_url=None, | ||
228 | 1273 | dest_display_name=None): | ||
229 | 1345 | """Return a message detailing the sync action. | 1274 | """Return a message detailing the sync action. |
230 | 1346 | 1275 | ||
231 | 1347 | :param source_pubs_count: The number of source pubs requested for syncing. | 1276 | :param source_pubs_count: The number of source pubs requested for syncing. |
232 | 1277 | :param dest_archive: The destination IArchive. | ||
233 | 1278 | :param dest_url: The URL of the destination to display in the | ||
234 | 1279 | notification box. Defaults to the target archive. | ||
235 | 1280 | :param dest_display_name: The text to use for the dest_url link. | ||
236 | 1281 | Defaults to the target archive's display name. | ||
237 | 1348 | """ | 1282 | """ |
238 | 1283 | if dest_url is None: | ||
239 | 1284 | dest_url = canonical_url(dest_archive) + '/+packages' | ||
240 | 1285 | |||
241 | 1286 | if dest_display_name is None: | ||
242 | 1287 | dest_display_name = dest_archive.displayname | ||
243 | 1288 | |||
244 | 1349 | package_or_packages = get_plural_text( | 1289 | package_or_packages = get_plural_text( |
245 | 1350 | source_pubs_count, "package", "packages") | 1290 | source_pubs_count, "package", "packages") |
246 | 1351 | if source_pubs_count == 0: | 1291 | if source_pubs_count == 0: |
247 | 1352 | return structured( | 1292 | return structured( |
250 | 1353 | "Requested sync of %s %s.", | 1293 | 'Requested sync of %s %s to <a href="%s">%s</a>.', |
251 | 1354 | source_pubs_count, package_or_packages) | 1294 | source_pubs_count, package_or_packages, dest_url, |
252 | 1295 | dest_display_name) | ||
253 | 1355 | else: | 1296 | else: |
254 | 1356 | this_or_these = get_plural_text( | 1297 | this_or_these = get_plural_text( |
255 | 1357 | source_pubs_count, "this", "these") | 1298 | source_pubs_count, "this", "these") |
256 | 1358 | return structured( | 1299 | return structured( |
258 | 1359 | "Requested sync of %s %s.<br />" | 1300 | 'Requested sync of %s %s to <a href="%s">%s</a>.<br />' |
259 | 1360 | "Please allow some time for %s to be processed.", | 1301 | "Please allow some time for %s to be processed.", |
261 | 1361 | source_pubs_count, package_or_packages, this_or_these) | 1302 | source_pubs_count, package_or_packages, dest_url, |
262 | 1303 | dest_display_name, this_or_these) | ||
263 | 1362 | 1304 | ||
264 | 1363 | 1305 | ||
265 | 1364 | def render_cannotcopy_as_html(cannotcopy_exception): | 1306 | def render_cannotcopy_as_html(cannotcopy_exception): |
266 | @@ -1385,28 +1327,14 @@ | |||
267 | 1385 | class PackageCopyingMixin: | 1327 | class PackageCopyingMixin: |
268 | 1386 | """A mixin class that adds helpers for package copying.""" | 1328 | """A mixin class that adds helpers for package copying.""" |
269 | 1387 | 1329 | ||
270 | 1388 | def canCopySynchronously(self, source_pubs): | ||
271 | 1389 | """Can we afford to copy `source_pubs` synchronously?""" | ||
272 | 1390 | # Fixed estimate: up to 100 packages can be copied in acceptable | ||
273 | 1391 | # time. Anything more than that and we go async. | ||
274 | 1392 | limit = getFeatureFlag(FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS) | ||
275 | 1393 | try: | ||
276 | 1394 | limit = int(limit) | ||
277 | 1395 | except: | ||
278 | 1396 | limit = 100 | ||
279 | 1397 | |||
280 | 1398 | return len(source_pubs) <= limit | ||
281 | 1399 | |||
282 | 1400 | def do_copy(self, sources_field_name, source_pubs, dest_archive, | 1330 | def do_copy(self, sources_field_name, source_pubs, dest_archive, |
283 | 1401 | dest_series, dest_pocket, include_binaries, | 1331 | dest_series, dest_pocket, include_binaries, |
284 | 1402 | dest_url=None, dest_display_name=None, person=None, | 1332 | dest_url=None, dest_display_name=None, person=None, |
287 | 1403 | check_permissions=True, force_async=False, | 1333 | check_permissions=True, sponsored_person=None): |
286 | 1404 | sponsored_person=None): | ||
288 | 1405 | """Copy packages and add appropriate feedback to the browser page. | 1334 | """Copy packages and add appropriate feedback to the browser page. |
289 | 1406 | 1335 | ||
293 | 1407 | This may either copy synchronously, if there are few enough | 1336 | This will copy asynchronously, scheduling jobs that will be |
294 | 1408 | requests to process right now; or asynchronously in which case | 1337 | processed by a script. |
292 | 1409 | it will schedule jobs that will be processed by a script. | ||
295 | 1410 | 1338 | ||
296 | 1411 | :param sources_field_name: The name of the form field to set errors | 1339 | :param sources_field_name: The name of the form field to set errors |
297 | 1412 | on when the copy fails | 1340 | on when the copy fails |
298 | @@ -1425,30 +1353,18 @@ | |||
299 | 1425 | :param person: The person requesting the copy. | 1353 | :param person: The person requesting the copy. |
300 | 1426 | :param: check_permissions: boolean indicating whether or not the | 1354 | :param: check_permissions: boolean indicating whether or not the |
301 | 1427 | requester's permissions to copy should be checked. | 1355 | requester's permissions to copy should be checked. |
302 | 1428 | :param force_async: Force the copy to create package copy jobs and | ||
303 | 1429 | perform the copy asynchronously. | ||
304 | 1430 | :param sponsored_person: An IPerson representing the person being | 1356 | :param sponsored_person: An IPerson representing the person being |
306 | 1431 | sponsored (for asynchronous copies only). | 1357 | sponsored. |
307 | 1432 | 1358 | ||
308 | 1433 | :return: True if the copying worked, False otherwise. | 1359 | :return: True if the copying worked, False otherwise. |
309 | 1434 | """ | 1360 | """ |
310 | 1435 | assert force_async or not sponsored_person, ( | ||
311 | 1436 | "sponsored must be None for sync copies") | ||
312 | 1437 | try: | 1361 | try: |
327 | 1438 | if (force_async == False and | 1362 | notification = copy_asynchronously( |
328 | 1439 | self.canCopySynchronously(source_pubs)): | 1363 | source_pubs, dest_archive, dest_series, dest_pocket, |
329 | 1440 | notification = copy_synchronously( | 1364 | include_binaries, dest_url=dest_url, |
330 | 1441 | source_pubs, dest_archive, dest_series, dest_pocket, | 1365 | dest_display_name=dest_display_name, person=person, |
331 | 1442 | include_binaries, dest_url=dest_url, | 1366 | check_permissions=check_permissions, |
332 | 1443 | dest_display_name=dest_display_name, person=person, | 1367 | sponsored=sponsored_person) |
319 | 1444 | check_permissions=check_permissions) | ||
320 | 1445 | else: | ||
321 | 1446 | notification = copy_asynchronously( | ||
322 | 1447 | source_pubs, dest_archive, dest_series, dest_pocket, | ||
323 | 1448 | include_binaries, dest_url=dest_url, | ||
324 | 1449 | dest_display_name=dest_display_name, person=person, | ||
325 | 1450 | check_permissions=check_permissions, | ||
326 | 1451 | sponsored=sponsored_person) | ||
333 | 1452 | except CannotCopy as error: | 1368 | except CannotCopy as error: |
334 | 1453 | self.setFieldError( | 1369 | self.setFieldError( |
335 | 1454 | sources_field_name, render_cannotcopy_as_html(error)) | 1370 | sources_field_name, render_cannotcopy_as_html(error)) |
336 | 1455 | 1371 | ||
337 | === modified file 'lib/lp/soyuz/browser/tests/archive-views.txt' | |||
338 | --- lib/lp/soyuz/browser/tests/archive-views.txt 2012-10-29 16:52:31 +0000 | |||
339 | +++ lib/lp/soyuz/browser/tests/archive-views.txt 2012-11-13 09:52:27 +0000 | |||
340 | @@ -1342,9 +1342,8 @@ | |||
341 | 1342 | Copy private files to public archives | 1342 | Copy private files to public archives |
342 | 1343 | ------------------------------------- | 1343 | ------------------------------------- |
343 | 1344 | 1344 | ||
347 | 1345 | Users are allowed to copy private sources into private PPAs, however | 1345 | Users are allowed to copy private sources into public PPAs. |
348 | 1346 | it happens via 'delayed-copies' not the usual direct copying method. | 1346 | See more information in scripts/packagecopier.py. |
346 | 1347 | See more information in scripts/packagecopier.py | ||
349 | 1348 | 1347 | ||
350 | 1349 | First we will enable Celso's private PPA. | 1348 | First we will enable Celso's private PPA. |
351 | 1350 | 1349 | ||
352 | @@ -1386,61 +1385,13 @@ | |||
353 | 1386 | >>> len(view.errors) | 1385 | >>> len(view.errors) |
354 | 1387 | 0 | 1386 | 0 |
355 | 1388 | 1387 | ||
357 | 1389 | The action is performed as a delayed-copy, and the user is informed of | 1388 | The action is performed as an asynchronous copy, and the user is informed of |
358 | 1390 | it via a page notification. | 1389 | it via a page notification. |
359 | 1391 | 1390 | ||
360 | 1392 | >>> from lp.testing.pages import extract_text | 1391 | >>> from lp.testing.pages import extract_text |
361 | 1393 | >>> for notification in view.request.response.notifications: | 1392 | >>> for notification in view.request.response.notifications: |
362 | 1394 | ... print extract_text(notification.message) | 1393 | ... print extract_text(notification.message) |
412 | 1395 | Packages copied to PPA for Ubuntu Team: | 1394 | Requested sync of 1 package to PPA for Ubuntu Team. |
364 | 1396 | Delayed copy of foocomm - 2.0-1 (source) | ||
365 | 1397 | |||
366 | 1398 | The delayed-copy request is waiting to be processed in the ACCEPTED | ||
367 | 1399 | upload queue. | ||
368 | 1400 | |||
369 | 1401 | >>> from lp.soyuz.interfaces.queue import IPackageUploadSet | ||
370 | 1402 | >>> copy = getUtility(IPackageUploadSet).findSourceUpload( | ||
371 | 1403 | ... 'foocomm', '2.0-1', ubuntu_team_ppa, ubuntu) | ||
372 | 1404 | |||
373 | 1405 | >>> print copy.status.name | ||
374 | 1406 | ACCEPTED | ||
375 | 1407 | |||
376 | 1408 | The action may also be performed asynchronously. | ||
377 | 1409 | |||
378 | 1410 | >>> from lp.services.features.testing import FeatureFixture | ||
379 | 1411 | >>> from lp.soyuz.browser.archive import ( | ||
380 | 1412 | ... FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS, | ||
381 | 1413 | ... ) | ||
382 | 1414 | >>> fixture = FeatureFixture({ | ||
383 | 1415 | ... FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS: '0', | ||
384 | 1416 | ... }) | ||
385 | 1417 | >>> fixture.setUp() | ||
386 | 1418 | |||
387 | 1419 | >>> login('foo.bar@canonical.com') | ||
388 | 1420 | >>> async_private_source = test_publisher.createSource( | ||
389 | 1421 | ... cprov_private_ppa, 'foocomm', '1.0-1', new_version='3.0-1') | ||
390 | 1422 | >>> transaction.commit() | ||
391 | 1423 | |||
392 | 1424 | >>> print async_private_source.displayname | ||
393 | 1425 | foocomm 3.0-1 in hoary | ||
394 | 1426 | |||
395 | 1427 | >>> login('celso.providelo@canonical.com') | ||
396 | 1428 | >>> view = create_initialized_view( | ||
397 | 1429 | ... cprov_private_ppa, name="+copy-packages", | ||
398 | 1430 | ... form={ | ||
399 | 1431 | ... 'field.selected_sources': [str(async_private_source.id)], | ||
400 | 1432 | ... 'field.destination_archive': 'ubuntu-team/ppa', | ||
401 | 1433 | ... 'field.destination_series': '', | ||
402 | 1434 | ... 'field.include_binaries': 'REBUILD_SOURCES', | ||
403 | 1435 | ... 'field.actions.copy': 'Copy', | ||
404 | 1436 | ... }) | ||
405 | 1437 | |||
406 | 1438 | >>> len(view.errors) | ||
407 | 1439 | 0 | ||
408 | 1440 | |||
409 | 1441 | >>> for notification in view.request.response.notifications: | ||
410 | 1442 | ... print extract_text(notification.message) | ||
411 | 1443 | Requested sync of 1 package. | ||
413 | 1444 | Please allow some time for this to be processed. | 1395 | Please allow some time for this to be processed. |
414 | 1445 | 1396 | ||
415 | 1446 | There is one copy job waiting, which we run. | 1397 | There is one copy job waiting, which we run. |
416 | @@ -1463,14 +1414,14 @@ | |||
417 | 1463 | >>> [copied_source] = ubuntu_team_ppa.getPublishedSources( | 1414 | >>> [copied_source] = ubuntu_team_ppa.getPublishedSources( |
418 | 1464 | ... name="foocomm", exact_match=True) | 1415 | ... name="foocomm", exact_match=True) |
419 | 1465 | >>> print copied_source.displayname | 1416 | >>> print copied_source.displayname |
421 | 1466 | foocomm 3.0-1 in hoary | 1417 | foocomm 2.0-1 in hoary |
422 | 1467 | 1418 | ||
423 | 1468 | If we run the same copy again, it will fail. | 1419 | If we run the same copy again, it will fail. |
424 | 1469 | 1420 | ||
425 | 1470 | >>> view = create_initialized_view( | 1421 | >>> view = create_initialized_view( |
426 | 1471 | ... cprov_private_ppa, name="+copy-packages", | 1422 | ... cprov_private_ppa, name="+copy-packages", |
427 | 1472 | ... form={ | 1423 | ... form={ |
429 | 1473 | ... 'field.selected_sources': [str(async_private_source.id)], | 1424 | ... 'field.selected_sources': [str(private_source.id)], |
430 | 1474 | ... 'field.destination_archive': 'ubuntu-team/ppa', | 1425 | ... 'field.destination_archive': 'ubuntu-team/ppa', |
431 | 1475 | ... 'field.destination_series': '', | 1426 | ... 'field.destination_series': '', |
432 | 1476 | ... 'field.include_binaries': 'REBUILD_SOURCES', | 1427 | ... 'field.include_binaries': 'REBUILD_SOURCES', |
433 | @@ -1495,12 +1446,10 @@ | |||
434 | 1495 | >>> for job in ubuntu_team_ppa_view.package_copy_jobs: | 1446 | >>> for job in ubuntu_team_ppa_view.package_copy_jobs: |
435 | 1496 | ... print job.status.title, job.package_name, job.package_version | 1447 | ... print job.status.title, job.package_name, job.package_version |
436 | 1497 | ... print job.error_message | 1448 | ... print job.error_message |
439 | 1498 | Failed foocomm 3.0-1 | 1449 | Failed foocomm 2.0-1 |
440 | 1499 | foocomm 3.0-1 in hoary (same version already building in the destination | 1450 | foocomm 2.0-1 in hoary (same version already building in the destination |
441 | 1500 | archive for Hoary) | 1451 | archive for Hoary) |
442 | 1501 | 1452 | ||
443 | 1502 | >>> fixture.cleanUp() | ||
444 | 1503 | |||
445 | 1504 | 1453 | ||
446 | 1505 | External dependencies validation | 1454 | External dependencies validation |
447 | 1506 | ================================ | 1455 | ================================ |
448 | 1507 | 1456 | ||
449 | === modified file 'lib/lp/soyuz/browser/tests/test_package_copying_mixin.py' | |||
450 | --- lib/lp/soyuz/browser/tests/test_package_copying_mixin.py 2012-10-29 16:52:31 +0000 | |||
451 | +++ lib/lp/soyuz/browser/tests/test_package_copying_mixin.py 2012-11-13 09:52:27 +0000 | |||
452 | @@ -1,4 +1,4 @@ | |||
454 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2011-2012 Canonical Ltd. This software is licensed under the |
455 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
456 | 3 | 3 | ||
457 | 4 | """Tests for `PackageCopyingMixin`.""" | 4 | """Tests for `PackageCopyingMixin`.""" |
458 | @@ -9,14 +9,9 @@ | |||
459 | 9 | from zope.security.proxy import removeSecurityProxy | 9 | from zope.security.proxy import removeSecurityProxy |
460 | 10 | 10 | ||
461 | 11 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 11 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
462 | 12 | from lp.services.features.testing import FeatureFixture | ||
463 | 13 | from lp.services.propertycache import cachedproperty | 12 | from lp.services.propertycache import cachedproperty |
464 | 14 | from lp.soyuz.browser.archive import ( | 13 | from lp.soyuz.browser.archive import ( |
465 | 15 | compose_synchronous_copy_feedback, | ||
466 | 16 | copy_asynchronously, | 14 | copy_asynchronously, |
467 | 17 | copy_synchronously, | ||
468 | 18 | FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS, | ||
469 | 19 | PackageCopyingMixin, | ||
470 | 20 | render_cannotcopy_as_html, | 15 | render_cannotcopy_as_html, |
471 | 21 | ) | 16 | ) |
472 | 22 | from lp.soyuz.enums import SourcePackageFormat | 17 | from lp.soyuz.enums import SourcePackageFormat |
473 | @@ -29,9 +24,7 @@ | |||
474 | 29 | TestCase, | 24 | TestCase, |
475 | 30 | TestCaseWithFactory, | 25 | TestCaseWithFactory, |
476 | 31 | ) | 26 | ) |
477 | 32 | from lp.testing.fakemethod import FakeMethod | ||
478 | 33 | from lp.testing.layers import LaunchpadFunctionalLayer | 27 | from lp.testing.layers import LaunchpadFunctionalLayer |
479 | 34 | from lp.testing.views import create_initialized_view | ||
480 | 35 | 28 | ||
481 | 36 | 29 | ||
482 | 37 | def find_spph_copy(archive, spph): | 30 | def find_spph_copy(archive, spph): |
483 | @@ -41,40 +34,6 @@ | |||
484 | 41 | name=spr.sourcepackagename.name, version=spr.version).one() | 34 | name=spr.sourcepackagename.name, version=spr.version).one() |
485 | 42 | 35 | ||
486 | 43 | 36 | ||
487 | 44 | class FakeDistribution: | ||
488 | 45 | def __init__(self): | ||
489 | 46 | self.archive = FakeArchive() | ||
490 | 47 | |||
491 | 48 | |||
492 | 49 | class FakeDistroSeries: | ||
493 | 50 | def __init__(self): | ||
494 | 51 | self.distribution = FakeDistribution() | ||
495 | 52 | |||
496 | 53 | |||
497 | 54 | class FakeSPN: | ||
498 | 55 | name = "spn-name" | ||
499 | 56 | |||
500 | 57 | |||
501 | 58 | class FakeSPR: | ||
502 | 59 | def __init__(self): | ||
503 | 60 | self.sourcepackagename = FakeSPN() | ||
504 | 61 | self.version = "1.0" | ||
505 | 62 | |||
506 | 63 | |||
507 | 64 | class FakeArchive: | ||
508 | 65 | def __init__(self, displayname="archive-name"): | ||
509 | 66 | self.displayname = displayname | ||
510 | 67 | |||
511 | 68 | |||
512 | 69 | class FakeSPPH: | ||
513 | 70 | def __init__(self, archive=None): | ||
514 | 71 | if archive is None: | ||
515 | 72 | archive = FakeArchive() | ||
516 | 73 | self.sourcepackagerelease = FakeSPR() | ||
517 | 74 | self.displayname = "spph-displayname" | ||
518 | 75 | self.archive = archive | ||
519 | 76 | |||
520 | 77 | |||
521 | 78 | class TestPackageCopyingMixinLight(TestCase): | 37 | class TestPackageCopyingMixinLight(TestCase): |
522 | 79 | """Test lightweight functions and methods. | 38 | """Test lightweight functions and methods. |
523 | 80 | 39 | ||
524 | @@ -83,25 +42,11 @@ | |||
525 | 83 | 42 | ||
526 | 84 | unique_number = 1 | 43 | unique_number = 1 |
527 | 85 | 44 | ||
528 | 86 | def getPocket(self): | ||
529 | 87 | """Return an arbitrary `PackagePublishingPocket`.""" | ||
530 | 88 | return PackagePublishingPocket.SECURITY | ||
531 | 89 | |||
532 | 90 | def getUniqueString(self): | 45 | def getUniqueString(self): |
533 | 91 | """Return an arbitrary string.""" | 46 | """Return an arbitrary string.""" |
534 | 92 | self.unique_number += 1 | 47 | self.unique_number += 1 |
535 | 93 | return "string_%d_" % self.unique_number | 48 | return "string_%d_" % self.unique_number |
536 | 94 | 49 | ||
537 | 95 | def test_canCopySynchronously_allows_small_synchronous_copies(self): | ||
538 | 96 | # Small numbers of packages can be copied synchronously. | ||
539 | 97 | packages = [self.getUniqueString() for counter in range(3)] | ||
540 | 98 | self.assertTrue(PackageCopyingMixin().canCopySynchronously(packages)) | ||
541 | 99 | |||
542 | 100 | def test_canCopySynchronously_disallows_large_synchronous_copies(self): | ||
543 | 101 | # Large numbers of packages must be copied asynchronously. | ||
544 | 102 | packages = [self.getUniqueString() for counter in range(300)] | ||
545 | 103 | self.assertFalse(PackageCopyingMixin().canCopySynchronously(packages)) | ||
546 | 104 | |||
547 | 105 | def test_render_cannotcopy_as_html_lists_errors(self): | 50 | def test_render_cannotcopy_as_html_lists_errors(self): |
548 | 106 | # render_cannotcopy_as_html includes a CannotCopy error message | 51 | # render_cannotcopy_as_html includes a CannotCopy error message |
549 | 107 | # into its HTML notice. | 52 | # into its HTML notice. |
550 | @@ -116,24 +61,6 @@ | |||
551 | 116 | self.assertNotIn(message, html_text) | 61 | self.assertNotIn(message, html_text) |
552 | 117 | self.assertIn("x<>y", html_text) | 62 | self.assertIn("x<>y", html_text) |
553 | 118 | 63 | ||
554 | 119 | def test_compose_synchronous_copy_feedback_escapes_archive_name(self): | ||
555 | 120 | # compose_synchronous_copy_feedback escapes archive displaynames. | ||
556 | 121 | archive = FakeArchive(displayname="a&b") | ||
557 | 122 | notice = compose_synchronous_copy_feedback( | ||
558 | 123 | ["hi"], archive, dest_url="/") | ||
559 | 124 | html_text = notice.escapedtext | ||
560 | 125 | self.assertNotIn("a&b", html_text) | ||
561 | 126 | self.assertIn("a&b", html_text) | ||
562 | 127 | |||
563 | 128 | def test_compose_synchronous_copy_feedback_escapes_package_names(self): | ||
564 | 129 | # compose_synchronous_copy_feedback escapes package names. | ||
565 | 130 | archive = FakeArchive() | ||
566 | 131 | notice = compose_synchronous_copy_feedback( | ||
567 | 132 | ["x<y"], archive, dest_url="/") | ||
568 | 133 | html_text = notice.escapedtext | ||
569 | 134 | self.assertNotIn("x<y", html_text) | ||
570 | 135 | self.assertIn("x<y", html_text) | ||
571 | 136 | |||
572 | 137 | 64 | ||
573 | 138 | class TestPackageCopyingMixinIntegration(TestCaseWithFactory): | 65 | class TestPackageCopyingMixinIntegration(TestCaseWithFactory): |
574 | 139 | """Integration tests for `PackageCopyingMixin`.""" | 66 | """Integration tests for `PackageCopyingMixin`.""" |
575 | @@ -180,36 +107,12 @@ | |||
576 | 180 | derived_series, SourcePackageFormat.FORMAT_1_0) | 107 | derived_series, SourcePackageFormat.FORMAT_1_0) |
577 | 181 | return derived_series | 108 | return derived_series |
578 | 182 | 109 | ||
579 | 183 | def makeView(self): | ||
580 | 184 | """Create a `PackageCopyingMixin`-based view.""" | ||
581 | 185 | return create_initialized_view( | ||
582 | 186 | self.makeDerivedSeries(), "+localpackagediffs") | ||
583 | 187 | |||
584 | 188 | def getUploader(self, archive, spn): | 110 | def getUploader(self, archive, spn): |
585 | 189 | """Get person with upload rights for the given package and archive.""" | 111 | """Get person with upload rights for the given package and archive.""" |
586 | 190 | uploader = archive.owner | 112 | uploader = archive.owner |
587 | 191 | removeSecurityProxy(archive).newPackageUploader(uploader, spn) | 113 | removeSecurityProxy(archive).newPackageUploader(uploader, spn) |
588 | 192 | return uploader | 114 | return uploader |
589 | 193 | 115 | ||
590 | 194 | def test_canCopySynchronously_obeys_feature_flag(self): | ||
591 | 195 | packages = [self.getUniqueString() for counter in range(3)] | ||
592 | 196 | mixin = PackageCopyingMixin() | ||
593 | 197 | with FeatureFixture({FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS: 2}): | ||
594 | 198 | can_copy_synchronously = mixin.canCopySynchronously(packages) | ||
595 | 199 | self.assertFalse(can_copy_synchronously) | ||
596 | 200 | |||
597 | 201 | def test_copy_synchronously_copies_packages(self): | ||
598 | 202 | # copy_synchronously copies packages into the destination | ||
599 | 203 | # archive. | ||
600 | 204 | spph = self.makeSPPH() | ||
601 | 205 | dest_series = self.makeDerivedSeries() | ||
602 | 206 | archive = dest_series.distribution.main_archive | ||
603 | 207 | pocket = self.factory.getAnyPocket() | ||
604 | 208 | copy_synchronously( | ||
605 | 209 | [spph], archive, dest_series, pocket, include_binaries=False, | ||
606 | 210 | check_permissions=False) | ||
607 | 211 | self.assertNotEqual(None, find_spph_copy(archive, spph)) | ||
608 | 212 | |||
609 | 213 | def test_copy_asynchronously_does_not_copy_packages(self): | 116 | def test_copy_asynchronously_does_not_copy_packages(self): |
610 | 214 | # copy_asynchronously does not copy packages into the destination | 117 | # copy_asynchronously does not copy packages into the destination |
611 | 215 | # archive; that happens later, asynchronously. | 118 | # archive; that happens later, asynchronously. |
612 | @@ -222,19 +125,6 @@ | |||
613 | 222 | check_permissions=False, person=self.factory.makePerson()) | 125 | check_permissions=False, person=self.factory.makePerson()) |
614 | 223 | self.assertEqual(None, find_spph_copy(archive, spph)) | 126 | self.assertEqual(None, find_spph_copy(archive, spph)) |
615 | 224 | 127 | ||
616 | 225 | def test_copy_synchronously_lists_packages(self): | ||
617 | 226 | # copy_synchronously returns feedback that includes the names of | ||
618 | 227 | # packages it copied. | ||
619 | 228 | spph = self.makeSPPH() | ||
620 | 229 | dest_series = self.makeDerivedSeries() | ||
621 | 230 | pocket = self.factory.getAnyPocket() | ||
622 | 231 | notice = copy_synchronously( | ||
623 | 232 | [spph], dest_series.distribution.main_archive, dest_series, | ||
624 | 233 | pocket, include_binaries=False, | ||
625 | 234 | check_permissions=False).escapedtext | ||
626 | 235 | self.assertIn( | ||
627 | 236 | spph.sourcepackagerelease.sourcepackagename.name, notice) | ||
628 | 237 | |||
629 | 238 | def test_copy_asynchronously_creates_copy_jobs(self): | 128 | def test_copy_asynchronously_creates_copy_jobs(self): |
630 | 239 | # copy_asynchronously creates PackageCopyJobs. | 129 | # copy_asynchronously creates PackageCopyJobs. |
631 | 240 | spph = self.makeSPPH() | 130 | spph = self.makeSPPH() |
632 | @@ -281,46 +171,6 @@ | |||
633 | 281 | [("one", spph_one.distroseries), ("two", spph_two.distroseries)], | 171 | [("one", spph_one.distroseries), ("two", spph_two.distroseries)], |
634 | 282 | [(job.package_name, job.target_distroseries) for job in jobs]) | 172 | [(job.package_name, job.target_distroseries) for job in jobs]) |
635 | 283 | 173 | ||
636 | 284 | def test_do_copy_goes_async_if_canCopySynchronously_says_so(self): | ||
637 | 285 | # The view opts for asynchronous copying if canCopySynchronously | ||
638 | 286 | # returns False. This creates PackageCopyJobs. | ||
639 | 287 | spph = self.makeSPPH() | ||
640 | 288 | pocket = self.factory.getAnyPocket() | ||
641 | 289 | view = self.makeView() | ||
642 | 290 | dest_series = view.context | ||
643 | 291 | archive = dest_series.distribution.main_archive | ||
644 | 292 | view.canCopySynchronously = FakeMethod(result=False) | ||
645 | 293 | view.do_copy( | ||
646 | 294 | 'selected_differences', [spph], archive, dest_series, pocket, | ||
647 | 295 | False, check_permissions=False, person=self.factory.makePerson()) | ||
648 | 296 | jobs = list(getUtility(IPlainPackageCopyJobSource).getActiveJobs( | ||
649 | 297 | archive)) | ||
650 | 298 | self.assertNotEqual([], jobs) | ||
651 | 299 | |||
652 | 300 | def test_copy_synchronously_may_allow_copy(self): | ||
653 | 301 | # In a normal working situation, copy_synchronously allows a | ||
654 | 302 | # copy. | ||
655 | 303 | spph = self.makeSPPH() | ||
656 | 304 | pocket = PackagePublishingPocket.RELEASE | ||
657 | 305 | dest_series = self.makeDerivedSeries() | ||
658 | 306 | dest_archive = dest_series.main_archive | ||
659 | 307 | spn = spph.sourcepackagerelease.sourcepackagename | ||
660 | 308 | notification = copy_synchronously( | ||
661 | 309 | [spph], dest_archive, dest_series, pocket, False, | ||
662 | 310 | person=self.getUploader(dest_archive, spn)) | ||
663 | 311 | self.assertIn("Packages copied", notification.escapedtext) | ||
664 | 312 | |||
665 | 313 | def test_copy_synchronously_checks_permissions(self): | ||
666 | 314 | # Unless told not to, copy_synchronously does a permissions | ||
667 | 315 | # check. | ||
668 | 316 | spph = self.makeSPPH() | ||
669 | 317 | pocket = self.factory.getAnyPocket() | ||
670 | 318 | dest_series = self.makeDistroSeries() | ||
671 | 319 | self.assertRaises( | ||
672 | 320 | CannotCopy, | ||
673 | 321 | copy_synchronously, | ||
674 | 322 | [spph], dest_series.main_archive, dest_series, pocket, False) | ||
675 | 323 | |||
676 | 324 | def test_copy_asynchronously_may_allow_copy(self): | 174 | def test_copy_asynchronously_may_allow_copy(self): |
677 | 325 | # In a normal working situation, copy_asynchronously allows a | 175 | # In a normal working situation, copy_asynchronously allows a |
678 | 326 | # copy. | 176 | # copy. |
679 | 327 | 177 | ||
680 | === modified file 'lib/lp/soyuz/model/packagecopyjob.py' | |||
681 | --- lib/lp/soyuz/model/packagecopyjob.py 2012-10-25 11:02:37 +0000 | |||
682 | +++ lib/lp/soyuz/model/packagecopyjob.py 2012-11-13 09:52:27 +0000 | |||
683 | @@ -187,6 +187,7 @@ | |||
684 | 187 | 187 | ||
685 | 188 | def __init__(self, job): | 188 | def __init__(self, job): |
686 | 189 | self.context = job | 189 | self.context = job |
687 | 190 | self.logger = logging.getLogger() | ||
688 | 190 | 191 | ||
689 | 191 | @classmethod | 192 | @classmethod |
690 | 192 | def get(cls, job_id): | 193 | def get(cls, job_id): |
691 | @@ -574,8 +575,7 @@ | |||
692 | 574 | # Remember the target archive purpose, as otherwise aborting the | 575 | # Remember the target archive purpose, as otherwise aborting the |
693 | 575 | # transaction will forget it. | 576 | # transaction will forget it. |
694 | 576 | target_archive_purpose = self.target_archive.purpose | 577 | target_archive_purpose = self.target_archive.purpose |
697 | 577 | logger = logging.getLogger() | 578 | self.logger.info("Job:\n%s\nraised CannotCopy:\n%s" % (self, e)) |
696 | 578 | logger.info("Job:\n%s\nraised CannotCopy:\n%s" % (self, e)) | ||
698 | 579 | self.abort() # Abort the txn. | 579 | self.abort() # Abort the txn. |
699 | 580 | self.reportFailure(unicode(e)) | 580 | self.reportFailure(unicode(e)) |
700 | 581 | 581 | ||
701 | @@ -671,6 +671,12 @@ | |||
702 | 671 | # does exist we need to make sure it gets moved to DONE. | 671 | # does exist we need to make sure it gets moved to DONE. |
703 | 672 | pu.setDone() | 672 | pu.setDone() |
704 | 673 | 673 | ||
705 | 674 | if copied_publications: | ||
706 | 675 | self.logger.debug( | ||
707 | 676 | "Packages copied to %s:" % self.target_archive.displayname) | ||
708 | 677 | for copy in copied_publications: | ||
709 | 678 | self.logger.debug(copy.displayname) | ||
710 | 679 | |||
711 | 674 | def abort(self): | 680 | def abort(self): |
712 | 675 | """Abort work.""" | 681 | """Abort work.""" |
713 | 676 | transaction.abort() | 682 | transaction.abort() |
714 | 677 | 683 | ||
715 | === modified file 'lib/lp/soyuz/stories/ppa/xx-copy-packages.txt' | |||
716 | --- lib/lp/soyuz/stories/ppa/xx-copy-packages.txt 2012-09-27 02:53:00 +0000 | |||
717 | +++ lib/lp/soyuz/stories/ppa/xx-copy-packages.txt 2012-11-13 09:52:27 +0000 | |||
718 | @@ -137,6 +137,28 @@ | |||
719 | 137 | >>> flush_database_updates() | 137 | >>> flush_database_updates() |
720 | 138 | >>> logout() | 138 | >>> logout() |
721 | 139 | 139 | ||
722 | 140 | Copying packages will create jobs. Define a simple doctest-friendly runner. | ||
723 | 141 | |||
724 | 142 | >>> from zope.security.proxy import removeSecurityProxy | ||
725 | 143 | >>> from lp.services.log.logger import FakeLogger | ||
726 | 144 | >>> from lp.soyuz.interfaces.packagecopyjob import ( | ||
727 | 145 | ... IPlainPackageCopyJobSource, | ||
728 | 146 | ... ) | ||
729 | 147 | |||
730 | 148 | >>> def run_copy_jobs(): | ||
731 | 149 | ... login('foo.bar@canonical.com') | ||
732 | 150 | ... source = getUtility(IPlainPackageCopyJobSource) | ||
733 | 151 | ... for job in removeSecurityProxy(source).iterReady(): | ||
734 | 152 | ... job.logger = FakeLogger() | ||
735 | 153 | ... job.start(manage_transaction=True) | ||
736 | 154 | ... try: | ||
737 | 155 | ... job.run() | ||
738 | 156 | ... except Exception: | ||
739 | 157 | ... job.fail(manage_transaction=True) | ||
740 | 158 | ... else: | ||
741 | 159 | ... job.complete(manage_transaction=True) | ||
742 | 160 | ... logout() | ||
743 | 161 | |||
744 | 140 | Let's say James wants to rebuild the Celso's 'pmount' source in his PPA. | 162 | Let's say James wants to rebuild the Celso's 'pmount' source in his PPA. |
745 | 141 | 163 | ||
746 | 142 | He is a little confused by the number of packages presented by | 164 | He is a little confused by the number of packages presented by |
747 | @@ -235,8 +257,11 @@ | |||
748 | 235 | >>> messages = get_feedback_messages(jblack_browser.contents) | 257 | >>> messages = get_feedback_messages(jblack_browser.contents) |
749 | 236 | >>> for msg in messages: | 258 | >>> for msg in messages: |
750 | 237 | ... print msg | 259 | ... print msg |
753 | 238 | Packages copied to PPA for James Blackwell: | 260 | Requested sync of 1 package to PPA for James Blackwell. |
754 | 239 | pmount 0.1-1 in hoary | 261 | Please allow some time for this to be processed. |
755 | 262 | >>> run_copy_jobs() | ||
756 | 263 | DEBUG Packages copied to PPA for James Blackwell: | ||
757 | 264 | DEBUG pmount 0.1-1 in hoary | ||
758 | 240 | 265 | ||
759 | 241 | James uses the link in the copy summary to go straight to the target | 266 | James uses the link in the copy summary to go straight to the target |
760 | 242 | PPA, his own. There he can see the just copied package as PENDING and | 267 | PPA, his own. There he can see the just copied package as PENDING and |
761 | @@ -377,9 +402,10 @@ | |||
762 | 377 | >>> messages = get_feedback_messages(jblack_browser.contents) | 402 | >>> messages = get_feedback_messages(jblack_browser.contents) |
763 | 378 | >>> for msg in messages: | 403 | >>> for msg in messages: |
764 | 379 | ... print msg | 404 | ... print msg |
768 | 380 | There is 1 error. | 405 | Requested sync of 1 package to PPA for James Blackwell. |
769 | 381 | The following source cannot be copied: | 406 | Please allow some time for this to be processed. |
770 | 382 | pmount 0.1-1 in hoary | 407 | >>> run_copy_jobs() |
771 | 408 | INFO ... raised CannotCopy: pmount 0.1-1 in hoary | ||
772 | 383 | (same version already building in the destination archive for Hoary) | 409 | (same version already building in the destination archive for Hoary) |
773 | 384 | 410 | ||
774 | 385 | Now, knowing that pmount can only be copied within the same PPA if the | 411 | Now, knowing that pmount can only be copied within the same PPA if the |
775 | @@ -398,8 +424,10 @@ | |||
776 | 398 | >>> messages = get_feedback_messages(jblack_browser.contents) | 424 | >>> messages = get_feedback_messages(jblack_browser.contents) |
777 | 399 | >>> for msg in messages: | 425 | >>> for msg in messages: |
778 | 400 | ... print msg | 426 | ... print msg |
781 | 401 | There is 1 error. | 427 | Requested sync of 1 package to PPA for James Blackwell. |
782 | 402 | The following source cannot be copied: | 428 | Please allow some time for this to be processed. |
783 | 429 | >>> run_copy_jobs() | ||
784 | 430 | INFO ... raised CannotCopy: | ||
785 | 403 | pmount 0.1-1 in hoary (source has no binaries to be copied) | 431 | pmount 0.1-1 in hoary (source has no binaries to be copied) |
786 | 404 | 432 | ||
787 | 405 | We will mark the pmount build completed, to emulate the situation | 433 | We will mark the pmount build completed, to emulate the situation |
788 | @@ -433,9 +461,10 @@ | |||
789 | 433 | >>> messages = get_feedback_messages(jblack_browser.contents) | 461 | >>> messages = get_feedback_messages(jblack_browser.contents) |
790 | 434 | >>> for msg in messages: | 462 | >>> for msg in messages: |
791 | 435 | ... print msg | 463 | ... print msg |
795 | 436 | There is 1 error. | 464 | Requested sync of 1 package to PPA for James Blackwell. |
796 | 437 | The following source cannot be copied: | 465 | Please allow some time for this to be processed. |
797 | 438 | pmount 0.1-1 in hoary | 466 | >>> run_copy_jobs() |
798 | 467 | INFO ... raised CannotCopy: pmount 0.1-1 in hoary | ||
799 | 439 | (same version has unpublished binaries in the destination | 468 | (same version has unpublished binaries in the destination |
800 | 440 | archive for Hoary, please wait for them to be published before | 469 | archive for Hoary, please wait for them to be published before |
801 | 441 | copying) | 470 | copying) |
802 | @@ -453,8 +482,10 @@ | |||
803 | 453 | >>> messages = get_feedback_messages(jblack_browser.contents) | 482 | >>> messages = get_feedback_messages(jblack_browser.contents) |
804 | 454 | >>> for msg in messages: | 483 | >>> for msg in messages: |
805 | 455 | ... print msg | 484 | ... print msg |
808 | 456 | There is 1 error. | 485 | Requested sync of 1 package to PPA for James Blackwell. |
809 | 457 | The following source cannot be copied: | 486 | Please allow some time for this to be processed. |
810 | 487 | >>> run_copy_jobs() | ||
811 | 488 | INFO ... raised CannotCopy: | ||
812 | 458 | pmount 0.1-1 in hoary (source has no binaries to be copied) | 489 | pmount 0.1-1 in hoary (source has no binaries to be copied) |
813 | 459 | 490 | ||
814 | 460 | We will build and publish the architecture independent binary for | 491 | We will build and publish the architecture independent binary for |
815 | @@ -491,9 +522,13 @@ | |||
816 | 491 | >>> messages = get_feedback_messages(jblack_browser.contents) | 522 | >>> messages = get_feedback_messages(jblack_browser.contents) |
817 | 492 | >>> for msg in messages: | 523 | >>> for msg in messages: |
818 | 493 | ... print msg | 524 | ... print msg |
822 | 494 | Packages copied to PPA for James Blackwell: | 525 | Requested sync of 1 package to PPA for James Blackwell. |
823 | 495 | pmount 0.1-1 in grumpy | 526 | Please allow some time for this to be processed. |
824 | 496 | pmount-bin 0.1-1 in grumpy i386 | 527 | >>> run_copy_jobs() |
825 | 528 | DEBUG Packages copied to PPA for James Blackwell: | ||
826 | 529 | DEBUG pmount 0.1-1 in grumpy | ||
827 | 530 | DEBUG pmount-bin 0.1-1 in grumpy i386 | ||
828 | 531 | >>> jblack_browser.open(jblack_browser.url) | ||
829 | 497 | 532 | ||
830 | 498 | Note that only the i386 binary got copied to grumpy since it lacks | 533 | Note that only the i386 binary got copied to grumpy since it lacks |
831 | 499 | hppa support. | 534 | hppa support. |
832 | @@ -519,9 +554,8 @@ | |||
833 | 519 | pmount - 0.1-1 Pending Grumpy Editors | 554 | pmount - 0.1-1 Pending Grumpy Editors |
834 | 520 | pmount - 0.1-1 (Newer...) Pending Hoary Editors | 555 | pmount - 0.1-1 (Newer...) Pending Hoary Editors |
835 | 521 | 556 | ||
839 | 522 | If James performs exactly the same copy procedure again, a message | 557 | If James performs exactly the same copy procedure again, no more packages |
840 | 523 | stating that all packages involved were already copied to the | 558 | will be copied. |
838 | 524 | selected destination PPA will be rendered. | ||
841 | 525 | 559 | ||
842 | 526 | >>> jblack_browser.getControl( | 560 | >>> jblack_browser.getControl( |
843 | 527 | ... name='field.selected_sources').value = [pmount_pub_id] | 561 | ... name='field.selected_sources').value = [pmount_pub_id] |
844 | @@ -533,7 +567,9 @@ | |||
845 | 533 | >>> messages = get_feedback_messages(jblack_browser.contents) | 567 | >>> messages = get_feedback_messages(jblack_browser.contents) |
846 | 534 | >>> for msg in messages: | 568 | >>> for msg in messages: |
847 | 535 | ... print msg | 569 | ... print msg |
849 | 536 | All packages already copied to PPA for James Blackwell. | 570 | Requested sync of 1 package to PPA for James Blackwell. |
850 | 571 | Please allow some time for this to be processed. | ||
851 | 572 | >>> run_copy_jobs() | ||
852 | 537 | 573 | ||
853 | 538 | After some time, James realises that pmount in hoary doesn't make much | 574 | After some time, James realises that pmount in hoary doesn't make much |
854 | 539 | sense and simply deletes it, so his users won't be bothered by this | 575 | sense and simply deletes it, so his users won't be bothered by this |
855 | @@ -603,9 +639,10 @@ | |||
856 | 603 | >>> messages = get_feedback_messages(jblack_browser.contents) | 639 | >>> messages = get_feedback_messages(jblack_browser.contents) |
857 | 604 | >>> for msg in messages: | 640 | >>> for msg in messages: |
858 | 605 | ... print msg | 641 | ... print msg |
862 | 606 | There is 1 error. | 642 | Requested sync of 1 package to PPA for James Blackwell. |
863 | 607 | The following source cannot be copied: | 643 | Please allow some time for this to be processed. |
864 | 608 | pmount 0.1-1 in hoary | 644 | >>> run_copy_jobs() |
865 | 645 | INFO ... raised CannotCopy: pmount 0.1-1 in hoary | ||
866 | 609 | (same version already has published binaries in the destination | 646 | (same version already has published binaries in the destination |
867 | 610 | archive) | 647 | archive) |
868 | 611 | 648 | ||
869 | @@ -624,13 +661,16 @@ | |||
870 | 624 | >>> messages = get_feedback_messages(jblack_browser.contents) | 661 | >>> messages = get_feedback_messages(jblack_browser.contents) |
871 | 625 | >>> for msg in messages: | 662 | >>> for msg in messages: |
872 | 626 | ... print msg | 663 | ... print msg |
877 | 627 | Packages copied to PPA for James Blackwell: | 664 | Requested sync of 1 package to PPA for James Blackwell. |
878 | 628 | pmount 0.1-1 in warty | 665 | Please allow some time for this to be processed. |
879 | 629 | pmount-bin 0.1-1 in warty hppa | 666 | >>> run_copy_jobs() |
880 | 630 | pmount-bin 0.1-1 in warty i386 | 667 | DEBUG Packages copied to PPA for James Blackwell: |
881 | 668 | DEBUG pmount 0.1-1 in warty | ||
882 | 669 | DEBUG pmount-bin 0.1-1 in warty hppa | ||
883 | 670 | DEBUG pmount-bin 0.1-1 in warty i386 | ||
884 | 671 | >>> jblack_browser.open(jblack_browser.url) | ||
885 | 631 | 672 | ||
888 | 632 | James sees the just-copied 'pmount' source in warty pending | 673 | James sees the just-copied 'pmount' source in warty pending publication. |
887 | 633 | publication. | ||
889 | 634 | 674 | ||
890 | 635 | >>> print_ppa_packages(jblack_browser.contents) | 675 | >>> print_ppa_packages(jblack_browser.contents) |
891 | 636 | Source Published Status Series Section Build | 676 | Source Published Status Series Section Build |
892 | @@ -717,12 +757,16 @@ | |||
893 | 717 | >>> messages = get_feedback_messages(jblack_browser.contents) | 757 | >>> messages = get_feedback_messages(jblack_browser.contents) |
894 | 718 | >>> for msg in messages: | 758 | >>> for msg in messages: |
895 | 719 | ... print msg | 759 | ... print msg |
902 | 720 | Packages copied to PPA for James Blackwell Friends: | 760 | Requested sync of 2 packages to PPA for James Blackwell Friends. |
903 | 721 | iceweasel 1.0 in hoary | 761 | Please allow some time for these to be processed. |
904 | 722 | mozilla-firefox 1.0 in hoary i386 | 762 | >>> run_copy_jobs() |
905 | 723 | pmount 0.1-1 in hoary | 763 | DEBUG Packages copied to PPA for James Blackwell Friends: |
906 | 724 | pmount 0.1-1 in hoary hppa | 764 | DEBUG iceweasel 1.0 in hoary |
907 | 725 | pmount 0.1-1 in hoary i386 | 765 | DEBUG mozilla-firefox 1.0 in hoary i386 |
908 | 766 | DEBUG Packages copied to PPA for James Blackwell Friends: | ||
909 | 767 | DEBUG pmount 0.1-1 in hoary | ||
910 | 768 | DEBUG pmount 0.1-1 in hoary hppa | ||
911 | 769 | DEBUG pmount 0.1-1 in hoary i386 | ||
912 | 726 | 770 | ||
913 | 727 | So happy-hacking for James Friends, Celso's 'iceweasel' and 'pmount' | 771 | So happy-hacking for James Friends, Celso's 'iceweasel' and 'pmount' |
914 | 728 | sources and binaries are copied to their PPA. | 772 | sources and binaries are copied to their PPA. |
915 | @@ -780,11 +824,12 @@ | |||
916 | 780 | >>> messages = get_feedback_messages(jblack_browser.contents) | 824 | >>> messages = get_feedback_messages(jblack_browser.contents) |
917 | 781 | >>> for msg in messages: | 825 | >>> for msg in messages: |
918 | 782 | ... print msg | 826 | ... print msg |
922 | 783 | There is 1 error. | 827 | Requested sync of 2 packages to PPA for James Blackwell Friends. |
923 | 784 | The following sources cannot be copied: | 828 | Please allow some time for these to be processed. |
924 | 785 | iceweasel 1.0 in hoary | 829 | >>> run_copy_jobs() |
925 | 830 | INFO ... raised CannotCopy: iceweasel 1.0 in hoary | ||
926 | 786 | (same version already has published binaries in the destination archive) | 831 | (same version already has published binaries in the destination archive) |
928 | 787 | pmount 0.1-1 in hoary | 832 | INFO ... raised CannotCopy: pmount 0.1-1 in hoary |
929 | 788 | (same version already has published binaries in the destination archive) | 833 | (same version already has published binaries in the destination archive) |
930 | 789 | 834 | ||
931 | 790 | James goes wild and decided to create a new team PPA for his sandbox | 835 | James goes wild and decided to create a new team PPA for his sandbox |
932 | @@ -850,12 +895,15 @@ | |||
933 | 850 | >>> messages = get_feedback_messages(jblack_browser.contents) | 895 | >>> messages = get_feedback_messages(jblack_browser.contents) |
934 | 851 | >>> for msg in messages: | 896 | >>> for msg in messages: |
935 | 852 | ... print msg | 897 | ... print msg |
942 | 853 | There is 1 error. | 898 | Requested sync of 3 packages to PPA for James Blackwell Sandbox. |
943 | 854 | The following sources cannot be copied: | 899 | Please allow some time for these to be processed. |
944 | 855 | pmount 0.1-1 in grumpy | 900 | >>> run_copy_jobs() |
945 | 856 | (same version already building in the destination archive for Warty) | 901 | DEBUG Packages copied to PPA for James Blackwell Sandbox: |
946 | 857 | pmount 0.1-1 in hoary | 902 | DEBUG pmount 0.1-1 in warty |
947 | 858 | (same version already building in the destination archive for Warty) | 903 | INFO ... raised CannotCopy: pmount 0.1-1 in grumpy |
948 | 904 | (same version already building in the destination archive for Warty) | ||
949 | 905 | INFO ... raised CannotCopy: pmount 0.1-1 in hoary | ||
950 | 906 | (same version already building in the destination archive for Warty) | ||
951 | 859 | 907 | ||
952 | 860 | Due to the copy error, nothing was copied to the destination PPA, not | 908 | Due to the copy error, nothing was copied to the destination PPA, not |
953 | 861 | even the 'warty' source, which was not denied. | 909 | even the 'warty' source, which was not denied. |
954 | @@ -901,10 +949,14 @@ | |||
955 | 901 | >>> messages = get_feedback_messages(jblack_browser.contents) | 949 | >>> messages = get_feedback_messages(jblack_browser.contents) |
956 | 902 | >>> for msg in messages: | 950 | >>> for msg in messages: |
957 | 903 | ... print msg | 951 | ... print msg |
962 | 904 | Packages copied to PPA for James Blackwell: | 952 | Requested sync of 1 package to PPA for James Blackwell. |
963 | 905 | pmount 0.1-1 in hoary | 953 | Please allow some time for this to be processed. |
964 | 906 | pmount-bin 0.1-1 in hoary hppa | 954 | >>> run_copy_jobs() |
965 | 907 | pmount-bin 0.1-1 in hoary i386 | 955 | DEBUG Packages copied to PPA for James Blackwell: |
966 | 956 | DEBUG pmount 0.1-1 in hoary | ||
967 | 957 | DEBUG pmount-bin 0.1-1 in hoary hppa | ||
968 | 958 | DEBUG pmount-bin 0.1-1 in hoary i386 | ||
969 | 959 | >>> jblack_browser.open(jblack_browser.url) | ||
970 | 908 | 960 | ||
971 | 909 | >>> print_ppa_packages(jblack_browser.contents) | 961 | >>> print_ppa_packages(jblack_browser.contents) |
972 | 910 | Source Published Status Series Section Build | 962 | Source Published Status Series Section Build |
973 | @@ -983,10 +1035,13 @@ | |||
974 | 983 | >>> messages = get_feedback_messages(jblack_browser.contents) | 1035 | >>> messages = get_feedback_messages(jblack_browser.contents) |
975 | 984 | >>> for msg in messages: | 1036 | >>> for msg in messages: |
976 | 985 | ... print msg | 1037 | ... print msg |
981 | 986 | Packages copied to PPA for James Blackwell: | 1038 | Requested sync of 1 package to PPA for James Blackwell. |
982 | 987 | foo 2.0 in hoary | 1039 | Please allow some time for this to be processed. |
983 | 988 | foo-bin 2.0 in hoary hppa | 1040 | >>> run_copy_jobs() |
984 | 989 | foo-bin 2.0 in hoary i386 | 1041 | DEBUG Packages copied to PPA for James Blackwell: |
985 | 1042 | DEBUG foo 2.0 in hoary | ||
986 | 1043 | DEBUG foo-bin 2.0 in hoary hppa | ||
987 | 1044 | DEBUG foo-bin 2.0 in hoary i386 | ||
988 | 990 | 1045 | ||
989 | 991 | James tries to copy some of Celso's packages that are older than | 1046 | James tries to copy some of Celso's packages that are older than |
990 | 992 | the ones in his own PPA. He is not allowed to copy these older | 1047 | the ones in his own PPA. He is not allowed to copy these older |
991 | @@ -1014,9 +1069,10 @@ | |||
992 | 1014 | >>> messages = get_feedback_messages(jblack_browser.contents) | 1069 | >>> messages = get_feedback_messages(jblack_browser.contents) |
993 | 1015 | >>> for msg in messages: | 1070 | >>> for msg in messages: |
994 | 1016 | ... print msg | 1071 | ... print msg |
998 | 1017 | There is 1 error. | 1072 | Requested sync of 1 package to PPA for James Blackwell. |
999 | 1018 | The following source cannot be copied: | 1073 | Please allow some time for this to be processed. |
1000 | 1019 | foo 1.1 in hoary | 1074 | >>> run_copy_jobs() |
1001 | 1075 | INFO ... raised CannotCopy: foo 1.1 in hoary | ||
1002 | 1020 | (version older than the foo 2.0 in hoary published in hoary) | 1076 | (version older than the foo 2.0 in hoary published in hoary) |
1003 | 1021 | 1077 | ||
1004 | 1022 | However if he copies it to another suite is just works (tm) since PPAs | 1078 | However if he copies it to another suite is just works (tm) since PPAs |
1005 | @@ -1032,10 +1088,13 @@ | |||
1006 | 1032 | >>> messages = get_feedback_messages(jblack_browser.contents) | 1088 | >>> messages = get_feedback_messages(jblack_browser.contents) |
1007 | 1033 | >>> for msg in messages: | 1089 | >>> for msg in messages: |
1008 | 1034 | ... print msg | 1090 | ... print msg |
1013 | 1035 | Packages copied to PPA for James Blackwell: | 1091 | Requested sync of 1 package to PPA for James Blackwell. |
1014 | 1036 | foo 1.1 in warty | 1092 | Please allow some time for this to be processed. |
1015 | 1037 | foo-bin 1.1 in warty hppa | 1093 | >>> run_copy_jobs() |
1016 | 1038 | foo-bin 1.1 in warty i386 | 1094 | DEBUG Packages copied to PPA for James Blackwell: |
1017 | 1095 | DEBUG foo 1.1 in warty | ||
1018 | 1096 | DEBUG foo-bin 1.1 in warty hppa | ||
1019 | 1097 | DEBUG foo-bin 1.1 in warty i386 | ||
1020 | 1039 | 1098 | ||
1021 | 1040 | >>> jblack_browser.open( | 1099 | >>> jblack_browser.open( |
1022 | 1041 | ... 'http://launchpad.dev/~jblack/+archive/ppa/+packages') | 1100 | ... 'http://launchpad.dev/~jblack/+archive/ppa/+packages') |
1023 | @@ -1073,9 +1132,10 @@ | |||
1024 | 1073 | >>> messages = get_feedback_messages(jblack_browser.contents) | 1132 | >>> messages = get_feedback_messages(jblack_browser.contents) |
1025 | 1074 | >>> for msg in messages: | 1133 | >>> for msg in messages: |
1026 | 1075 | ... print msg | 1134 | ... print msg |
1030 | 1076 | There is 1 error. | 1135 | Requested sync of 1 package to PPA for James Blackwell. |
1031 | 1077 | The following source cannot be copied: | 1136 | Please allow some time for this to be processed. |
1032 | 1078 | foo 1.1 in hoary | 1137 | >>> run_copy_jobs() |
1033 | 1138 | INFO ... raised CannotCopy: foo 1.1 in hoary | ||
1034 | 1079 | (a different source with the same version is published in the | 1139 | (a different source with the same version is published in the |
1035 | 1080 | destination archive) | 1140 | destination archive) |
1036 | 1081 | 1141 | ||
1037 | @@ -1108,8 +1168,10 @@ | |||
1038 | 1108 | >>> messages = get_feedback_messages(jblack_browser.contents) | 1168 | >>> messages = get_feedback_messages(jblack_browser.contents) |
1039 | 1109 | >>> for msg in messages: | 1169 | >>> for msg in messages: |
1040 | 1110 | ... print msg | 1170 | ... print msg |
1043 | 1111 | There is 1 error. | 1171 | Requested sync of 1 package to PPA for James Blackwell. |
1044 | 1112 | The following source cannot be copied: | 1172 | Please allow some time for this to be processed. |
1045 | 1173 | >>> run_copy_jobs() | ||
1046 | 1174 | INFO ... raised CannotCopy: | ||
1047 | 1113 | foo 9.9 in hoary (source has no binaries to be copied) | 1175 | foo 9.9 in hoary (source has no binaries to be copied) |
1048 | 1114 | 1176 | ||
1049 | 1115 | No game, no matter what he tries, James can't break PPAs. | 1177 | No game, no matter what he tries, James can't break PPAs. |
Don't review this yet - I made another mistake ...