← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/upgrade-keystoneclient-swiftclient into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/upgrade-keystoneclient-swiftclient into lp:launchpad.

Commit message:
Upgrade to python-keystoneclient 0.7.1 and python-swiftclient 2.0.3.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/upgrade-keystoneclient-swiftclient/+merge/335391

These were the latest versions in Icehouse, to go with keystone 2014.1.5 and
swift 1.13.1 currently on the server.  Upgrading to these allows us to use a
more recent version of pbr that is compatible with other dependencies we'd
like to upgrade.

We need a few tweaks to make everything work properly:

 * quieten overly-noisy logging (needed until python-swiftclient 3.2.0);
 * explicitly restrict HashStream to the desired segment size, since
   otherwise the client will read past the end of the segment and we can end
   up with corrupted hashes;
 * advertise a keystone endpoint in the fake Swift fixture;
 * cope with slightly different exception types due to the client now using
   requests.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/upgrade-keystoneclient-swiftclient into lp:launchpad.
=== modified file 'constraints.txt'
--- constraints.txt	2017-12-19 09:46:14 +0000
+++ constraints.txt	2017-12-19 17:02:21 +0000
@@ -228,6 +228,7 @@
 auditor==0.0.3
 auditorclient==0.0.4
 auditorfixture==0.0.7
+Babel==2.5.1
 backports.lzma==0.0.3
 BeautifulSoup==3.2.1
 billiard==3.3.0.20
@@ -255,7 +256,7 @@
 httplib2==0.8
 importlib==1.0.2
 ipython==0.13.2
-iso8601==0.1.4
+iso8601==0.1.12
 jsautobuild==0.2
 keyring==0.6.2
 kombu==3.0.30
@@ -282,6 +283,7 @@
 meliae==0.2.0.final.0
 mock==1.0.1
 mocker==1.1.1
+netaddr==0.7.19
 oauth==1.0
 oops==0.0.13
 oops-amqp==0.0.8b1
@@ -290,9 +292,9 @@
 oops-twisted==0.0.7
 oops-wsgi==0.0.8
 ordereddict==1.1
-oslo.config==1.1.1
+oslo.config==1.3.0
 paramiko==1.7.7.2
-pbr==0.5.20
+pbr==0.11.1
 pgbouncer==0.0.8
 prettytable==0.7.2
 psycopg2==2.6.1
@@ -305,14 +307,14 @@
 pystache==0.5.3
 python-dateutil==1.5
 python-debian==0.1.23
-python-keystoneclient==0.3.1
+python-keystoneclient==0.7.1
 python-memcached==1.58
 python-mimeparse==0.1.4
 # XXX: deryck 2012-08-10
 # See lp:~deryck/python-openid/python-openid-fix1034376 which
 # reapplied a patch from wgrant to get codehosting going again.
 python-openid==2.2.5-fix1034376
-python-swiftclient==1.5.0
+python-swiftclient==2.0.3
 PyYAML==3.10
 rabbitfixture==0.3.6
 requests==2.7.0

