libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From to...@apache.org
Subject svn commit: r1485843 - in /libcloud/branches/0.12.x: ./ CHANGES libcloud/common/openstack.py libcloud/storage/drivers/cloudfiles.py libcloud/test/compute/test_openstack.py libcloud/test/storage/test_cloudfiles.py libcloud/utils/misc.py tox.ini
Date Thu, 23 May 2013 20:18:55 GMT
Author: tomaz
Date: Thu May 23 20:18:55 2013
New Revision: 1485843

URL: http://svn.apache.org/r1485843
Log:
Backport commits from trunk.

Modified:
    libcloud/branches/0.12.x/   (props changed)
    libcloud/branches/0.12.x/CHANGES
    libcloud/branches/0.12.x/libcloud/common/openstack.py
    libcloud/branches/0.12.x/libcloud/storage/drivers/cloudfiles.py
    libcloud/branches/0.12.x/libcloud/test/compute/test_openstack.py
    libcloud/branches/0.12.x/libcloud/test/storage/test_cloudfiles.py
    libcloud/branches/0.12.x/libcloud/utils/misc.py
    libcloud/branches/0.12.x/tox.ini

Propchange: libcloud/branches/0.12.x/
------------------------------------------------------------------------------
  Merged /libcloud/trunk:r1484932-1485842

Modified: libcloud/branches/0.12.x/CHANGES
URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/CHANGES?rev=1485843&r1=1485842&r2=1485843&view=diff
==============================================================================
--- libcloud/branches/0.12.x/CHANGES (original)
+++ libcloud/branches/0.12.x/CHANGES Thu May 23 20:18:55 2013
@@ -9,6 +9,15 @@ Changes with Apache Libcloud in deveplom
      validation. (LIBCLOUD-324)
      [Robert Chiniquy]
 
+   - Modify OpenStackAuthConnection and change auth_token_expires attribute to
+     be a datetime object instead of a string.
+     [Tomaz Muraus]
+
+   - Modify OpenStackAuthConnection to support re-using of the existing auth
+     token if it's still valid instead of re-authenticating on every
+     authenticate() call.
+     [Tomaz Muraus]
+
  *) Compute
 
     - Fix destroy_node method in the experimental libvirt driver.
@@ -41,6 +50,14 @@ Changes with Apache Libcloud in deveplom
       return value. (LIBCLOUD-326)
       [Andre Merzky, Tomaz Muraus]
 
+ *) Storage
+
+    - Fix an issue with double encoding the container name in the CloudFiles
+      driver upload_object method.
+      Also properly encode container and object name used in the HTTP request
+      in the get_container and get_object method. (LIBCLOUD-328)
+      [Tomaz Muraus]
+
  *) Load Balancer
 
     - Add ex_list_current_usage method to the Rackspace driver.

Modified: libcloud/branches/0.12.x/libcloud/common/openstack.py
URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/common/openstack.py?rev=1485843&r1=1485842&r2=1485843&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/common/openstack.py (original)
+++ libcloud/branches/0.12.x/libcloud/common/openstack.py Thu May 23 20:18:55 2013
@@ -19,8 +19,10 @@ Common utilities for OpenStack
 import sys
 import binascii
 import os
+import datetime
 
 from libcloud.utils.py3 import httplib
+from libcloud.utils.iso8601 import parse_date
 
 from libcloud.common.base import ConnectionUserAndKey, Response
 from libcloud.compute.types import (LibcloudError, InvalidCredsError,
@@ -33,10 +35,30 @@ except ImportError:
 
 AUTH_API_VERSION = '1.1'
 
+# Auth versions which contain token expiration information.
+AUTH_VERSIONS_WITH_EXPIRES = [
+    '1.1',
+    '2.0',
+    '2.0_apikey',
+    '2.0_password'
+]
+
+# How many seconds to substract from the auth token expiration time before
+# testing if the token is still valid.
+# The time is subtracted to account for the HTTP request latency and prevent
+# user from getting "InvalidCredsError" if token is about to expire.
+AUTH_TOKEN_EXPIRES_GRACE_SECONDS = 5
+
 __all__ = [
+    'OpenStackBaseConnection',
+    'OpenStackAuthConnection',
+    'OpenStackServiceCatalog',
+    'OpenStackDriverMixin',
     "OpenStackBaseConnection",
-    "OpenStackAuthConnection",
-    ]
+    "OpenStackAuthConnection"
+
+    'AUTH_TOKEN_EXPIRES_GRACE_SECONDS'
+]
 
 
 # @TODO: Refactor for re-use by other openstack drivers
