← Back to team overview

launchpad-reviewers team mailing list archive

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

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/always-copy-packages-asynchronously into lp:launchpad.

Commit message:
Remove the old synchronous package copy path in favour of PackageCopyJobs.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #575450 in Launchpad itself: "Archive:+copy-packages nearly unusable due to timeouts"
  https://bugs.launchpad.net/launchpad/+bug/575450

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/always-copy-packages-asynchronously/+merge/131837

== Summary ==

The synchronous package copy path has always been full of timeouts, and is now completely obsoleted by the asynchronous path.  It has been feature-flagged off for all users for a couple of weeks, and the initial Ubuntu auto-sync was uneventful in this regard.  We can and should now remove the old code.

== Proposed fix ==

Remove the dead code.

== Tests ==

bin/test -vvct lib/lp/soyuz/browser/tests/archive-views.txt -t lp.soyuz.browser.tests.test_package_copying_mixin

== Demo and Q/A ==

There should be no user-visible effects since this just amounts to removing code that has been disabled by a feature flag, but we should verify that copies between PPAs using the web UI still work.
-- 
https://code.launchpad.net/~cjwatson/launchpad/always-copy-packages-asynchronously/+merge/131837
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/always-copy-packages-asynchronously into lp:launchpad.
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py	2012-10-25 14:18:36 +0000
+++ lib/lp/services/features/flags.py	2012-10-29 09:40:33 +0000
@@ -154,12 +154,6 @@
      '',
      '',
      ''),
-    ('soyuz.derived_series.max_synchronous_syncs',
-     'int',
-     "How many package syncs may be done directly in a web request.",
-     '100',
-     '',
-     ''),
     ('soyuz.derived_series_sync.enabled',
      'boolean',
      'Enables syncing of packages on derivative distributions pages.',

=== modified file 'lib/lp/soyuz/browser/archive.py'
--- lib/lp/soyuz/browser/archive.py	2012-09-21 06:52:06 +0000
+++ lib/lp/soyuz/browser/archive.py	2012-10-29 09:40:33 +0000
@@ -29,7 +29,6 @@
     ]
 
 
-from cgi import escape
 from datetime import (
     datetime,
     timedelta,
@@ -92,11 +91,7 @@
     get_plural_text,
     get_user_agent_distroseries,
     )
-from lp.services.database.bulk import (
-    load,
-    load_related,
-    )
-from lp.services.features import getFeatureFlag
+from lp.services.database.bulk import load_related
 from lp.services.helpers import english_list
 from lp.services.job.model.job import Job
 from lp.services.librarian.browser import FileNavigationMixin
@@ -166,21 +161,8 @@
     Archive,
     validate_ppa,
     )
-from lp.soyuz.model.binarypackagename import BinaryPackageName
-from lp.soyuz.model.publishing import (
-    BinaryPackagePublishingHistory,
-    SourcePackagePublishingHistory,
-    )
-from lp.soyuz.scripts.packagecopier import (
-    check_copy_permissions,
-    do_copy,
-    )
-
-# Feature flag: up to how many package sync requests (inclusive) are to be
-# processed synchronously within the web request?
-# Set to -1 to disable synchronous syncs.
-FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS = (
-    'soyuz.derived_series.max_synchronous_syncs')
+from lp.soyuz.model.publishing import SourcePackagePublishingHistory
+from lp.soyuz.scripts.packagecopier import check_copy_permissions
 
 
 class ArchiveBadges(HasBadgeBase):
@@ -1256,63 +1238,6 @@
     _messageNoValue = _("vocabulary-copy-to-same-series", "The same series")
 
 