=== modified file 'lib/lp/services/librarianserver/librariangc.py'
--- lib/lp/services/librarianserver/librariangc.py	2017-04-02 01:17:50 +0000
+++ lib/lp/services/librarianserver/librariangc.py	2017-12-19 17:02:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Librarian garbage collection routines"""
@@ -55,7 +55,8 @@
         swift_connection = swift.connection_pool.get()
         container, name = swift.swift_location(content_id)
         try:
-            swift_connection.head_object(container, name)
+            with swift.disable_swiftclient_logging():
+                swift_connection.head_object(container, name)
             return True
         except swiftclient.ClientException as x:
             if x.http_status != 404:
@@ -79,8 +80,9 @@
         try:
             swift_connection = swift.connection_pool.get()
             container, name = swift.swift_location(content_id)
-            chunks = swift_connection.get_object(
-                container, name, resp_chunk_size=STREAM_CHUNK_SIZE)[1]
+            with swift.disable_swiftclient_logging():
+                chunks = swift_connection.get_object(
+                    container, name, resp_chunk_size=STREAM_CHUNK_SIZE)[1]
             return swift.SwiftStream(swift_connection, chunks)
         except swiftclient.ClientException as x:
             if x.http_status != 404:
@@ -562,7 +564,8 @@
                 container, name = swift.swift_location(content_id)
                 with swift.connection() as swift_connection:
                     try:
-                        swift_connection.delete_object(container, name)
+                        with swift.disable_swiftclient_logging():
+                            swift_connection.delete_object(container, name)
                         removed.append('Swift')
                     except swiftclient.ClientException as x:
                         if x.http_status != 404:
@@ -739,10 +742,11 @@
             container_num += 1
             container = swift.SWIFT_CONTAINER_PREFIX + str(container_num)
             try:
-                names = sorted(
-                    swift_connection.get_container(
-                        container, full_listing=True)[1],
-                    key=lambda x: map(int, x['name'].split('/')))
+                with swift.disable_swiftclient_logging():
+                    names = sorted(
+                        swift_connection.get_container(
+                            container, full_listing=True)[1],
+                        key=lambda x: map(int, x['name'].split('/')))
                 for name in names:
                     yield (container, name)
             except swiftclient.ClientException as x:

=== modified file 'lib/lp/services/librarianserver/storage.py'
--- lib/lp/services/librarianserver/storage.py	2016-09-19 13:53:42 +0000
+++ lib/lp/services/librarianserver/storage.py	2017-12-19 17:02:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -10,9 +10,9 @@
 import tempfile
 
 from swiftclient import client as swiftclient
-from twisted.python import log
 from twisted.internet import defer
 from twisted.internet.threads import deferToThread
+from twisted.python import log
 from twisted.web.static import StaticProducer
 
 from lp.registry.model.product import Product
@@ -123,8 +123,12 @@
             container, name = swift.swift_location(fileid)
             swift_connection = swift.connection_pool.get()
             try:
+                def get_object_quiet(*args, **kwargs):
+                    with swift.disable_swiftclient_logging():
+                        return swift_connection.get_object(*args, **kwargs)
+
                 headers, chunks = yield deferToThread(
-                    swift_connection.get_object,
+                    get_object_quiet,
                     container, name, resp_chunk_size=self.CHUNK_SIZE)
                 swift_stream = TxSwiftStream(swift_connection, chunks)
                 defer.returnValue(swift_stream)

=== modified file 'lib/lp/services/librarianserver/swift.py'
--- lib/lp/services/librarianserver/swift.py	2015-02-16 00:09:14 +0000
+++ lib/lp/services/librarianserver/swift.py	2017-12-19 17:02:21 +0000
@@ -1,12 +1,17 @@
-# Copyright 2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2013-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Move files from Librarian disk storage into Swift."""
 
 __metaclass__ = type
 __all__ = [
-    'to_swift', 'filesystem_path', 'swift_location',
-    'connection', 'connection_pool', 'SWIFT_CONTAINER_PREFIX',
+    'SWIFT_CONTAINER_PREFIX',
+    'connection',
+    'connection_pool',
+    'disable_swiftclient_logging',
+    'filesystem_path',
+    'swift_location',
+    'to_swift',
     ]
 
 from contextlib import contextmanager
@@ -29,6 +34,23 @@
 ONE_DAY = 24 * 60 * 60
 
 
