Merge lp:~cjwatson/launchpad/always-copy-packages-asynchronously-2 into lp:launchpad

Proposed by Colin Watson
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
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://code.launchpad.net/~cjwatson/launchpad/always-copy-packages-asynchronously/+merge/131837; Curtis reverted the previous attempt on my behalf since it broke buildbot.

== Proposed fix ==

The complicated bit here was fixing xx-copy-packages.txt, which is a long and horrible doctest built around synchronous copies. The bulk of the fix is fairly mechanical: run copy jobs after each copy operation in the UI. However, to make this work well, I needed to make PCJs say what they're copying in their debug output, as otherwise there was no way to get an accurate idea of which binaries were copied; this seems likely to be occasionally useful elsewhere anyway.

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-packages.txt a bit since it wanted to follow that link. I therefore added this to the asynchronous notification.

== Tests ==

bin/test -vvct lp.registry.browser.tests.test_distroseries -t soyuz

== Demo and Q/A ==

Verify that copies between PPAs using the web UI still work.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

Don't review this yet - I made another mistake ...

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

OK, this should be worth reviewing again now.

Revision history for this message
William Grant (wgrant) wrote :

Unless I'm forgetting something, this eliminates the penultimate delayed copy callsite, the last being the API's Archive.syncSource(s). Do you have a strategy for the final excision of that callsite and the significant volume of called code?

240 + if dest_url is None:
241 + dest_url = escape(
242 + canonical_url(dest_archive) + '/+packages', quote=True)
243 +
244 + if dest_display_name is None:
245 + dest_display_name = escape(dest_archive.displayname)

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_publications:
709 + self.logger.debug(copy.displayname)

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.