-def preload_binary_package_names(copies):
-    """Preload `BinaryPackageName`s to speed up display-name construction."""
-    bpn_ids = [
-        copy.binarypackagerelease.binarypackagenameID for copy in copies
-        if isinstance(copy, BinaryPackagePublishingHistory)]
-    load(BinaryPackageName, bpn_ids)
-
-
-def compose_synchronous_copy_feedback(copies, dest_archive, dest_url=None,
-                                      dest_display_name=None):
-    """Compose human-readable feedback after a synchronous copy."""
-    if dest_url is None:
-        dest_url = escape(
-            canonical_url(dest_archive) + '/+packages', quote=True)
-
-    if dest_display_name is None:
-        dest_display_name = escape(dest_archive.displayname)
-
-    if len(copies) == 0:
-        return structured(
-            '<p>All packages already copied to <a href="%s">%s</a>.</p>'
-            % (dest_url, dest_display_name))
-    else:
-        messages = []
-        messages.append(
-            '<p>Packages copied to <a href="%s">%s</a>:</p>'
-            % (dest_url, dest_display_name))
-        messages.append('<ul>')
-        messages.append("\n".join([
-            '<li>%s</li>' % escape(copy) for copy in copies]))
-        messages.append('</ul>')
-        return structured("\n".join(messages))
-
-
-def copy_synchronously(source_pubs, dest_archive, dest_series, dest_pocket,
-                       include_binaries, dest_url=None,
-                       dest_display_name=None, person=None,
-                       check_permissions=True):
-    """Copy packages right now.
-
-    :return: A `structured` with human-readable feedback about the
-        operation.
-    :raises CannotCopy: If `check_permissions` is True and the copy is
-        not permitted.
-    """
-    copies = do_copy(
-        source_pubs, dest_archive, dest_series, dest_pocket, include_binaries,
-        allow_delayed_copies=True, person=person,
-        check_permissions=check_permissions)
-
-    preload_binary_package_names(copies)
-
-    return compose_synchronous_copy_feedback(
-        [copy.displayname for copy in copies], dest_archive, dest_url,
-        dest_display_name)
-
-
 def copy_asynchronously(source_pubs, dest_archive, dest_series, dest_pocket,
                         include_binaries, dest_url=None,
                         dest_display_name=None, person=None,
@@ -1385,28 +1310,14 @@
 class PackageCopyingMixin:
     """A mixin class that adds helpers for package copying."""
 
-    def canCopySynchronously(self, source_pubs):
-        """Can we afford to copy `source_pubs` synchronously?"""
-        # Fixed estimate: up to 100 packages can be copied in acceptable
-        # time.  Anything more than that and we go async.
-        limit = getFeatureFlag(FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS)
-        try:
-            limit = int(limit)
-        except:
-            limit = 100
-
-        return len(source_pubs) <= limit
-
     def do_copy(self, sources_field_name, source_pubs, dest_archive,
                 dest_series, dest_pocket, include_binaries,
                 dest_url=None, dest_display_name=None, person=None,
-                check_permissions=True, force_async=False,
-                sponsored_person=None):
+                check_permissions=True, sponsored_person=None):
         """Copy packages and add appropriate feedback to the browser page.
 
-        This may either copy synchronously, if there are few enough
-        requests to process right now; or asynchronously in which case
-        it will schedule jobs that will be processed by a script.
+        This will copy asynchronously, scheduling jobs that will be
+        processed by a script.
 
         :param sources_field_name: The name of the form field to set errors
             on when the copy fails
@@ -1425,30 +1336,18 @@
         :param person: The person requesting the copy.
         :param: check_permissions: boolean indicating whether or not the
             requester's permissions to copy should be checked.
-        :param force_async: Force the copy to create package copy jobs and
-            perform the copy asynchronously.
         :param sponsored_person: An IPerson representing the person being
-            sponsored (for asynchronous copies only).
+            sponsored.
 
         :return: True if the copying worked, False otherwise.
         """
-        assert force_async or not sponsored_person, (
-            "sponsored must be None for sync copies")
         try:
-            if (force_async == False and
-                    self.canCopySynchronously(source_pubs)):
-                notification = copy_synchronously(
-                    source_pubs, dest_archive, dest_series, dest_pocket,
-                    include_binaries, dest_url=dest_url,
-                    dest_display_name=dest_display_name, person=person,
-                    check_permissions=check_permissions)
-            else:
-                notification = copy_asynchronously(
-                    source_pubs, dest_archive, dest_series, dest_pocket,
-                    include_binaries, dest_url=dest_url,
-                    dest_display_name=dest_display_name, person=person,
-                    check_permissions=check_permissions,
-                    sponsored=sponsored_person)
+            notification = copy_asynchronously(
+                source_pubs, dest_archive, dest_series, dest_pocket,
+                include_binaries, dest_url=dest_url,
+                dest_display_name=dest_display_name, person=person,
+                check_permissions=check_permissions,
+                sponsored=sponsored_person)
         except CannotCopy as error:
             self.setFieldError(
                 sources_field_name, render_cannotcopy_as_html(error))