+@contextmanager
+def disable_swiftclient_logging():
+    # swiftclient has some very rude logging practices: the low-level API
+    # calls `logger.exception` when a request fails, without considering
+    # whether the caller might handle it and recover.  This was introduced
+    # in 1.6.0 and removed in 3.2.0; until we're on a new enough version not
+    # to need to worry about this, we shut up the noisy logging around calls
+    # whose failure we can handle.
+    # Messier still, logging.getLogger('swiftclient') doesn't necessarily
+    # refer to the Logger instance actually being used by swiftclient, so we
+    # have to use swiftclient.logger directly.
+    old_disabled = swiftclient.logger.disabled
+    swiftclient.logger.disabled = True
+    yield
+    swiftclient.logger.disabled = old_disabled
+
+
 def to_swift(log, start_lfc_id=None, end_lfc_id=None, remove_func=False):
     '''Copy a range of Librarian files from disk into Swift.
 
@@ -118,7 +140,8 @@
             container, obj_name = swift_location(lfc)
 
             try:
-                swift_connection.head_container(container)
+                with disable_swiftclient_logging():
+                    swift_connection.head_container(container)
                 log.debug2('{0} container already exists'.format(container))
             except swiftclient.ClientException as x:
                 if x.http_status != 404:
@@ -127,7 +150,8 @@
                 swift_connection.put_container(container)
 
             try:
-                headers = swift_connection.head_object(container, obj_name)
+                with disable_swiftclient_logging():
+                    headers = swift_connection.head_object(container, obj_name)
                 log.debug(
                     "{0} already exists in Swift({1}, {2})".format(
                         lfc, container, obj_name))
@@ -188,7 +212,7 @@
             assert segment <= 9999, 'Insane number of segments'
             seg_name = '%s/%04d' % (obj_name, segment)
             seg_size = min(fs_size - fs_file.tell(), MAX_SWIFT_OBJECT_SIZE)
-            md5_stream = HashStream(fs_file)
+            md5_stream = HashStream(fs_file, length=seg_size)
             swift_md5_hash = swift_connection.put_object(
                 container, seg_name, md5_stream, seg_size)
             segment_md5_hash = md5_stream.hash.hexdigest()
@@ -304,13 +328,20 @@
 
 class HashStream:
     """Read a file while calculating a checksum as we go."""
-    def __init__(self, stream, hash_factory=hashlib.md5):
+    def __init__(self, stream, length=None, hash_factory=hashlib.md5):
         self._stream = stream
+        self._length = self._remaining = length
         self.hash_factory = hash_factory
         self.hash = hash_factory()
 
     def read(self, size=-1):
+        if self._remaining is not None:
+            if self._remaining <= 0:
+                return ''
+            size = min(size, self._remaining)
         chunk = self._stream.read(size)
+        if self._remaining is not None:
+            self._remaining -= len(chunk)
         self.hash.update(chunk)
         return chunk
 
@@ -320,6 +351,8 @@
     def seek(self, offset):
         """Seek to offset, and reset the hash."""
         self.hash = self.hash_factory()
+        if self._remaining is not None:
+            self._remaining = self._length - offset
         return self._stream.seek(offset)
 
 

=== modified file 'lib/lp/services/librarianserver/tests/test_gc.py'
--- lib/lp/services/librarianserver/tests/test_gc.py	2017-12-07 12:05:01 +0000
+++ lib/lp/services/librarianserver/tests/test_gc.py	2017-12-19 17:02:21 +0000
@@ -728,7 +728,8 @@
             name += suffix
         with swift.connection() as swift_connection:
             try:
-                swift_connection.head_object(container, name)
+                with swift.disable_swiftclient_logging():
+                    swift_connection.head_object(container, name)
                 return True
             except swiftclient.ClientException as x:
                 if x.http_status == 404:

=== modified file 'lib/lp/services/librarianserver/tests/test_swift.py'
--- lib/lp/services/librarianserver/tests/test_swift.py	2014-12-16 10:51:41 +0000
+++ lib/lp/services/librarianserver/tests/test_swift.py	2017-12-19 17:02:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2013-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Librarian disk to Swift storage tests."""
@@ -19,14 +19,17 @@
 from lp.services.features.testing import FeatureFixture
 from lp.services.librarian.client import LibrarianClient
 from lp.services.librarian.model import LibraryFileAlias
+from lp.services.librarianserver import swift
 from lp.services.librarianserver.storage import LibrarianStorage
 from lp.services.log.logger import BufferLogger
 from lp.testing import TestCase
-from lp.testing.layers import BaseLayer, LaunchpadZopelessLayer, LibrarianLayer
+from lp.testing.layers import (
+    BaseLayer,
+    LaunchpadZopelessLayer,
+    LibrarianLayer,
+    )
 from lp.testing.swift.fixture import SwiftFixture
 
-from lp.services.librarianserver import swift
-
 
 class TestFeedSwift(TestCase):
     layer = LaunchpadZopelessLayer
@@ -293,7 +296,8 @@
 
     def test_partial_read(self):
         empty_sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
-        s = swift.HashStream(StringIO('make me another coffee'), hashlib.sha1)
+        s = swift.HashStream(
+            StringIO('make me another coffee'), hash_factory=hashlib.sha1)
         self.assertEqual(s.hash.hexdigest(), empty_sha1)
         chunk = s.read(4)
         self.assertEqual(chunk, 'make')
@@ -304,6 +308,21 @@
         self.assertEqual(s.hash.hexdigest(),
                          '8c826e573016ce05f3968044f82507b46fd2aa93')
 
+    def test_limited_length(self):
+        base_stream = StringIO('make me a coffee')
+        s = swift.HashStream(base_stream, length=8)
+        chunk = s.read(4)
+        self.assertEqual(chunk, 'make')
+        self.assertEqual(s.hash.hexdigest(),
+                         '099dafc678df7d266c25f95ccf6cde22')
+        chunk = s.read(8)
+        self.assertEqual(chunk, ' me ')
+        self.assertEqual(s.hash.hexdigest(),
+                         '10a0334e435b75f35b1923842bd87f81')
+        self.assertEqual(s.read(), '')
+        self.assertEqual(s.tell(), 8)
+        self.assertEqual(base_stream.tell(), 8)
+
     def test_tell(self):
         s = swift.HashStream(StringIO('hurry up with that coffee'))
         self.assertEqual(s.tell(), 0)

=== modified file 'lib/lp/testing/swift/fakeswift.py'
--- lib/lp/testing/swift/fakeswift.py	2017-12-07 12:05:13 +0000
+++ lib/lp/testing/swift/fakeswift.py	2017-12-19 17:02:21 +0000
@@ -557,17 +557,33 @@
         """Compute service catalog for the given request and tenant."""
         port = request.transport.socket.getsockname()[1]
         tenant_id = self.tenants[tenant]["id"]
-        base_url = "http://%s:%d/swift/v1"; % (self.hostname, port)
+        base_url = "http://%s:%d"; % (self.hostname, port)
+        keystone_base_url = "%s/keystone/v2.0" % base_url
+        swift_base_url = "%s/swift/v1" % base_url
         catalog = [
-            {"endpoints": [
-                {"adminURL": base_url,
-                 "id": uuid.uuid4().hex,
-                 "internalURL": base_url + "/AUTH_" + tenant_id,
-                 "publicURL": base_url + "/AUTH_" + tenant_id,
-                 "region": DEFAULT_REGION}
-                ],
-             "endpoints_links": [],
-             "name": "swift",
-             "type": "object-store"
-             }]
+            {
+                "endpoints": [{
+                    "adminURL": keystone_base_url,
+                    "id": uuid.uuid4().hex,
+                    "internalURL": keystone_base_url,
+                    "publicURL": keystone_base_url,
+                    "region": DEFAULT_REGION,
+                    }],
+                "endpoints_links": [],
+                "name": "keystone",
+                "type": "identity",
+                },
+            {
+                "endpoints": [{
+                    "adminURL": swift_base_url,
+                    "id": uuid.uuid4().hex,
+                    "internalURL": swift_base_url + "/AUTH_" + tenant_id,
+                    "publicURL": swift_base_url + "/AUTH_" + tenant_id,
+                    "region": DEFAULT_REGION,
+                    }],
+                "endpoints_links": [],
+                "name": "swift",
+                "type": "object-store",
+                },
+            ]
         return catalog

=== modified file 'lib/lp/testing/swift/tests/test_fixture.py'
--- lib/lp/testing/swift/tests/test_fixture.py	2017-12-07 12:05:13 +0000
+++ lib/lp/testing/swift/tests/test_fixture.py	2017-12-19 17:02:21 +0000
@@ -8,9 +8,10 @@
 
 from datetime import datetime
 from hashlib import md5
-import httplib
 
+from requests.exceptions import ConnectionError
 from swiftclient import client as swiftclient
+from swiftclient.exceptions import ClientException
 from testtools.matchers import (
     GreaterThan,
     LessThan,
@@ -200,14 +201,14 @@
         # authenticated.
         self.swift_fixture.shutdown()
         self.assertRaises(
-            httplib.HTTPException,
+            ConnectionError,
             client.get_object, "size", str(size))
 
         # And even if we bring it back up, existing connections
         # continue to fail
         self.swift_fixture.startup()
         self.assertRaises(
-            httplib.HTTPException,
+            ClientException,
             client.get_object, "size", str(size))
 
         # But fresh connections are fine.


Follow ups