review: Approve (code)
Revision history for this message
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.syncSource(s).
> 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_synchronous_copy_feedback used
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py 2012-07-30 12:11:31 +0000
+++ lib/lp/registry/browser/distroseries.py 2012-11-13 09:52:27 +0000
@@ -960,15 +960,11 @@
960960
961 sponsored_person = data.get("sponsored_person")961 sponsored_person = data.get("sponsored_person")
962962
963 # When syncing we *must* do it asynchronously so that a package
964 # copy job is created. This gives the job a chance to inspect
965 # the copy and create a PackageUpload if required.
966 if self.do_copy(963 if self.do_copy(
967 'selected_differences', sources, self.context.main_archive,964 'selected_differences', sources, self.context.main_archive,
968 self.context, destination_pocket, include_binaries=False,965 self.context, destination_pocket, include_binaries=False,
969 dest_url=series_url, dest_display_name=series_title,966 dest_url=series_url, dest_display_name=series_title,
970 person=self.user, force_async=True,967 person=self.user, sponsored_person=sponsored_person):
971 sponsored_person=sponsored_person):
972 # The copy worked so we redirect back to show the results. Include968 # The copy worked so we redirect back to show the results. Include
973 # the query string so that the user ends up on the same batch page969 # the query string so that the user ends up on the same batch page
974 # with the same filtering parameters as before.970 # with the same filtering parameters as before.
975971
=== modified file 'lib/lp/registry/browser/tests/test_distroseries.py'
--- lib/lp/registry/browser/tests/test_distroseries.py 2012-09-28 06:25:44 +0000
+++ lib/lp/registry/browser/tests/test_distroseries.py 2012-11-13 09:52:27 +0000
@@ -2099,9 +2099,7 @@
2099 self.assertEqual(0, len(view.errors))2099 self.assertEqual(0, len(view.errors))
2100 notifications = view.request.response.notifications2100 notifications = view.request.response.notifications
2101 self.assertEqual(1, len(notifications))2101 self.assertEqual(1, len(notifications))
2102 self.assertIn(2102 self.assertIn("Requested sync of 1 package", notifications[0].message)
2103 "Requested sync of 1 package.",
2104 notifications[0].message)
2105 # 302 is a redirect back to the same page.2103 # 302 is a redirect back to the same page.
2106 self.assertEqual(302, view.request.response.getStatus())2104 self.assertEqual(302, view.request.response.getStatus())
21072105
@@ -2311,25 +2309,42 @@
2311 self.assertEqual(1, len(view.cached_differences.batch))2309 self.assertEqual(1, len(view.cached_differences.batch))
23122310
23132311
2314class TestCopyAsynchronouslyMessage(TestCase):2312class TestCopyAsynchronouslyMessage(TestCaseWithFactory):
2315 """Test the helper function `copy_asynchronously_message`."""2313 """Test the helper function `copy_asynchronously_message`."""
23162314
2315 layer = DatabaseFunctionalLayer
2316
2317 def setUp(self):
2318 super(TestCopyAsynchronouslyMessage, self).setUp()
2319 self.archive = self.factory.makeArchive()
2320 self.series = self.factory.makeDistroSeries()
2321 self.series_url = canonical_url(self.series)
2322 self.series_title = self.series.displayname
2323
2324 def message(self, source_pubs_count):
2325 return copy_asynchronously_message(
2326 source_pubs_count, self.archive, dest_url=self.series_url,
2327 dest_display_name=self.series_title)
2328
2317 def test_zero_packages(self):2329 def test_zero_packages(self):
2318 self.assertEqual(2330 self.assertEqual(
2319 "Requested sync of 0 packages.",2331 'Requested sync of 0 packages to <a href="%s">%s</a>.' %
2320 copy_asynchronously_message(0).escapedtext)2332 (self.series_url, self.series_title),
2333 self.message(0).escapedtext)
23212334
2322 def test_one_package(self):2335 def test_one_package(self):
2323 self.assertEqual(2336 self.assertEqual(
2324 "Requested sync of 1 package.<br />Please "2337 'Requested sync of 1 package to <a href="%s">%s</a>.<br />'
2325 "allow some time for this to be processed.",2338 'Please allow some time for this to be processed.' %
2326 copy_asynchronously_message(1).escapedtext)2339 (self.series_url, self.series_title),
2340 self.message(1).escapedtext)
23272341
2328 def test_multiple_packages(self):2342 def test_multiple_packages(self):
2329 self.assertEqual(2343 self.assertEqual(
2330 "Requested sync of 5 packages.<br />Please "2344 'Requested sync of 5 packages to <a href="%s">%s</a>.<br />'
2331 "allow some time for these to be processed.",2345 'Please allow some time for these to be processed.' %
2332 copy_asynchronously_message(5).escapedtext)2346 (self.series_url, self.series_title),
2347 self.message(5).escapedtext)
23332348
23342349
2335class TestDistroSeriesNeedsPackagesView(TestCaseWithFactory):2350class TestDistroSeriesNeedsPackagesView(TestCaseWithFactory):
23362351
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2012-11-08 06:06:22 +0000
+++ lib/lp/services/features/flags.py 2012-11-13 09:52:27 +0000
@@ -154,12 +154,6 @@
154 '',154 '',
155 '',155 '',
156 ''),156 ''),
157 ('soyuz.derived_series.max_synchronous_syncs',
158 'int',
159 "How many package syncs may be done directly in a web request.",
160 '100',
161 '',
162 ''),
163 ('soyuz.derived_series_sync.enabled',157 ('soyuz.derived_series_sync.enabled',
164 'boolean',158 'boolean',
165 'Enables syncing of packages on derivative distributions pages.',159 'Enables syncing of packages on derivative distributions pages.',
166160
=== modified file 'lib/lp/soyuz/browser/archive.py'
--- lib/lp/soyuz/browser/archive.py 2012-11-01 03:41:36 +0000
+++ lib/lp/soyuz/browser/archive.py 2012-11-13 09:52:27 +0000
@@ -92,11 +92,7 @@
92 get_plural_text,92 get_plural_text,
93 get_user_agent_distroseries,93 get_user_agent_distroseries,
94 )94 )
95from lp.services.database.bulk import (95from lp.services.database.bulk import load_related
96 load,
97 load_related,
98 )
99from lp.services.features import getFeatureFlag
100from lp.services.helpers import english_list96from lp.services.helpers import english_list
101from lp.services.job.model.job import Job97from lp.services.job.model.job import Job
102from lp.services.librarian.browser import FileNavigationMixin98from lp.services.librarian.browser import FileNavigationMixin
@@ -166,21 +162,8 @@
166 Archive,162 Archive,
167 validate_ppa,163 validate_ppa,
168 )164 )
169from lp.soyuz.model.binarypackagename import BinaryPackageName165from lp.soyuz.model.publishing import SourcePackagePublishingHistory
170from lp.soyuz.model.publishing import (166from lp.soyuz.scripts.packagecopier import check_copy_permissions
171 BinaryPackagePublishingHistory,
172 SourcePackagePublishingHistory,
173 )
174from lp.soyuz.scripts.packagecopier import (
175 check_copy_permissions,
176 do_copy,
177 )
178
179# Feature flag: up to how many package sync requests (inclusive) are to be
180# processed synchronously within the web request?
181# Set to -1 to disable synchronous syncs.
182FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS = (
183 'soyuz.derived_series.max_synchronous_syncs')
184167
185168
186class ArchiveBadges(HasBadgeBase):169class ArchiveBadges(HasBadgeBase):
@@ -1256,63 +1239,6 @@
1256 _messageNoValue = _("vocabulary-copy-to-same-series", "The same series")1239 _messageNoValue = _("vocabulary-copy-to-same-series", "The same series")
12571240
12581241
1259def preload_binary_package_names(copies):
1260 """Preload `BinaryPackageName`s to speed up display-name construction."""
1261 bpn_ids = [
1262 copy.binarypackagerelease.binarypackagenameID for copy in copies
1263 if isinstance(copy, BinaryPackagePublishingHistory)]
1264 load(BinaryPackageName, bpn_ids)
1265
1266
1267def compose_synchronous_copy_feedback(copies, dest_archive, dest_url=None,
1268 dest_display_name=None):
1269 """Compose human-readable feedback after a synchronous copy."""
1270 if dest_url is None:
1271 dest_url = escape(
1272 canonical_url(dest_archive) + '/+packages', quote=True)
1273
1274 if dest_display_name is None:
1275 dest_display_name = escape(dest_archive.displayname)
1276
1277 if len(copies) == 0:
1278 return structured(
1279 '<p>All packages already copied to <a href="%s">%s</a>.</p>'
1280 % (dest_url, dest_display_name))
1281 else:
1282 messages = []
1283 messages.append(
1284 '<p>Packages copied to <a href="%s">%s</a>:</p>'
1285 % (dest_url, dest_display_name))
1286 messages.append('<ul>')
1287 messages.append("\n".join([
1288 '<li>%s</li>' % escape(copy) for copy in copies]))
1289 messages.append('</ul>')
1290 return structured("\n".join(messages))
1291
1292
1293def copy_synchronously(source_pubs, dest_archive, dest_series, dest_pocket,
1294 include_binaries, dest_url=None,
1295 dest_display_name=None, person=None,
1296 check_permissions=True):
1297 """Copy packages right now.
1298
1299 :return: A `structured` with human-readable feedback about the
1300 operation.
1301 :raises CannotCopy: If `check_permissions` is True and the copy is
1302 not permitted.
1303 """
1304 copies = do_copy(
1305 source_pubs, dest_archive, dest_series, dest_pocket, include_binaries,
1306 allow_delayed_copies=True, person=person,
1307 check_permissions=check_permissions)
1308
1309 preload_binary_package_names(copies)
1310
1311 return compose_synchronous_copy_feedback(
1312 [copy.displayname for copy in copies], dest_archive, dest_url,
1313 dest_display_name)
1314
1315
1316def copy_asynchronously(source_pubs, dest_archive, dest_series, dest_pocket,1242def copy_asynchronously(source_pubs, dest_archive, dest_series, dest_pocket,
1317 include_binaries, dest_url=None,1243 include_binaries, dest_url=None,
1318 dest_display_name=None, person=None,1244 dest_display_name=None, person=None,
@@ -1336,29 +1262,45 @@
1336 dest_pocket, include_binaries=include_binaries,1262 dest_pocket, include_binaries=include_binaries,
1337 package_version=spph.sourcepackagerelease.version,1263 package_version=spph.sourcepackagerelease.version,
1338 copy_policy=PackageCopyPolicy.INSECURE,1264 copy_policy=PackageCopyPolicy.INSECURE,
1339 requester=person, sponsored=sponsored, unembargo=True)1265 requester=person, sponsored=sponsored, unembargo=True,
13401266 source_distroseries=spph.distroseries, source_pocket=spph.pocket)
1341 return copy_asynchronously_message(len(source_pubs))1267
13421268 return copy_asynchronously_message(
13431269 len(source_pubs), dest_archive, dest_url, dest_display_name)
1344def copy_asynchronously_message(source_pubs_count):1270
1271
1272def copy_asynchronously_message(source_pubs_count, dest_archive, dest_url=None,
1273 dest_display_name=None):
1345 """Return a message detailing the sync action.1274 """Return a message detailing the sync action.
13461275
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.
1277 :param dest_archive: The destination IArchive.
1278 :param dest_url: The URL of the destination to display in the
1279 notification box. Defaults to the target archive.
1280 :param dest_display_name: The text to use for the dest_url link.
1281 Defaults to the target archive's display name.
1348 """1282 """
1283 if dest_url is None:
1284 dest_url = canonical_url(dest_archive) + '/+packages'
1285
1286 if dest_display_name is None:
1287 dest_display_name = dest_archive.displayname
1288
1349 package_or_packages = get_plural_text(1289 package_or_packages = get_plural_text(
1350 source_pubs_count, "package", "packages")1290 source_pubs_count, "package", "packages")
1351 if source_pubs_count == 0:1291 if source_pubs_count == 0:
1352 return structured(1292 return structured(
1353 "Requested sync of %s %s.",1293 'Requested sync of %s %s to <a href="%s">%s</a>.',
1354 source_pubs_count, package_or_packages)1294 source_pubs_count, package_or_packages, dest_url,
1295 dest_display_name)
1355 else:1296 else:
1356 this_or_these = get_plural_text(1297 this_or_these = get_plural_text(
1357 source_pubs_count, "this", "these")1298 source_pubs_count, "this", "these")
1358 return structured(1299 return structured(
1359 "Requested sync of %s %s.<br />"1300 'Requested sync of %s %s to <a href="%s">%s</a>.<br />'
1360 "Please allow some time for %s to be processed.",1301 "Please allow some time for %s to be processed.",
1361 source_pubs_count, package_or_packages, this_or_these)1302 source_pubs_count, package_or_packages, dest_url,
1303 dest_display_name, this_or_these)
13621304
13631305
1364def render_cannotcopy_as_html(cannotcopy_exception):1306def render_cannotcopy_as_html(cannotcopy_exception):
@@ -1385,28 +1327,14 @@
1385class PackageCopyingMixin:1327class PackageCopyingMixin:
1386 """A mixin class that adds helpers for package copying."""1328 """A mixin class that adds helpers for package copying."""
13871329
1388 def canCopySynchronously(self, source_pubs):
1389 """Can we afford to copy `source_pubs` synchronously?"""
1390 # Fixed estimate: up to 100 packages can be copied in acceptable
1391 # time. Anything more than that and we go async.
1392 limit = getFeatureFlag(FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS)
1393 try:
1394 limit = int(limit)
1395 except:
1396 limit = 100
1397
1398 return len(source_pubs) <= limit
1399
1400 def do_copy(self, sources_field_name, source_pubs, dest_archive,1330 def do_copy(self, sources_field_name, source_pubs, dest_archive,
1401 dest_series, dest_pocket, include_binaries,1331 dest_series, dest_pocket, include_binaries,
1402 dest_url=None, dest_display_name=None, person=None,1332 dest_url=None, dest_display_name=None, person=None,
1403 check_permissions=True, force_async=False,1333 check_permissions=True, sponsored_person=None):
1404 sponsored_person=None):
1405 """Copy packages and add appropriate feedback to the browser page.1334 """Copy packages and add appropriate feedback to the browser page.
14061335
1407 This may either copy synchronously, if there are few enough1336 This will copy asynchronously, scheduling jobs that will be
1408 requests to process right now; or asynchronously in which case1337 processed by a script.
1409 it will schedule jobs that will be processed by a script.
14101338
1411 :param sources_field_name: The name of the form field to set errors1339 :param sources_field_name: The name of the form field to set errors
1412 on when the copy fails1340 on when the copy fails
@@ -1425,30 +1353,18 @@
1425 :param person: The person requesting the copy.1353 :param person: The person requesting the copy.
1426 :param: check_permissions: boolean indicating whether or not the1354 :param: check_permissions: boolean indicating whether or not the
1427 requester's permissions to copy should be checked.1355 requester's permissions to copy should be checked.
1428 :param force_async: Force the copy to create package copy jobs and
1429 perform the copy asynchronously.
1430 :param sponsored_person: An IPerson representing the person being1356 :param sponsored_person: An IPerson representing the person being
1431 sponsored (for asynchronous copies only).1357 sponsored.
14321358
1433 :return: True if the copying worked, False otherwise.1359 :return: True if the copying worked, False otherwise.
1434 """1360 """
1435 assert force_async or not sponsored_person, (
1436 "sponsored must be None for sync copies")
1437 try:1361 try:
1438 if (force_async == False and1362 notification = copy_asynchronously(
1439 self.canCopySynchronously(source_pubs)):1363 source_pubs, dest_archive, dest_series, dest_pocket,
1440 notification = copy_synchronously(1364 include_binaries, dest_url=dest_url,
1441 source_pubs, dest_archive, dest_series, dest_pocket,1365 dest_display_name=dest_display_name, person=person,
1442 include_binaries, dest_url=dest_url,1366 check_permissions=check_permissions,
1443 dest_display_name=dest_display_name, person=person,1367 sponsored=sponsored_person)
1444 check_permissions=check_permissions)
1445 else:
1446 notification = copy_asynchronously(
1447 source_pubs, dest_archive, dest_series, dest_pocket,
1448 include_binaries, dest_url=dest_url,
1449 dest_display_name=dest_display_name, person=person,
1450 check_permissions=check_permissions,
1451 sponsored=sponsored_person)
1452 except CannotCopy as error:1368 except CannotCopy as error:
1453 self.setFieldError(1369 self.setFieldError(
1454 sources_field_name, render_cannotcopy_as_html(error))1370 sources_field_name, render_cannotcopy_as_html(error))
14551371
=== modified file 'lib/lp/soyuz/browser/tests/archive-views.txt'
--- lib/lp/soyuz/browser/tests/archive-views.txt 2012-10-29 16:52:31 +0000
+++ lib/lp/soyuz/browser/tests/archive-views.txt 2012-11-13 09:52:27 +0000
@@ -1342,9 +1342,8 @@
1342Copy private files to public archives1342Copy private files to public archives
1343-------------------------------------1343-------------------------------------
13441344
1345Users are allowed to copy private sources into private PPAs, however1345Users are allowed to copy private sources into public PPAs.
1346it happens via 'delayed-copies' not the usual direct copying method.1346See more information in scripts/packagecopier.py.
1347See more information in scripts/packagecopier.py
13481347
1349First we will enable Celso's private PPA.1348First we will enable Celso's private PPA.
13501349
@@ -1386,61 +1385,13 @@
1386 >>> len(view.errors)1385 >>> len(view.errors)
1387 01386 0
13881387
1389The action is performed as a delayed-copy, and the user is informed of1388The action is performed as an asynchronous copy, and the user is informed of
1390it via a page notification.1389it via a page notification.
13911390
1392 >>> from lp.testing.pages import extract_text1391 >>> from lp.testing.pages import extract_text
1393 >>> for notification in view.request.response.notifications:1392 >>> for notification in view.request.response.notifications:
1394 ... print extract_text(notification.message)1393 ... print extract_text(notification.message)
1395 Packages copied to PPA for Ubuntu Team:1394 Requested sync of 1 package to PPA for Ubuntu Team.
1396 Delayed copy of foocomm - 2.0-1 (source)
1397
1398The delayed-copy request is waiting to be processed in the ACCEPTED
1399upload queue.
1400
1401 >>> from lp.soyuz.interfaces.queue import IPackageUploadSet
1402 >>> copy = getUtility(IPackageUploadSet).findSourceUpload(
1403 ... 'foocomm', '2.0-1', ubuntu_team_ppa, ubuntu)
1404
1405 >>> print copy.status.name
1406 ACCEPTED
1407
1408The action may also be performed asynchronously.
1409
1410 >>> from lp.services.features.testing import FeatureFixture
1411 >>> from lp.soyuz.browser.archive import (
1412 ... FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS,
1413 ... )
1414 >>> fixture = FeatureFixture({
1415 ... FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS: '0',
1416 ... })
1417 >>> fixture.setUp()
1418
1419 >>> login('foo.bar@canonical.com')
1420 >>> async_private_source = test_publisher.createSource(
1421 ... cprov_private_ppa, 'foocomm', '1.0-1', new_version='3.0-1')
1422 >>> transaction.commit()
1423
1424 >>> print async_private_source.displayname
1425 foocomm 3.0-1 in hoary
1426
1427 >>> login('celso.providelo@canonical.com')
1428 >>> view = create_initialized_view(
1429 ... cprov_private_ppa, name="+copy-packages",
1430 ... form={
1431 ... 'field.selected_sources': [str(async_private_source.id)],
1432 ... 'field.destination_archive': 'ubuntu-team/ppa',
1433 ... 'field.destination_series': '',
1434 ... 'field.include_binaries': 'REBUILD_SOURCES',
1435 ... 'field.actions.copy': 'Copy',
1436 ... })
1437
1438 >>> len(view.errors)
1439 0
1440
1441 >>> for notification in view.request.response.notifications:
1442 ... print extract_text(notification.message)
1443 Requested sync of 1 package.
1444 Please allow some time for this to be processed.1395 Please allow some time for this to be processed.
14451396
1446There is one copy job waiting, which we run.1397There is one copy job waiting, which we run.
@@ -1463,14 +1414,14 @@
1463 >>> [copied_source] = ubuntu_team_ppa.getPublishedSources(1414 >>> [copied_source] = ubuntu_team_ppa.getPublishedSources(
1464 ... name="foocomm", exact_match=True)1415 ... name="foocomm", exact_match=True)
1465 >>> print copied_source.displayname1416 >>> print copied_source.displayname
1466 foocomm 3.0-1 in hoary1417 foocomm 2.0-1 in hoary
14671418
1468If we run the same copy again, it will fail.1419If we run the same copy again, it will fail.
14691420
1470 >>> view = create_initialized_view(1421 >>> view = create_initialized_view(
1471 ... cprov_private_ppa, name="+copy-packages",1422 ... cprov_private_ppa, name="+copy-packages",
1472 ... form={1423 ... form={
1473 ... 'field.selected_sources': [str(async_private_source.id)],1424 ... 'field.selected_sources': [str(private_source.id)],
1474 ... 'field.destination_archive': 'ubuntu-team/ppa',1425 ... 'field.destination_archive': 'ubuntu-team/ppa',
1475 ... 'field.destination_series': '',1426 ... 'field.destination_series': '',
1476 ... 'field.include_binaries': 'REBUILD_SOURCES',1427 ... 'field.include_binaries': 'REBUILD_SOURCES',
@@ -1495,12 +1446,10 @@
1495 >>> for job in ubuntu_team_ppa_view.package_copy_jobs:1446 >>> for job in ubuntu_team_ppa_view.package_copy_jobs:
1496 ... print job.status.title, job.package_name, job.package_version1447 ... print job.status.title, job.package_name, job.package_version
1497 ... print job.error_message1448 ... print job.error_message
1498 Failed foocomm 3.0-11449 Failed foocomm 2.0-1
1499 foocomm 3.0-1 in hoary (same version already building in the destination1450 foocomm 2.0-1 in hoary (same version already building in the destination
1500 archive for Hoary)1451 archive for Hoary)
15011452
1502 >>> fixture.cleanUp()
1503
15041453
1505External dependencies validation1454External dependencies validation
1506================================1455================================
15071456
=== modified file 'lib/lp/soyuz/browser/tests/test_package_copying_mixin.py'
--- lib/lp/soyuz/browser/tests/test_package_copying_mixin.py 2012-10-29 16:52:31 +0000
+++ lib/lp/soyuz/browser/tests/test_package_copying_mixin.py 2012-11-13 09:52:27 +0000
@@ -1,4 +1,4 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the1# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for `PackageCopyingMixin`."""4"""Tests for `PackageCopyingMixin`."""
@@ -9,14 +9,9 @@
9from zope.security.proxy import removeSecurityProxy9from zope.security.proxy import removeSecurityProxy
1010
11from lp.registry.interfaces.pocket import PackagePublishingPocket11from lp.registry.interfaces.pocket import PackagePublishingPocket
12from lp.services.features.testing import FeatureFixture
13from lp.services.propertycache import cachedproperty12from lp.services.propertycache import cachedproperty
14from lp.soyuz.browser.archive import (13from lp.soyuz.browser.archive import (
15 compose_synchronous_copy_feedback,
16 copy_asynchronously,14 copy_asynchronously,
17 copy_synchronously,
18 FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS,
19 PackageCopyingMixin,
20 render_cannotcopy_as_html,15 render_cannotcopy_as_html,
21 )16 )
22from lp.soyuz.enums import SourcePackageFormat17from lp.soyuz.enums import SourcePackageFormat
@@ -29,9 +24,7 @@
29 TestCase,24 TestCase,
30 TestCaseWithFactory,25 TestCaseWithFactory,
31 )26 )
32from lp.testing.fakemethod import FakeMethod
33from lp.testing.layers import LaunchpadFunctionalLayer27from lp.testing.layers import LaunchpadFunctionalLayer
34from lp.testing.views import create_initialized_view
3528
3629
37def find_spph_copy(archive, spph):30def find_spph_copy(archive, spph):
@@ -41,40 +34,6 @@
41 name=spr.sourcepackagename.name, version=spr.version).one()34 name=spr.sourcepackagename.name, version=spr.version).one()
4235
4336
44class FakeDistribution:
45 def __init__(self):
46 self.archive = FakeArchive()
47
48
49class FakeDistroSeries:
50 def __init__(self):
51 self.distribution = FakeDistribution()
52
53
54class FakeSPN:
55 name = "spn-name"
56
57
58class FakeSPR:
59 def __init__(self):
60 self.sourcepackagename = FakeSPN()
61 self.version = "1.0"
62
63
64class FakeArchive:
65 def __init__(self, displayname="archive-name"):
66 self.displayname = displayname
67
68
69class FakeSPPH:
70 def __init__(self, archive=None):
71 if archive is None:
72 archive = FakeArchive()
73 self.sourcepackagerelease = FakeSPR()
74 self.displayname = "spph-displayname"
75 self.archive = archive
76
77
78class TestPackageCopyingMixinLight(TestCase):37class TestPackageCopyingMixinLight(TestCase):
79 """Test lightweight functions and methods.38 """Test lightweight functions and methods.
8039
@@ -83,25 +42,11 @@
8342
84 unique_number = 143 unique_number = 1
8544
86 def getPocket(self):
87 """Return an arbitrary `PackagePublishingPocket`."""
88 return PackagePublishingPocket.SECURITY
89
90 def getUniqueString(self):45 def getUniqueString(self):
91 """Return an arbitrary string."""46 """Return an arbitrary string."""
92 self.unique_number += 147 self.unique_number += 1
93 return "string_%d_" % self.unique_number48 return "string_%d_" % self.unique_number
9449
95 def test_canCopySynchronously_allows_small_synchronous_copies(self):
96 # Small numbers of packages can be copied synchronously.
97 packages = [self.getUniqueString() for counter in range(3)]
98 self.assertTrue(PackageCopyingMixin().canCopySynchronously(packages))
99
100 def test_canCopySynchronously_disallows_large_synchronous_copies(self):
101 # Large numbers of packages must be copied asynchronously.
102 packages = [self.getUniqueString() for counter in range(300)]
103 self.assertFalse(PackageCopyingMixin().canCopySynchronously(packages))
104
105 def test_render_cannotcopy_as_html_lists_errors(self):50 def test_render_cannotcopy_as_html_lists_errors(self):
106 # render_cannotcopy_as_html includes a CannotCopy error message51 # render_cannotcopy_as_html includes a CannotCopy error message
107 # into its HTML notice.52 # into its HTML notice.
@@ -116,24 +61,6 @@
116 self.assertNotIn(message, html_text)61 self.assertNotIn(message, html_text)
117 self.assertIn("x&lt;&gt;y", html_text)62 self.assertIn("x&lt;&gt;y", html_text)
11863
119 def test_compose_synchronous_copy_feedback_escapes_archive_name(self):
120 # compose_synchronous_copy_feedback escapes archive displaynames.
121 archive = FakeArchive(displayname="a&b")
122 notice = compose_synchronous_copy_feedback(
123 ["hi"], archive, dest_url="/")
124 html_text = notice.escapedtext
125 self.assertNotIn("a&b", html_text)
126 self.assertIn("a&amp;b", html_text)
127
128 def test_compose_synchronous_copy_feedback_escapes_package_names(self):
129 # compose_synchronous_copy_feedback escapes package names.
130 archive = FakeArchive()
131 notice = compose_synchronous_copy_feedback(
132 ["x<y"], archive, dest_url="/")
133 html_text = notice.escapedtext
134 self.assertNotIn("x<y", html_text)
135 self.assertIn("x&lt;y", html_text)
136
13764
138class TestPackageCopyingMixinIntegration(TestCaseWithFactory):65class TestPackageCopyingMixinIntegration(TestCaseWithFactory):
139 """Integration tests for `PackageCopyingMixin`."""66 """Integration tests for `PackageCopyingMixin`."""
@@ -180,36 +107,12 @@
180 derived_series, SourcePackageFormat.FORMAT_1_0)107 derived_series, SourcePackageFormat.FORMAT_1_0)
181 return derived_series108 return derived_series
182109
183 def makeView(self):
184 """Create a `PackageCopyingMixin`-based view."""
185 return create_initialized_view(
186 self.makeDerivedSeries(), "+localpackagediffs")
187
188 def getUploader(self, archive, spn):110 def getUploader(self, archive, spn):
189 """Get person with upload rights for the given package and archive."""111 """Get person with upload rights for the given package and archive."""
190 uploader = archive.owner112 uploader = archive.owner
191 removeSecurityProxy(archive).newPackageUploader(uploader, spn)113 removeSecurityProxy(archive).newPackageUploader(uploader, spn)
192 return uploader114 return uploader
193115
194 def test_canCopySynchronously_obeys_feature_flag(self):
195 packages = [self.getUniqueString() for counter in range(3)]
196 mixin = PackageCopyingMixin()
197 with FeatureFixture({FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS: 2}):
198 can_copy_synchronously = mixin.canCopySynchronously(packages)
199 self.assertFalse(can_copy_synchronously)
200
201 def test_copy_synchronously_copies_packages(self):
202 # copy_synchronously copies packages into the destination
203 # archive.
204 spph = self.makeSPPH()
205 dest_series = self.makeDerivedSeries()
206 archive = dest_series.distribution.main_archive
207 pocket = self.factory.getAnyPocket()
208 copy_synchronously(
209 [spph], archive, dest_series, pocket, include_binaries=False,
210 check_permissions=False)
211 self.assertNotEqual(None, find_spph_copy(archive, spph))
212
213 def test_copy_asynchronously_does_not_copy_packages(self):116 def test_copy_asynchronously_does_not_copy_packages(self):
214 # copy_asynchronously does not copy packages into the destination117 # copy_asynchronously does not copy packages into the destination
215 # archive; that happens later, asynchronously.118 # archive; that happens later, asynchronously.
@@ -222,19 +125,6 @@
222 check_permissions=False, person=self.factory.makePerson())125 check_permissions=False, person=self.factory.makePerson())
223 self.assertEqual(None, find_spph_copy(archive, spph))126 self.assertEqual(None, find_spph_copy(archive, spph))
224127
225 def test_copy_synchronously_lists_packages(self):
226 # copy_synchronously returns feedback that includes the names of
227 # packages it copied.
228 spph = self.makeSPPH()
229 dest_series = self.makeDerivedSeries()
230 pocket = self.factory.getAnyPocket()
231 notice = copy_synchronously(
232 [spph], dest_series.distribution.main_archive, dest_series,
233 pocket, include_binaries=False,
234 check_permissions=False).escapedtext
235 self.assertIn(
236 spph.sourcepackagerelease.sourcepackagename.name, notice)
237
238 def test_copy_asynchronously_creates_copy_jobs(self):128 def test_copy_asynchronously_creates_copy_jobs(self):
239 # copy_asynchronously creates PackageCopyJobs.129 # copy_asynchronously creates PackageCopyJobs.
240 spph = self.makeSPPH()130 spph = self.makeSPPH()
@@ -281,46 +171,6 @@
281 [("one", spph_one.distroseries), ("two", spph_two.distroseries)],171 [("one", spph_one.distroseries), ("two", spph_two.distroseries)],
282 [(job.package_name, job.target_distroseries) for job in jobs])172 [(job.package_name, job.target_distroseries) for job in jobs])
283173
284 def test_do_copy_goes_async_if_canCopySynchronously_says_so(self):
285 # The view opts for asynchronous copying if canCopySynchronously
286 # returns False. This creates PackageCopyJobs.
287 spph = self.makeSPPH()
288 pocket = self.factory.getAnyPocket()
289 view = self.makeView()
290 dest_series = view.context
291 archive = dest_series.distribution.main_archive
292 view.canCopySynchronously = FakeMethod(result=False)
293 view.do_copy(
294 'selected_differences', [spph], archive, dest_series, pocket,
295 False, check_permissions=False, person=self.factory.makePerson())
296 jobs = list(getUtility(IPlainPackageCopyJobSource).getActiveJobs(
297 archive))
298 self.assertNotEqual([], jobs)
299
300 def test_copy_synchronously_may_allow_copy(self):
301 # In a normal working situation, copy_synchronously allows a
302 # copy.
303 spph = self.makeSPPH()
304 pocket = PackagePublishingPocket.RELEASE
305 dest_series = self.makeDerivedSeries()
306 dest_archive = dest_series.main_archive
307 spn = spph.sourcepackagerelease.sourcepackagename
308 notification = copy_synchronously(
309 [spph], dest_archive, dest_series, pocket, False,
310 person=self.getUploader(dest_archive, spn))
311 self.assertIn("Packages copied", notification.escapedtext)
312
313 def test_copy_synchronously_checks_permissions(self):
314 # Unless told not to, copy_synchronously does a permissions
315 # check.
316 spph = self.makeSPPH()
317 pocket = self.factory.getAnyPocket()
318 dest_series = self.makeDistroSeries()
319 self.assertRaises(
320 CannotCopy,
321 copy_synchronously,
322 [spph], dest_series.main_archive, dest_series, pocket, False)
323
324 def test_copy_asynchronously_may_allow_copy(self):174 def test_copy_asynchronously_may_allow_copy(self):
325 # In a normal working situation, copy_asynchronously allows a175 # In a normal working situation, copy_asynchronously allows a
326 # copy.176 # copy.
327177
=== modified file 'lib/lp/soyuz/model/packagecopyjob.py'
--- lib/lp/soyuz/model/packagecopyjob.py 2012-10-25 11:02:37 +0000
+++ lib/lp/soyuz/model/packagecopyjob.py 2012-11-13 09:52:27 +0000
@@ -187,6 +187,7 @@
187187
188 def __init__(self, job):188 def __init__(self, job):
189 self.context = job189 self.context = job
190 self.logger = logging.getLogger()
190191
191 @classmethod192 @classmethod
192 def get(cls, job_id):193 def get(cls, job_id):
@@ -574,8 +575,7 @@
574 # Remember the target archive purpose, as otherwise aborting the575 # Remember the target archive purpose, as otherwise aborting the
575 # transaction will forget it.576 # transaction will forget it.
576 target_archive_purpose = self.target_archive.purpose577 target_archive_purpose = self.target_archive.purpose
577 logger = logging.getLogger()578 self.logger.info("Job:\n%s\nraised CannotCopy:\n%s" % (self, e))
578 logger.info("Job:\n%s\nraised CannotCopy:\n%s" % (self, e))
579 self.abort() # Abort the txn.579 self.abort() # Abort the txn.
580 self.reportFailure(unicode(e))580 self.reportFailure(unicode(e))
581581
@@ -671,6 +671,12 @@
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.
672 pu.setDone()672 pu.setDone()
673673
674 if copied_publications:
675 self.logger.debug(
676 "Packages copied to %s:" % self.target_archive.displayname)
677 for copy in copied_publications:
678 self.logger.debug(copy.displayname)
679
674 def abort(self):680 def abort(self):
675 """Abort work."""681 """Abort work."""
676 transaction.abort()682 transaction.abort()
677683
=== modified file 'lib/lp/soyuz/stories/ppa/xx-copy-packages.txt'
--- lib/lp/soyuz/stories/ppa/xx-copy-packages.txt 2012-09-27 02:53:00 +0000
+++ lib/lp/soyuz/stories/ppa/xx-copy-packages.txt 2012-11-13 09:52:27 +0000
@@ -137,6 +137,28 @@
137 >>> flush_database_updates()137 >>> flush_database_updates()
138 >>> logout()138 >>> logout()
139139
140Copying packages will create jobs. Define a simple doctest-friendly runner.
141
142 >>> from zope.security.proxy import removeSecurityProxy
143 >>> from lp.services.log.logger import FakeLogger
144 >>> from lp.soyuz.interfaces.packagecopyjob import (
145 ... IPlainPackageCopyJobSource,
146 ... )
147
148 >>> def run_copy_jobs():
149 ... login('foo.bar@canonical.com')
150 ... source = getUtility(IPlainPackageCopyJobSource)
151 ... for job in removeSecurityProxy(source).iterReady():
152 ... job.logger = FakeLogger()
153 ... job.start(manage_transaction=True)
154 ... try:
155 ... job.run()
156 ... except Exception:
157 ... job.fail(manage_transaction=True)
158 ... else:
159 ... job.complete(manage_transaction=True)
160 ... logout()
161
140Let's say James wants to rebuild the Celso's 'pmount' source in his PPA.162Let's say James wants to rebuild the Celso's 'pmount' source in his PPA.
141163
142He is a little confused by the number of packages presented by164He is a little confused by the number of packages presented by
@@ -235,8 +257,11 @@
235 >>> messages = get_feedback_messages(jblack_browser.contents)257 >>> messages = get_feedback_messages(jblack_browser.contents)
236 >>> for msg in messages:258 >>> for msg in messages:
237 ... print msg259 ... print msg
238 Packages copied to PPA for James Blackwell:260 Requested sync of 1 package to PPA for James Blackwell.
239 pmount 0.1-1 in hoary261 Please allow some time for this to be processed.
262 >>> run_copy_jobs()
263 DEBUG Packages copied to PPA for James Blackwell:
264 DEBUG pmount 0.1-1 in hoary
240265
241James uses the link in the copy summary to go straight to the target266James uses the link in the copy summary to go straight to the target
242PPA, his own. There he can see the just copied package as PENDING and267PPA, his own. There he can see the just copied package as PENDING and
@@ -377,9 +402,10 @@
377 >>> messages = get_feedback_messages(jblack_browser.contents)402 >>> messages = get_feedback_messages(jblack_browser.contents)
378 >>> for msg in messages:403 >>> for msg in messages:
379 ... print msg404 ... print msg
380 There is 1 error.405 Requested sync of 1 package to PPA for James Blackwell.
381 The following source cannot be copied:406 Please allow some time for this to be processed.
382 pmount 0.1-1 in hoary407 >>> run_copy_jobs()
408 INFO ... raised CannotCopy: pmount 0.1-1 in hoary
383 (same version already building in the destination archive for Hoary)409 (same version already building in the destination archive for Hoary)
384410
385Now, knowing that pmount can only be copied within the same PPA if the411Now, knowing that pmount can only be copied within the same PPA if the
@@ -398,8 +424,10 @@
398 >>> messages = get_feedback_messages(jblack_browser.contents)424 >>> messages = get_feedback_messages(jblack_browser.contents)
399 >>> for msg in messages:425 >>> for msg in messages:
400 ... print msg426 ... print msg
401 There is 1 error.427 Requested sync of 1 package to PPA for James Blackwell.
402 The following source cannot be copied:428 Please allow some time for this to be processed.
429 >>> run_copy_jobs()
430 INFO ... raised CannotCopy:
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)
404432
405We will mark the pmount build completed, to emulate the situation433We will mark the pmount build completed, to emulate the situation
@@ -433,9 +461,10 @@
433 >>> messages = get_feedback_messages(jblack_browser.contents)461 >>> messages = get_feedback_messages(jblack_browser.contents)
434 >>> for msg in messages:462 >>> for msg in messages:
435 ... print msg463 ... print msg
436 There is 1 error.464 Requested sync of 1 package to PPA for James Blackwell.
437 The following source cannot be copied:465 Please allow some time for this to be processed.
438 pmount 0.1-1 in hoary466 >>> run_copy_jobs()
467 INFO ... raised CannotCopy: pmount 0.1-1 in hoary
439 (same version has unpublished binaries in the destination468 (same version has unpublished binaries in the destination
440 archive for Hoary, please wait for them to be published before469 archive for Hoary, please wait for them to be published before
441 copying)470 copying)
@@ -453,8 +482,10 @@
453 >>> messages = get_feedback_messages(jblack_browser.contents)482 >>> messages = get_feedback_messages(jblack_browser.contents)
454 >>> for msg in messages:483 >>> for msg in messages:
455 ... print msg484 ... print msg
456 There is 1 error.485 Requested sync of 1 package to PPA for James Blackwell.
457 The following source cannot be copied:486 Please allow some time for this to be processed.
487 >>> run_copy_jobs()
488 INFO ... raised CannotCopy:
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)
459490
460We will build and publish the architecture independent binary for491We will build and publish the architecture independent binary for
@@ -491,9 +522,13 @@
491 >>> messages = get_feedback_messages(jblack_browser.contents)522 >>> messages = get_feedback_messages(jblack_browser.contents)
492 >>> for msg in messages:523 >>> for msg in messages:
493 ... print msg524 ... print msg
494 Packages copied to PPA for James Blackwell:525 Requested sync of 1 package to PPA for James Blackwell.
495 pmount 0.1-1 in grumpy526 Please allow some time for this to be processed.
496 pmount-bin 0.1-1 in grumpy i386527 >>> run_copy_jobs()
528 DEBUG Packages copied to PPA for James Blackwell:
529 DEBUG pmount 0.1-1 in grumpy
530 DEBUG pmount-bin 0.1-1 in grumpy i386
531 >>> jblack_browser.open(jblack_browser.url)
497532
498Note that only the i386 binary got copied to grumpy since it lacks533Note that only the i386 binary got copied to grumpy since it lacks
499hppa support.534hppa support.
@@ -519,9 +554,8 @@
519 pmount - 0.1-1 Pending Grumpy Editors554 pmount - 0.1-1 Pending Grumpy Editors
520 pmount - 0.1-1 (Newer...) Pending Hoary Editors555 pmount - 0.1-1 (Newer...) Pending Hoary Editors
521556
522If James performs exactly the same copy procedure again, a message557If James performs exactly the same copy procedure again, no more packages
523stating that all packages involved were already copied to the558will be copied.
524selected destination PPA will be rendered.
525559
526 >>> jblack_browser.getControl(560 >>> jblack_browser.getControl(
527 ... name='field.selected_sources').value = [pmount_pub_id]561 ... name='field.selected_sources').value = [pmount_pub_id]
@@ -533,7 +567,9 @@
533 >>> messages = get_feedback_messages(jblack_browser.contents)567 >>> messages = get_feedback_messages(jblack_browser.contents)
534 >>> for msg in messages:568 >>> for msg in messages:
535 ... print msg569 ... print msg
536 All packages already copied to PPA for James Blackwell.570 Requested sync of 1 package to PPA for James Blackwell.
571 Please allow some time for this to be processed.
572 >>> run_copy_jobs()
537573
538After some time, James realises that pmount in hoary doesn't make much574After some time, James realises that pmount in hoary doesn't make much
539sense and simply deletes it, so his users won't be bothered by this575sense and simply deletes it, so his users won't be bothered by this
@@ -603,9 +639,10 @@
603 >>> messages = get_feedback_messages(jblack_browser.contents)639 >>> messages = get_feedback_messages(jblack_browser.contents)
604 >>> for msg in messages:640 >>> for msg in messages:
605 ... print msg641 ... print msg
606 There is 1 error.642 Requested sync of 1 package to PPA for James Blackwell.
607 The following source cannot be copied:643 Please allow some time for this to be processed.
608 pmount 0.1-1 in hoary644 >>> run_copy_jobs()
645 INFO ... raised CannotCopy: pmount 0.1-1 in hoary
609 (same version already has published binaries in the destination646 (same version already has published binaries in the destination
610 archive)647 archive)
611648
@@ -624,13 +661,16 @@
624 >>> messages = get_feedback_messages(jblack_browser.contents)661 >>> messages = get_feedback_messages(jblack_browser.contents)
625 >>> for msg in messages:662 >>> for msg in messages:
626 ... print msg663 ... print msg
627 Packages copied to PPA for James Blackwell:664 Requested sync of 1 package to PPA for James Blackwell.
628 pmount 0.1-1 in warty665 Please allow some time for this to be processed.
629 pmount-bin 0.1-1 in warty hppa666 >>> run_copy_jobs()
630 pmount-bin 0.1-1 in warty i386667 DEBUG Packages copied to PPA for James Blackwell:
668 DEBUG pmount 0.1-1 in warty
669 DEBUG pmount-bin 0.1-1 in warty hppa
670 DEBUG pmount-bin 0.1-1 in warty i386
671 >>> jblack_browser.open(jblack_browser.url)
631672
632James sees the just-copied 'pmount' source in warty pending673James sees the just-copied 'pmount' source in warty pending publication.
633publication.
634674
635 >>> print_ppa_packages(jblack_browser.contents)675 >>> print_ppa_packages(jblack_browser.contents)
636 Source Published Status Series Section Build676 Source Published Status Series Section Build
@@ -717,12 +757,16 @@
717 >>> messages = get_feedback_messages(jblack_browser.contents)757 >>> messages = get_feedback_messages(jblack_browser.contents)
718 >>> for msg in messages:758 >>> for msg in messages:
719 ... print msg759 ... print msg
720 Packages copied to PPA for James Blackwell Friends:760 Requested sync of 2 packages to PPA for James Blackwell Friends.
721 iceweasel 1.0 in hoary761 Please allow some time for these to be processed.
722 mozilla-firefox 1.0 in hoary i386762 >>> run_copy_jobs()
723 pmount 0.1-1 in hoary763 DEBUG Packages copied to PPA for James Blackwell Friends:
724 pmount 0.1-1 in hoary hppa764 DEBUG iceweasel 1.0 in hoary
725 pmount 0.1-1 in hoary i386765 DEBUG mozilla-firefox 1.0 in hoary i386
766 DEBUG Packages copied to PPA for James Blackwell Friends:
767 DEBUG pmount 0.1-1 in hoary
768 DEBUG pmount 0.1-1 in hoary hppa
769 DEBUG pmount 0.1-1 in hoary i386
726770
727So happy-hacking for James Friends, Celso's 'iceweasel' and 'pmount'771So happy-hacking for James Friends, Celso's 'iceweasel' and 'pmount'
728sources and binaries are copied to their PPA.772sources and binaries are copied to their PPA.
@@ -780,11 +824,12 @@
780 >>> messages = get_feedback_messages(jblack_browser.contents)824 >>> messages = get_feedback_messages(jblack_browser.contents)
781 >>> for msg in messages:825 >>> for msg in messages:
782 ... print msg826 ... print msg
783 There is 1 error.827 Requested sync of 2 packages to PPA for James Blackwell Friends.
784 The following sources cannot be copied:828 Please allow some time for these to be processed.
785 iceweasel 1.0 in hoary829 >>> run_copy_jobs()
830 INFO ... raised CannotCopy: iceweasel 1.0 in hoary
786 (same version already has published binaries in the destination archive)831 (same version already has published binaries in the destination archive)
787 pmount 0.1-1 in hoary832 INFO ... raised CannotCopy: pmount 0.1-1 in hoary
788 (same version already has published binaries in the destination archive)833 (same version already has published binaries in the destination archive)
789834
790James goes wild and decided to create a new team PPA for his sandbox835James goes wild and decided to create a new team PPA for his sandbox
@@ -850,12 +895,15 @@
850 >>> messages = get_feedback_messages(jblack_browser.contents)895 >>> messages = get_feedback_messages(jblack_browser.contents)
851 >>> for msg in messages:896 >>> for msg in messages:
852 ... print msg897 ... print msg
853 There is 1 error.898 Requested sync of 3 packages to PPA for James Blackwell Sandbox.
854 The following sources cannot be copied:899 Please allow some time for these to be processed.
855 pmount 0.1-1 in grumpy900 >>> run_copy_jobs()
856 (same version already building in the destination archive for Warty)901 DEBUG Packages copied to PPA for James Blackwell Sandbox:
857 pmount 0.1-1 in hoary902 DEBUG pmount 0.1-1 in warty
858 (same version already building in the destination archive for Warty)903 INFO ... raised CannotCopy: pmount 0.1-1 in grumpy
904 (same version already building in the destination archive for Warty)
905 INFO ... raised CannotCopy: pmount 0.1-1 in hoary
906 (same version already building in the destination archive for Warty)
859907
860Due to the copy error, nothing was copied to the destination PPA, not908Due to the copy error, nothing was copied to the destination PPA, not
861even the 'warty' source, which was not denied.909even the 'warty' source, which was not denied.
@@ -901,10 +949,14 @@
901 >>> messages = get_feedback_messages(jblack_browser.contents)949 >>> messages = get_feedback_messages(jblack_browser.contents)
902 >>> for msg in messages:950 >>> for msg in messages:
903 ... print msg951 ... print msg
904 Packages copied to PPA for James Blackwell:952 Requested sync of 1 package to PPA for James Blackwell.
905 pmount 0.1-1 in hoary953 Please allow some time for this to be processed.
906 pmount-bin 0.1-1 in hoary hppa954 >>> run_copy_jobs()
907 pmount-bin 0.1-1 in hoary i386955 DEBUG Packages copied to PPA for James Blackwell:
956 DEBUG pmount 0.1-1 in hoary
957 DEBUG pmount-bin 0.1-1 in hoary hppa
958 DEBUG pmount-bin 0.1-1 in hoary i386
959 >>> jblack_browser.open(jblack_browser.url)
908960
909 >>> print_ppa_packages(jblack_browser.contents)961 >>> print_ppa_packages(jblack_browser.contents)
910 Source Published Status Series Section Build962 Source Published Status Series Section Build
@@ -983,10 +1035,13 @@
983 >>> messages = get_feedback_messages(jblack_browser.contents)1035 >>> messages = get_feedback_messages(jblack_browser.contents)
984 >>> for msg in messages:1036 >>> for msg in messages:
985 ... print msg1037 ... print msg
986 Packages copied to PPA for James Blackwell:1038 Requested sync of 1 package to PPA for James Blackwell.
987 foo 2.0 in hoary1039 Please allow some time for this to be processed.
988 foo-bin 2.0 in hoary hppa1040 >>> run_copy_jobs()
989 foo-bin 2.0 in hoary i3861041 DEBUG Packages copied to PPA for James Blackwell:
1042 DEBUG foo 2.0 in hoary
1043 DEBUG foo-bin 2.0 in hoary hppa
1044 DEBUG foo-bin 2.0 in hoary i386
9901045
991James tries to copy some of Celso's packages that are older than1046James tries to copy some of Celso's packages that are older than
992the ones in his own PPA. He is not allowed to copy these older1047the ones in his own PPA. He is not allowed to copy these older
@@ -1014,9 +1069,10 @@
1014 >>> messages = get_feedback_messages(jblack_browser.contents)1069 >>> messages = get_feedback_messages(jblack_browser.contents)
1015 >>> for msg in messages:1070 >>> for msg in messages:
1016 ... print msg1071 ... print msg
1017 There is 1 error.1072 Requested sync of 1 package to PPA for James Blackwell.
1018 The following source cannot be copied:1073 Please allow some time for this to be processed.
1019 foo 1.1 in hoary1074 >>> run_copy_jobs()
1075 INFO ... raised CannotCopy: foo 1.1 in hoary
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)
10211077
1022However if he copies it to another suite is just works (tm) since PPAs1078However if he copies it to another suite is just works (tm) since PPAs
@@ -1032,10 +1088,13 @@
1032 >>> messages = get_feedback_messages(jblack_browser.contents)1088 >>> messages = get_feedback_messages(jblack_browser.contents)
1033 >>> for msg in messages:1089 >>> for msg in messages:
1034 ... print msg1090 ... print msg
1035 Packages copied to PPA for James Blackwell:1091 Requested sync of 1 package to PPA for James Blackwell.
1036 foo 1.1 in warty1092 Please allow some time for this to be processed.
1037 foo-bin 1.1 in warty hppa1093 >>> run_copy_jobs()
1038 foo-bin 1.1 in warty i3861094 DEBUG Packages copied to PPA for James Blackwell:
1095 DEBUG foo 1.1 in warty
1096 DEBUG foo-bin 1.1 in warty hppa
1097 DEBUG foo-bin 1.1 in warty i386
10391098
1040 >>> jblack_browser.open(1099 >>> jblack_browser.open(
1041 ... 'http://launchpad.dev/~jblack/+archive/ppa/+packages')1100 ... 'http://launchpad.dev/~jblack/+archive/ppa/+packages')
@@ -1073,9 +1132,10 @@
1073 >>> messages = get_feedback_messages(jblack_browser.contents)1132 >>> messages = get_feedback_messages(jblack_browser.contents)
1074 >>> for msg in messages:1133 >>> for msg in messages:
1075 ... print msg1134 ... print msg
1076 There is 1 error.1135 Requested sync of 1 package to PPA for James Blackwell.
1077 The following source cannot be copied:1136 Please allow some time for this to be processed.
1078 foo 1.1 in hoary1137 >>> run_copy_jobs()
1138 INFO ... raised CannotCopy: foo 1.1 in hoary
1079 (a different source with the same version is published in the1139 (a different source with the same version is published in the
1080 destination archive)1140 destination archive)
10811141
@@ -1108,8 +1168,10 @@
1108 >>> messages = get_feedback_messages(jblack_browser.contents)1168 >>> messages = get_feedback_messages(jblack_browser.contents)
1109 >>> for msg in messages:1169 >>> for msg in messages:
1110 ... print msg1170 ... print msg
1111 There is 1 error.1171 Requested sync of 1 package to PPA for James Blackwell.
1112 The following source cannot be copied:1172 Please allow some time for this to be processed.
1173 >>> run_copy_jobs()
1174 INFO ... raised CannotCopy:
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)
11141176
1115No game, no matter what he tries, James can't break PPAs.1177No game, no matter what he tries, James can't break PPAs.