=== modified file 'lib/lp/soyuz/browser/tests/archive-views.txt'
--- lib/lp/soyuz/browser/tests/archive-views.txt	2012-09-13 04:11:09 +0000
+++ lib/lp/soyuz/browser/tests/archive-views.txt	2012-10-29 09:40:33 +0000
@@ -1342,9 +1342,8 @@
 Copy private files to public archives
 -------------------------------------
 
-Users are allowed to copy private sources into private PPAs, however
-it happens via 'delayed-copies' not the usual direct copying method.
-See more information in scripts/packagecopier.py
+Users are allowed to copy private sources into public PPAs.
+See more information in scripts/packagecopier.py.
 
 First we will enable Celso's private PPA.
 
@@ -1386,60 +1385,12 @@
     >>> len(view.errors)
     0
 
-The action is performed as a delayed-copy, and the user is informed of
+The action is performed as an asynchronous copy, and the user is informed of
 it via a page notification.
 
     >>> from lp.testing.pages import extract_text
     >>> for notification in view.request.response.notifications:
     ...     print extract_text(notification.message)
-    Packages copied to PPA for Ubuntu Team:
-    Delayed copy of foocomm - 2.0-1 (source)
-
-The delayed-copy request is waiting to be processed in the ACCEPTED
-upload queue.
-
-    >>> from lp.soyuz.interfaces.queue import IPackageUploadSet
-    >>> copy = getUtility(IPackageUploadSet).findSourceUpload(
-    ...     'foocomm', '2.0-1', ubuntu_team_ppa, ubuntu)
-
-    >>> print copy.status.name
-    ACCEPTED
-
-The action may also be performed asynchronously.
-
-    >>> from lp.services.features.testing import FeatureFixture
-    >>> from lp.soyuz.browser.archive import (
-    ...     FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS,
-    ...     )
-    >>> fixture = FeatureFixture({
-    ...     FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS: '0',
-    ...     })
-    >>> fixture.setUp()
-
-    >>> login('foo.bar@xxxxxxxxxxxxx')
-    >>> async_private_source = test_publisher.createSource(
-    ...     cprov_private_ppa, 'foocomm', '1.0-1', new_version='3.0-1')
-    >>> transaction.commit()
-
-    >>> print async_private_source.displayname
-    foocomm 3.0-1 in hoary
-
-    >>> login('celso.providelo@xxxxxxxxxxxxx')
-    >>> view = create_initialized_view(
-    ...     cprov_private_ppa, name="+copy-packages",
-    ...     form={
-    ...         'field.selected_sources': [str(async_private_source.id)],
-    ...         'field.destination_archive': 'ubuntu-team/ppa',
-    ...         'field.destination_series': '',
-    ...         'field.include_binaries': 'REBUILD_SOURCES',
-    ...         'field.actions.copy': 'Copy',
-    ...         })
-
-    >>> len(view.errors)
-    0
-
-    >>> for notification in view.request.response.notifications:
-    ...     print extract_text(notification.message)
     Requested sync of 1 package.
     Please allow some time for this to be processed.
 
@@ -1463,14 +1414,14 @@
     >>> [copied_source] = ubuntu_team_ppa.getPublishedSources(
     ...     name="foocomm", exact_match=True)
     >>> print copied_source.displayname