@@ -87,17 +109,19 @@ class OpenStackAuthConnection(Connection
         # enable tests to use the same mock connection classes.
         self.conn_classes = parent_conn.conn_classes
 
-        if timeout:
-            self.timeout = timeout
-
         super(OpenStackAuthConnection, self).__init__(
-            user_id, key, url=auth_url, timeout=self.timeout)
+            user_id, key, url=auth_url, timeout=timeout)
 
         self.auth_version = auth_version
         self.auth_url = auth_url
-        self.urls = {}
         self.driver = self.parent_conn.driver
         self.tenant_name = tenant_name
+        self.timeout = timeout
+
+        self.urls = {}
+        self.auth_token = None
+        self.auth_token_expires = None
+        self.auth_user_info = None
 
     def morph_action_hook(self, action):
         return action
@@ -107,7 +131,19 @@ class OpenStackAuthConnection(Connection
         headers['Content-Type'] = 'application/json; charset=UTF-8'
         return headers
 
-    def authenticate(self):
+    def authenticate(self, force=False):
+        """
+        Authenticate against the keystone api.
+
+        @param force: Forcefully update the token even if it's already cached
+                      and still valid.
+        @type force: C{bool}
+        """
+        if not force and self.auth_version in AUTH_VERSIONS_WITH_EXPIRES \
+           and self._is_token_valid():
+            # If token is still valid, there is no need to re-authenticate
+            return self
+
         if self.auth_version == "1.0":
             return self.authenticate_1_0()
         elif self.auth_version == "1.1":
@@ -153,6 +189,8 @@ class OpenStackAuthConnection(Connection
                 raise MalformedResponseError('Missing X-Auth-Token in \
                                               response headers')
 
+        return self
+
     def authenticate_1_1(self):
         reqbody = json.dumps({'credentials': {'username': self.user_id,
                                               'key': self.key}})
@@ -174,9 +212,12 @@ class OpenStackAuthConnection(Connection
             except Exception:
                 e = sys.exc_info()[1]
                 raise MalformedResponseError('Failed to parse JSON', e)
+
             try:
+                expires = body['auth']['token']['expires']
+
                 self.auth_token = body['auth']['token']['id']
-                self.auth_token_expires = body['auth']['token']['expires']
+                self.auth_token_expires = parse_date(expires)
                 self.urls = body['auth']['serviceCatalog']
                 self.auth_user_info = None
             except KeyError:
@@ -184,6 +225,8 @@ class OpenStackAuthConnection(Connection
                 raise MalformedResponseError('Auth JSON response is \
                                              missing required elements', e)
 
+        return self
+
     def authenticate_2_0_with_apikey(self):
         # API Key based authentication uses the RAX-KSKEY extension.
         # http://s.apache.org/oAi
@@ -228,8 +271,10 @@ class OpenStackAuthConnection(Connection
 
             try:
                 access = body['access']
+                expires = access['token']['expires']
+
                 self.auth_token = access['token']['id']
-                self.auth_token_expires = access['token']['expires']
+                self.auth_token_expires = parse_date(expires)
                 self.urls = access['serviceCatalog']
                 self.auth_user_info = access.get('user', {})
             except KeyError:
@@ -237,6 +282,32 @@ class OpenStackAuthConnection(Connection
                 raise MalformedResponseError('Auth JSON response is \
                                              missing required elements', e)
 
+        return self
+
+    def _is_token_valid(self):
+        """
+        Return True if the current taken is already cached and hasn't expired
+        yet.
+
+        @rtype: C{bool}
+        """
+        if not self.auth_token:
+            return False
+
+        if not self.auth_token_expires:
+            return False
+
+        expires = self.auth_token_expires - \
+                datetime.timedelta(seconds=AUTH_TOKEN_EXPIRES_GRACE_SECONDS)
+
+        time_tuple_expires = expires.utctimetuple()
+        time_tuple_now = datetime.datetime.utcnow().utctimetuple()
+
+        # TODO: Subtract some reasonable grace time period
+        if time_tuple_now < time_tuple_expires:
+            return True
+
+        return False
 
 class OpenStackServiceCatalog(object):
     """
@@ -421,6 +492,8 @@ class OpenStackBaseConnection(Connection
         self._ex_force_service_name = ex_force_service_name
         self._ex_force_service_region = ex_force_service_region
 
+        self._osa = None
+
         if ex_force_auth_token:
             self.auth_token = ex_force_auth_token
 
@@ -487,7 +560,7 @@ class OpenStackBaseConnection(Connection
         if not self.auth_token:
             aurl = self.auth_url
 
-            if self._ex_force_auth_url != None:
+            if self._ex_force_auth_url is not None:
                 aurl = self._ex_force_auth_url
 
             if aurl == None:

Modified: libcloud/branches/0.12.x/libcloud/storage/drivers/cloudfiles.py
URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/storage/drivers/cloudfiles.py?rev=1485843&r1=1485842&r2=1485843&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/storage/drivers/cloudfiles.py (original)
+++ libcloud/branches/0.12.x/libcloud/storage/drivers/cloudfiles.py Thu May 23 20:18:55 2013
@@ -244,7 +244,8 @@ class CloudFilesStorageDriver(StorageDri
         return LazyList(get_more=self._get_more, value_dict=value_dict)
 
     def get_container(self, container_name):
-        response = self.connection.request('/%s' % (container_name),
+        container_name_encoded = self._encode_container_name(container_name)
+        response = self.connection.request('/%s' % (container_name_encoded),
                                            method='HEAD')
 
         if response.status == httplib.NO_CONTENT:
@@ -258,8 +259,11 @@ class CloudFilesStorageDriver(StorageDri
 
     def get_object(self, container_name, object_name):
         container = self.get_container(container_name)
-        response = self.connection.request('/%s/%s' % (container_name,
-                                                       object_name),
+        container_name_encoded = self._encode_container_name(container_name)
+        object_name_encoded = self._encode_container_name(object_name)
+
+        response = self.connection.request('/%s/%s' % (container_name_encoded,
+                                                       object_name_encoded),
                                            method='HEAD')
         if response.status in [httplib.OK, httplib.NO_CONTENT]:
             obj = self._headers_to_object(
@@ -311,9 +315,9 @@ class CloudFilesStorageDriver(StorageDri
         return response.status in [httplib.CREATED, httplib.ACCEPTED]
 
     def create_container(self, container_name):
-        container_name = self._clean_container_name(container_name)
+        container_name_encoded = self._encode_container_name(container_name)
         response = self.connection.request(
-            '/%s' % (container_name), method='PUT')
+            '/%s' % (container_name_encoded), method='PUT')
 
         if response.status == httplib.CREATED:
             # Accepted mean that container is not yet created but it will be
@@ -330,7 +334,7 @@ class CloudFilesStorageDriver(StorageDri
         raise LibcloudError('Unexpected status code: %s' % (response.status))
 
     def delete_container(self, container):
-        name = self._clean_container_name(container.name)
+        name = self._encode_container_name(container.name)
 
         # Only empty container can be deleted
         response = self.connection.request('/%s' % (name), method='DELETE')
@@ -405,8 +409,8 @@ class CloudFilesStorageDriver(StorageDri
                                 extra=extra, iterator=iterator)
 
     def delete_object(self, obj):
-        container_name = self._clean_container_name(obj.container.name)
-        object_name = self._clean_object_name(obj.name)
+        container_name = self._encode_container_name(obj.container.name)
+        object_name = self._encode_object_name(obj.name)
 
         response = self.connection.request(
             '/%s/%s' % (container_name, object_name), method='DELETE')
@@ -419,6 +423,26 @@ class CloudFilesStorageDriver(StorageDri
 
         raise LibcloudError('Unexpected status code: %s' % (response.status))
 
+    def ex_purge_object_from_cdn(self, obj, email=None):
+        """
+        Purge edge cache for the specified object.
+
+        @param email: Email where a notification will be sent when the job
+        completes. (optional)
+        @type email: C{str}
+        """
+        container_name = self._encode_container_name(obj.container.name)
+        object_name = self._encode_object_name(obj.name)
+        headers = {'X-Purge-Email': email} if email else {}
+
+        response = self.connection.request('/%s/%s' % (container_name,
+                                                       object_name),
+                                           method='DELETE',
+                                           headers=headers,
+                                           cdn_request=True)
+
+        return response.status == httplib.NO_CONTENT
+
     def ex_get_meta_data(self):
         """
         Get meta data
@@ -594,14 +618,14 @@ class CloudFilesStorageDriver(StorageDri
         extra = extra or {}
         meta_data = extra.get('meta_data')
 
-        container_name_cleaned = self._clean_container_name(container.name)
-        object_name_cleaned = self._clean_object_name(object_name)
-        request_path = '/%s/%s' % (container_name_cleaned, object_name_cleaned)
+        container_name_encoded = self._encode_container_name(container.name)
+        object_name_encoded = self._encode_object_name(object_name)
+        request_path = '/%s/%s' % (container_name_encoded, object_name_encoded)
 
         headers = {'X-Auth-Token': self.connection.auth_token,
                    'X-Object-Manifest': '%s/%s/' %
-                                        (container_name_cleaned,
-                                         object_name_cleaned)}
+                                        (container_name_encoded,
+                                         object_name_encoded)}
 
         data = ''
         response = self.connection.request(request_path,
@@ -657,8 +681,8 @@ class CloudFilesStorageDriver(StorageDri
                     upload_func_kwargs, extra=None, file_path=None,
                     iterator=None, verify_hash=True):
         extra = extra or {}
-        container_name_cleaned = self._clean_container_name(container.name)
-        object_name_cleaned = self._clean_object_name(object_name)
+        container_name_encoded = self._encode_container_name(container.name)
+        object_name_encoded = self._encode_object_name(object_name)
         content_type = extra.get('content_type', None)
         meta_data = extra.get('meta_data', None)
 
@@ -668,7 +692,7 @@ class CloudFilesStorageDriver(StorageDri
                 key = 'X-Object-Meta-%s' % (key)
                 headers[key] = value
 
-        request_path = '/%s/%s' % (container_name_cleaned, object_name_cleaned)
+        request_path = '/%s/%s' % (container_name_encoded, object_name_encoded)
         result_dict = self._upload_object(
             object_name=object_name, content_type=content_type,
             upload_func=upload_func, upload_func_kwargs=upload_func_kwargs,
@@ -702,9 +726,9 @@ class CloudFilesStorageDriver(StorageDri
             raise LibcloudError('status_code=%s' % (response.status),
                                 driver=self)
 
-    def _clean_container_name(self, name):
+    def _encode_container_name(self, name):
         """
-        Clean container name.
+        Encode container name so it can be used as part of the HTTP request.
         """
         if name.startswith('/'):
             name = name[1:]
@@ -722,7 +746,7 @@ class CloudFilesStorageDriver(StorageDri
 
         return name
 
-    def _clean_object_name(self, name):
+    def _encode_object_name(self, name):
         name = urlquote(name)
         return name
 

Modified: libcloud/branches/0.12.x/libcloud/test/compute/test_openstack.py
URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/test/compute/test_openstack.py?rev=1485843&r1=1485842&r2=1485843&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/test/compute/test_openstack.py (original)
+++ libcloud/branches/0.12.x/libcloud/test/compute/test_openstack.py Thu May 23 20:18:55 2013
@@ -12,14 +12,18 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+
 import sys
 import unittest
+import datetime
 
 try:
     import simplejson as json
 except ImportError:
     import json
 
+from mock import Mock
+
 from libcloud.utils.py3 import httplib
 from libcloud.utils.py3 import method_type
 from libcloud.utils.py3 import u
@@ -27,6 +31,8 @@ from libcloud.utils.py3 import u
 from libcloud.common.types import InvalidCredsError, MalformedResponseError, \
                                   LibcloudError
 from libcloud.common.openstack import OpenStackBaseConnection
+from libcloud.common.openstack import OpenStackAuthConnection
+from libcloud.common.openstack import AUTH_TOKEN_EXPIRES_GRACE_SECONDS
 from libcloud.compute.types import Provider
 from libcloud.compute.providers import get_driver
 from libcloud.compute.drivers.openstack import (
@@ -70,6 +76,7 @@ class OpenStack_1_0_ResponseTestCase(uni
 
 
 class OpenStackServiceCatalogTests(unittest.TestCase):
+    # TODO refactor and move into libcloud/test/common
     def test_connection_get_service_catalog(self):
         connection = OpenStackBaseConnection(*OPENSTACK_PARAMS)
         connection.conn_classes = (OpenStackMockHttp, OpenStackMockHttp)
@@ -84,14 +91,129 @@ class OpenStackServiceCatalogTests(unitt
 
         expected_urls = [
             'https://cdn2.clouddrive.com/v1/MossoCloudFS',
-            'https://cdn2.clouddrive.com/v1/MossoCloudFS'
         ]
 
         self.assertTrue('cloudFilesCDN' in catalog)
-        self.assertEqual(len(endpoints), 2)
+        self.assertEqual(len(endpoints), len(expected_urls))
         self.assertEqual(public_urls, expected_urls)
 
 
+class OpenStackAuthConnectionTests(unittest.TestCase):
+    # TODO refactor and move into libcloud/test/common
+    def test_basic_authentication(self):
+        tuples = [
+           ('1.0', OpenStackMockHttp),
+           ('1.1', OpenStackMockHttp),
+           ('2.0', OpenStack_2_0_MockHttp),
+           ('2.0_apikey', OpenStack_2_0_MockHttp),
+           ('2.0_password', OpenStack_2_0_MockHttp)
+        ]
+
+        user_id = OPENSTACK_PARAMS[0]
+        key = OPENSTACK_PARAMS[1]
+
+        for (auth_version, mock_http_class) in tuples:
+            connection = \
+                    self._get_mock_connection(mock_http_class=mock_http_class)
+            auth_url = connection.auth_url
+
+            osa = OpenStackAuthConnection(connection, auth_url, auth_version,
+                                          user_id, key)
+
+            self.assertEqual(osa.urls, {})
+            self.assertEqual(osa.auth_token, None)
+            self.assertEqual(osa.auth_user_info, None)
+            osa = osa.authenticate()
+
+            self.assertTrue(len(osa.urls) >= 1)
+            self.assertTrue(osa.auth_token is not None)
+
+            if auth_version in ['1.1', '2.0', '2.0_apikey', '2.0_password']:
+                self.assertTrue(osa.auth_token_expires is not None)
+
+            if auth_version in ['2.0', '2.0_apikey', '2.0_password']:
+                self.assertTrue(osa.auth_user_info is not None)
+
+    def test_token_expiration_and_force_reauthentication(self):
+        user_id = OPENSTACK_PARAMS[0]
+        key = OPENSTACK_PARAMS[1]
+
+        connection = self._get_mock_connection(OpenStack_2_0_MockHttp)
+        auth_url = connection.auth_url
+        auth_version = '2.0'
+
+        yesterday = datetime.datetime.today() - datetime.timedelta(1)
+        tomorrow = datetime.datetime.today() + datetime.timedelta(1)
+
+        osa = OpenStackAuthConnection(connection, auth_url, auth_version,
+                                      user_id, key)
+
+        mocked_auth_method = Mock(wraps=osa.authenticate_2_0_with_body)
+        osa.authenticate_2_0_with_body = mocked_auth_method
+
+        # Force re-auth, expired token
+        osa.auth_token = None
+        osa.auth_token_expires = yesterday
+        count = 5
+
+        for i in range(0, count):
+            osa.authenticate(force=True)
+
+        self.assertEqual(mocked_auth_method.call_count, count)
+
+        # No force reauth, expired token
+        osa.auth_token = None
+        osa.auth_token_expires = yesterday
+
+        mocked_auth_method.call_count = 0
+        self.assertEqual(mocked_auth_method.call_count, 0)
+
+        for i in range(0, count):
+            osa.authenticate(force=False)
+
+        self.assertEqual(mocked_auth_method.call_count, count)
+
+        # No force reauth, valid / non-expired token
+        osa.auth_token = None
+
+        mocked_auth_method.call_count = 0
+        self.assertEqual(mocked_auth_method.call_count, 0)
+
+        for i in range(0, count):
+            osa.authenticate(force=False)
+
+            if i == 0:
+                osa.auth_token_expires = tomorrow
+
+        self.assertEqual(mocked_auth_method.call_count, 1)
+
+        # No force reauth, valid / non-expired token which is about to expire in
+        # less than AUTH_TOKEN_EXPIRES_GRACE_SECONDS
+        soon = datetime.datetime.now() + \
+            datetime.timedelta(seconds=AUTH_TOKEN_EXPIRES_GRACE_SECONDS - 1)
+        osa.auth_token = None
+
+        mocked_auth_method.call_count = 0
+        self.assertEqual(mocked_auth_method.call_count, 0)
+
+        for i in range(0, count):
+            osa.authenticate(force=False)
+
+            if i == 0:
+                osa.auth_token_expires = soon
+
+        self.assertEqual(mocked_auth_method.call_count, 5)
+
+    def _get_mock_connection(self, mock_http_class):
+        connection = OpenStackBaseConnection(*OPENSTACK_PARAMS)
+        connection.conn_classes = (mock_http_class, mock_http_class)
+        connection.auth_url = "https://auth.api.example.com/v1.1/"
+        connection._ex_force_base_url = "https://www.foo.com"
+        connection.driver = OpenStack_1_0_NodeDriver(*OPENSTACK_PARAMS)
+
+        return connection
+
+
 class OpenStack_1_0_Tests(unittest.TestCase, TestCaseMixin):
     should_list_locations = False
 
@@ -127,7 +249,9 @@ class OpenStack_1_0_Tests(unittest.TestC
 
     def test_auth_token_expires_is_set(self):
         self.driver.connection._populate_hosts_and_request_paths()
-        self.assertEquals(self.driver.connection.auth_token_expires, "2011-09-18T02:44:17.000-05:00")
+
+        expires = self.driver.connection.auth_token_expires
+        self.assertEquals(expires.isoformat(), "2011-09-18T02:44:17-05:00")
 
     def test_auth(self):
         OpenStackMockHttp.type = 'UNAUTHORIZED'
@@ -603,7 +727,8 @@ class OpenStack_1_1_Tests(unittest.TestC
         self.driver.connection.auth_token_expires = None
         self.driver.connection._populate_hosts_and_request_paths()
 
-        self.assertEquals(self.driver.connection.auth_token_expires, "2011-11-23T21:00:14.000-06:00")
+        expires = self.driver.connection.auth_token_expires
+        self.assertEquals(expires.isoformat(), "2011-11-23T21:00:14-06:00")
 
     def test_ex_force_base_url(self):
         # change base url and trash the current auth token so we can re-authenticate
@@ -1066,7 +1191,6 @@ class OpenStack_1_1_MockHttp(MockHttpTes
 
         raise NotImplementedError()
 
-
     def _v1_1_slug_servers_12064(self, method, url, body, headers):
         if method == "GET":
             body = self.fixtures.load('_servers_12064.json')

Modified: libcloud/branches/0.12.x/libcloud/test/storage/test_cloudfiles.py
URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/test/storage/test_cloudfiles.py?rev=1485843&r1=1485842&r2=1485843&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/test/storage/test_cloudfiles.py (original)
+++ libcloud/branches/0.12.x/libcloud/test/storage/test_cloudfiles.py Thu May 23 20:18:55
2013
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
@@ -28,6 +29,7 @@ import libcloud.utils.files
 from libcloud.utils.py3 import PY3
 from libcloud.utils.py3 import b
 from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import urlquote
 
 if PY3:
     from io import FileIO as file
@@ -44,6 +46,7 @@ from libcloud.storage.drivers.cloudfiles
 from libcloud.storage.drivers.dummy import DummyIterator
 
 from libcloud.test import StorageMockHttp, MockRawResponse # pylint: disable-msg=E0611
+from libcloud.test import MockHttpTestCase # pylint: disable-msg=E0611
 from libcloud.test.file_fixtures import StorageFileFixtures, OpenStackFixtures # pylint:
disable-msg=E0611
 
 current_hash = None
@@ -623,6 +626,26 @@ class CloudFilesTests(unittest.TestCase)
         finally:
             self.driver.connection.request = _request
 
+    def test_create_container_put_object_name_encoding(self):
+        def upload_file(self, response, file_path, chunked=False,
+                     calculate_hash=True):
+            return True, 'hash343hhash89h932439jsaa89', 1000
+
+        old_func = CloudFilesStorageDriver._upload_file
+        CloudFilesStorageDriver._upload_file = upload_file
+
+        container_name = 'speci@l_name'
+        object_name = 'm@obj€ct'
+        file_path = os.path.abspath(__file__)
+
+        container = self.driver.create_container(container_name=container_name)
+        self.assertEqual(container.name, container_name)
+
+        obj = self.driver.upload_object(file_path=file_path, container=container,
+                                        object_name=object_name)
+        self.assertEqual(obj.name, object_name)
+        CloudFilesStorageDriver._upload_file = old_func
+
     def test_ex_enable_static_website(self):
         container = Container(name='foo_bar_container', extra={}, driver=self)
         result = self.driver.ex_enable_static_website(container=container,
@@ -680,7 +703,7 @@ class CloudFilesTests(unittest.TestCase)
             pass
 
 
-class CloudFilesMockHttp(StorageMockHttp):
+class CloudFilesMockHttp(StorageMockHttp, MockHttpTestCase):
 
     fixtures = StorageFileFixtures('cloudfiles')
     auth_fixtures = OpenStackFixtures()
@@ -828,6 +851,22 @@ class CloudFilesMockHttp(StorageMockHttp
         status_code = httplib.CREATED
         return (status_code, body, headers, httplib.responses[httplib.OK])
 
+    def _v1_MossoCloudFS_speci_40l_name(self, method, url, body, headers):
+        # test_create_container_put_object_name_encoding
+        # Verify that the name is properly url encoded
+        container_name = 'speci@l_name'
+        encoded_container_name = urlquote(container_name)
+        self.assertTrue(encoded_container_name in url)
+
+        headers = copy.deepcopy(self.base_headers)
+        body = self.fixtures.load('list_container_objects_empty.json')
+        headers = copy.deepcopy(self.base_headers)
+        headers.update({ 'content-length': 18,
+                         'date': 'Mon, 28 Feb 2011 07:52:57 GMT'
+                       })
+        status_code = httplib.CREATED
+        return (status_code, body, headers, httplib.responses[httplib.OK])
+
     def _v1_MossoCloudFS_test_create_container_ALREADY_EXISTS(
         self, method, url, body, headers):
         # test_create_container_already_exists
@@ -911,6 +950,19 @@ class CloudFilesMockRawResponse(MockRawR
         headers['etag'] = 'hash343hhash89h932439jsaa89'
         return (httplib.CREATED, body, headers, httplib.responses[httplib.OK])
 
+    def _v1_MossoCloudFS_speci_40l_name_m_40obj_E2_82_ACct(self, method, url,
+                                                           body, headers):
+        # test_create_container_put_object_name_encoding
+        # Verify that the name is properly url encoded
+        object_name = 'm@obj€ct'
+        encoded_object_name = urlquote(object_name)
+
+        headers = copy.deepcopy(self.base_headers)
+        body = ''
+        headers['etag'] = 'hash343hhash89h932439jsaa89'
+        return (httplib.CREATED, body, headers, httplib.responses[httplib.OK])
+
+
     def _v1_MossoCloudFS_foo_bar_container_empty(self, method, url, body,
                                                  headers):
         # test_upload_object_zero_size_object

Modified: libcloud/branches/0.12.x/libcloud/utils/misc.py
URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/utils/misc.py?rev=1485843&r1=1485842&r2=1485843&view=diff
==============================================================================
--- libcloud/branches/0.12.x/libcloud/utils/misc.py (original)
+++ libcloud/branches/0.12.x/libcloud/utils/misc.py Thu May 23 20:18:55 2013
@@ -13,6 +13,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+__all__ = [
+    'get_driver',
+    'set_driver',
+    'merge_valid_keys',
+    'get_new_obj',
+    'str2dicts',
+    'dict2str',
+    'reverse_dict',
+    'lowercase_keys'
+]
+
 import sys
 
 
@@ -206,7 +217,7 @@ def dict2str(data):
     """
     result = ''
     for k in data:
-        if data[k] != None:
+        if data[k] is not None:
             result += '%s %s\n' % (str(k), str(data[k]))
         else:
             result += '%s\n' % str(k)

Modified: libcloud/branches/0.12.x/tox.ini
URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/tox.ini?rev=1485843&r1=1485842&r2=1485843&view=diff
==============================================================================
--- libcloud/branches/0.12.x/tox.ini (original)
+++ libcloud/branches/0.12.x/tox.ini Thu May 23 20:18:55 2013
@@ -1,6 +1,7 @@
 [tox]
-
 envlist = py25,py26,py27,pypy,py32,py33
+setenv =
+    PIP_USE_MIRRORS=1
 
 [testenv]
 deps = mock



Mime
View raw message