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