-    foocomm 3.0-1 in hoary
+    foocomm 2.0-1 in hoary
 
 If we run the same copy again, it will fail.
 
     >>> view = create_initialized_view(
     ...     cprov_private_ppa, name="+copy-packages",
     ...     form={
-    ...         'field.selected_sources': [str(async_private_source.id)],
+    ...         'field.selected_sources': [str(private_source.id)],
     ...         'field.destination_archive': 'ubuntu-team/ppa',
     ...         'field.destination_series': '',
     ...         'field.include_binaries': 'REBUILD_SOURCES',
@@ -1495,12 +1446,10 @@
     >>> for job in ubuntu_team_ppa_view.package_copy_jobs:
     ...     print job.status.title, job.package_name, job.package_version
     ...     print job.error_message
-    Failed foocomm 3.0-1
-    foocomm 3.0-1 in hoary (same version already building in the destination
+    Failed foocomm 2.0-1
+    foocomm 2.0-1 in hoary (same version already building in the destination
     archive for Hoary)
 
-    >>> fixture.cleanUp()
-
 
 External dependencies validation
 ================================

=== modified file 'lib/lp/soyuz/browser/tests/test_package_copying_mixin.py'
--- lib/lp/soyuz/browser/tests/test_package_copying_mixin.py	2012-06-28 17:50:41 +0000
+++ lib/lp/soyuz/browser/tests/test_package_copying_mixin.py	2012-10-29 09:40:33 +0000
@@ -1,4 +1,4 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2011-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for `PackageCopyingMixin`."""
@@ -9,14 +9,9 @@
 from zope.security.proxy import removeSecurityProxy
 
 from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.services.features.testing import FeatureFixture
 from lp.services.propertycache import cachedproperty
 from lp.soyuz.browser.archive import (
-    compose_synchronous_copy_feedback,
     copy_asynchronously,
-    copy_synchronously,
-    FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS,
-    PackageCopyingMixin,
     render_cannotcopy_as_html,
     )
 from lp.soyuz.enums import SourcePackageFormat
@@ -29,9 +24,7 @@
     TestCase,
     TestCaseWithFactory,
     )
-from lp.testing.fakemethod import FakeMethod
 from lp.testing.layers import LaunchpadFunctionalLayer
-from lp.testing.views import create_initialized_view
 
 
 def find_spph_copy(archive, spph):
@@ -41,40 +34,6 @@
         name=spr.sourcepackagename.name, version=spr.version).one()
 
 
-class FakeDistribution:
-    def __init__(self):
-        self.archive = FakeArchive()
-
-
-class FakeDistroSeries:
-    def __init__(self):
-        self.distribution = FakeDistribution()
-
-
-class FakeSPN:
-    name = "spn-name"
-
-
-class FakeSPR:
-    def __init__(self):
-        self.sourcepackagename = FakeSPN()
-        self.version = "1.0"
-
-
-class FakeArchive:
-    def __init__(self, displayname="archive-name"):
-        self.displayname = displayname
-
-
-class FakeSPPH:
-    def __init__(self, archive=None):
-        if archive is None:
-            archive = FakeArchive()
-        self.sourcepackagerelease = FakeSPR()
-        self.displayname = "spph-displayname"
-        self.archive = archive
-
-
 class TestPackageCopyingMixinLight(TestCase):
     """Test lightweight functions and methods.
 
@@ -83,25 +42,11 @@
 
     unique_number = 1
 
-    def getPocket(self):
-        """Return an arbitrary `PackagePublishingPocket`."""
-        return PackagePublishingPocket.SECURITY
-
     def getUniqueString(self):
         """Return an arbitrary string."""
         self.unique_number += 1
         return "string_%d_" % self.unique_number
 
-    def test_canCopySynchronously_allows_small_synchronous_copies(self):
-        # Small numbers of packages can be copied synchronously.
-        packages = [self.getUniqueString() for counter in range(3)]
-        self.assertTrue(PackageCopyingMixin().canCopySynchronously(packages))
-
-    def test_canCopySynchronously_disallows_large_synchronous_copies(self):
-        # Large numbers of packages must be copied asynchronously.
-        packages = [self.getUniqueString() for counter in range(300)]
-        self.assertFalse(PackageCopyingMixin().canCopySynchronously(packages))
-
     def test_render_cannotcopy_as_html_lists_errors(self):
         # render_cannotcopy_as_html includes a CannotCopy error message
         # into its HTML notice.
@@ -116,24 +61,6 @@
         self.assertNotIn(message, html_text)
         self.assertIn("x&lt;&gt;y", html_text)
 
-    def test_compose_synchronous_copy_feedback_escapes_archive_name(self):
-        # compose_synchronous_copy_feedback escapes archive displaynames.
-        archive = FakeArchive(displayname="a&b")
-        notice = compose_synchronous_copy_feedback(
-            ["hi"], archive, dest_url="/")
-        html_text = notice.escapedtext
-        self.assertNotIn("a&b", html_text)
-        self.assertIn("a&amp;b", html_text)
-
-    def test_compose_synchronous_copy_feedback_escapes_package_names(self):
-        # compose_synchronous_copy_feedback escapes package names.
-        archive = FakeArchive()
-        notice = compose_synchronous_copy_feedback(
-            ["x<y"], archive, dest_url="/")
-        html_text = notice.escapedtext
-        self.assertNotIn("x<y", html_text)
-        self.assertIn("x&lt;y", html_text)
-
 
 class TestPackageCopyingMixinIntegration(TestCaseWithFactory):
     """Integration tests for `PackageCopyingMixin`."""
@@ -180,36 +107,12 @@
             derived_series, SourcePackageFormat.FORMAT_1_0)
         return derived_series
 
-    def makeView(self):
-        """Create a `PackageCopyingMixin`-based view."""
-        return create_initialized_view(
-            self.makeDerivedSeries(), "+localpackagediffs")
-
     def getUploader(self, archive, spn):
         """Get person with upload rights for the given package and archive."""
         uploader = archive.owner
         removeSecurityProxy(archive).newPackageUploader(uploader, spn)
         return uploader
 
-    def test_canCopySynchronously_obeys_feature_flag(self):
-        packages = [self.getUniqueString() for counter in range(3)]
-        mixin = PackageCopyingMixin()
-        with FeatureFixture({FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS: 2}):
-            can_copy_synchronously = mixin.canCopySynchronously(packages)
-        self.assertFalse(can_copy_synchronously)
-
-    def test_copy_synchronously_copies_packages(self):
-        # copy_synchronously copies packages into the destination
-        # archive.
-        spph = self.makeSPPH()
-        dest_series = self.makeDerivedSeries()
-        archive = dest_series.distribution.main_archive
-        pocket = self.factory.getAnyPocket()
-        copy_synchronously(
-            [spph], archive, dest_series, pocket, include_binaries=False,
-            check_permissions=False)
-        self.assertNotEqual(None, find_spph_copy(archive, spph))
-
     def test_copy_asynchronously_does_not_copy_packages(self):
         # copy_asynchronously does not copy packages into the destination
         # archive; that happens later, asynchronously.
@@ -222,19 +125,6 @@
             check_permissions=False, person=self.factory.makePerson())
         self.assertEqual(None, find_spph_copy(archive, spph))
 
-    def test_copy_synchronously_lists_packages(self):
-        # copy_synchronously returns feedback that includes the names of
-        # packages it copied.
-        spph = self.makeSPPH()
-        dest_series = self.makeDerivedSeries()
-        pocket = self.factory.getAnyPocket()
-        notice = copy_synchronously(
-            [spph], dest_series.distribution.main_archive, dest_series,
-            pocket, include_binaries=False,
-            check_permissions=False).escapedtext
-        self.assertIn(
-            spph.sourcepackagerelease.sourcepackagename.name, notice)
-
     def test_copy_asynchronously_creates_copy_jobs(self):
         # copy_asynchronously creates PackageCopyJobs.
         spph = self.makeSPPH()
@@ -281,46 +171,6 @@
             [("one", spph_one.distroseries), ("two", spph_two.distroseries)],
             [(job.package_name, job.target_distroseries) for job in jobs])
 
-    def test_do_copy_goes_async_if_canCopySynchronously_says_so(self):
-        # The view opts for asynchronous copying if canCopySynchronously
-        # returns False.  This creates PackageCopyJobs.
-        spph = self.makeSPPH()
-        pocket = self.factory.getAnyPocket()
-        view = self.makeView()
-        dest_series = view.context
-        archive = dest_series.distribution.main_archive
-        view.canCopySynchronously = FakeMethod(result=False)
-        view.do_copy(
-            'selected_differences', [spph], archive, dest_series, pocket,
-            False, check_permissions=False, person=self.factory.makePerson())
-        jobs = list(getUtility(IPlainPackageCopyJobSource).getActiveJobs(
-            archive))
-        self.assertNotEqual([], jobs)
-
-    def test_copy_synchronously_may_allow_copy(self):
-        # In a normal working situation, copy_synchronously allows a
-        # copy.
-        spph = self.makeSPPH()
-        pocket = PackagePublishingPocket.RELEASE
-        dest_series = self.makeDerivedSeries()
-        dest_archive = dest_series.main_archive
-        spn = spph.sourcepackagerelease.sourcepackagename
-        notification = copy_synchronously(
-            [spph], dest_archive, dest_series, pocket, False,
-            person=self.getUploader(dest_archive, spn))
-        self.assertIn("Packages copied", notification.escapedtext)
-
-    def test_copy_synchronously_checks_permissions(self):
-        # Unless told not to, copy_synchronously does a permissions
-        # check.
-        spph = self.makeSPPH()
-        pocket = self.factory.getAnyPocket()
-        dest_series = self.makeDistroSeries()
-        self.assertRaises(
-            CannotCopy,
-            copy_synchronously,
-            [spph], dest_series.main_archive, dest_series, pocket, False)
-
     def test_copy_asynchronously_may_allow_copy(self):
         # In a normal working situation, copy_asynchronously allows a
         # copy.


Follow ups