Author: tomaz
Date: Wed Nov 7 04:11:06 2012
New Revision: 1406447
URL: http://svn.apache.org/viewvc?rev=1406447&view=rev
Log:
Add a new "local storage" storage driver. Contributed by Mahendra M, part of
LIBCLOUD-252.
Added:
libcloud/trunk/libcloud/storage/drivers/local.py
libcloud/trunk/libcloud/test/storage/test_local.py
Modified:
libcloud/trunk/CHANGES
libcloud/trunk/libcloud/storage/providers.py
libcloud/trunk/libcloud/storage/types.py
libcloud/trunk/setup.py
libcloud/trunk/tox.ini
Modified: libcloud/trunk/CHANGES
URL:
http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1406447&r1=1406446&r2=1406447&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Wed Nov 7 04:11:06 2012
@@ -40,6 +40,11 @@ Changes with Apache Libcloud in developm
the code to make it easier to maintain.
[Tomaz Muraus]
+ *) Storage
+
+ - Add a new local storage driver.
+ [Mahendra M]
+
*) DNS
- Update 'if type' checks in the update_record methods to behave correctly
Added: libcloud/trunk/libcloud/storage/drivers/local.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/storage/drivers/local.py?rev=1406447&view=auto
==============================================================================
--- libcloud/trunk/libcloud/storage/drivers/local.py (added)
+++ libcloud/trunk/libcloud/storage/drivers/local.py Wed Nov 7 04:11:06 2012
@@ -0,0 +1,592 @@
+# 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.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+
+"""
+Provides storage driver for working with local filesystem
+"""
+
+import errno
+import os
+import shutil
+import sys
+
+try:
+ from lockfile import mkdirlockfile
+except ImportError:
+ raise ImportError('Missing lockfile dependency, you can install it ' \
+ 'using pip: pip install lockfile')
+
+from libcloud.utils.files import read_in_chunks
+from libcloud.common.base import Connection
+from libcloud.storage.base import Object, Container, StorageDriver
+from libcloud.common.types import LibcloudError, LazyList
+from libcloud.storage.types import ContainerAlreadyExistsError
+from libcloud.storage.types import ContainerDoesNotExistError
+from libcloud.storage.types import ContainerIsNotEmptyError
+from libcloud.storage.types import ObjectError
+from libcloud.storage.types import ObjectDoesNotExistError
+from libcloud.storage.types import InvalidContainerNameError
+
+IGNORE_FOLDERS = ['.lock', '.hash']
+
+
+class LockLocalStorage(object):
+ """
+ A class to help in locking a local path before being updated
+ """
+ def __init__(self, path):
+ self.path = path
+ self.lock = mkdirlockfile.MkdirLockFile(self.path, threaded=True)
+
+ def __enter__(self):
+ try:
+ self.lock.acquire(timeout=0.1)
+ except lockfile.LockTimeout:
+ raise LibcloudError('Lock timeout')
+
+ def __exit__(self, type, value, traceback):
+ if self.lock.is_locked():
+ self.lock.release()
+
+ if value is not None:
+ raise value
+
+
+class LocalStorageDriver(StorageDriver):
+ """
+ Implementation of local file-system based storage. This is helpful
+ where the user would want to use the same code (using libcloud) and
+ switch between cloud storage and local storage
+ """
+
+ connectionCls = Connection
+ name = 'Local Storage'
+
+ def __init__(self, key, secret=None, secure=True, host=None, port=None,
+ **kwargs):
+
+ # Use the key as the path to the storage
+ self.base_path = key[0]
+
+ if not os.path.isdir(self.base_path):
+ raise LibcloudError('The base path is not a directory')
+
+ super(StorageDriver, self).__init__(key=key, secret=secret,
+ secure=secure, host=host,
+ port=port, **kwargs)
+
+ def _make_path(self, path, ignore_existing=True):
+ """
+ Create a path by checking if it already exists
+ """
+
+ try:
+ os.makedirs(path)
+ except OSError:
+ exp = sys.exc_info()[1]
+ if exp.errno == errno.EEXIST and not ignore_existing:
+ raise exp
+
+ def _check_container_name(self, container_name):
+ """
+ Check if the container name is valid
+
+ @param container_name: Container name
+ @type container_name: C{str}
+ """
+
+ if '/' in container_name or '\\' in container_name:
+ raise InvalidContainerNameError(value=None, driver=self,
+ container_name=container_name)
+
+ def _make_container(self, container_name):
+ """
+ Create a container instance
+
+ @param container_name: Container name.
+ @type container_name: C{str}
+
+ @return: Container instance.
+ @rtype: L{Container}
+ """
+
+ self._check_container_name(container_name)
+
+ full_path = os.path.join(self.base_path, container_name)
+
+ try:
+ stat = os.stat(full_path)
+ if not os.path.isdir(full_path):
+ raise OSError('Target path is not a directory')
+ except OSError:
+ raise ContainerDoesNotExistError(value=None, driver=self,
+ container_name=container_name)
+
+ extra = {}
+ extra['creation_time'] = stat.st_ctime
+ extra['access_time'] = stat.st_atime
+ extra['modify_time'] = stat.st_mtime
+
+ return Container(name=container_name, extra=extra, driver=self)
+
+ def _make_object(self, container, object_name):
+ """
+ Create an object instance
+
+ @param container: Container.
+ @type container: L{Container}
+
+ @param object_name: Object name.
+ @type object_name: C{str}
+
+ @return: Object instance.
+ @rtype: L{Object}
+ """
+
+ full_path = os.path.join(self.base_path, container.name, object_name)
+
+ if os.path.isdir(full_path):
+ raise ObjectError(value=None, driver=self, object_name=object_name)
+
+ try:
+ stat = os.stat(full_path)
+ except Exception:
+ raise ObjectDoesNotExistError(value=None, driver=self,
+ object_name=object_name)
+
+ extra = {}
+ extra['creation_time'] = stat.st_ctime
+ extra['access_time'] = stat.st_atime
+ extra['modify_time'] = stat.st_mtime
+
+ return Object(name=object_name, size=stat.st_size, extra=extra,
+ driver=self, container=container, hash=None,
+ meta_data=None)
+
+ def list_containers(self):
+ """
+ Return a list of containers.
+
+ @return: A list of Container instances.
+ @rtype: C{list} of L{Container}
+ """
+
+ containers = []
+
+ for container_name in os.listdir(self.base_path):
+ full_path = os.path.join(self.base_path, container_name)
+ if not os.path.isdir(full_path):
+ continue
+ containers.append(self._make_container(container_name))
+
+ return containers
+
+ def _get_objects(self, container):
+ """
+ Recursively iterate through the file-system and return the object names
+ """
+
+ cpath = self.get_container_cdn_url(container, check=True)
+
+ for folder, subfolders, files in os.walk(cpath, topdown=True):
+ # Remove unwanted subfolders
+ for subf in IGNORE_FOLDERS:
+ if subf in subfolders:
+ subfolders.remove(subf)
+
+ for name in files:
+ full_path = os.path.join(folder, name)
+ object_name = os.path.relpath(full_path, start=cpath)
+ yield self._make_object(container, object_name)
+
+ def _get_more(self, last_key, value_dict):
+ """
+ A handler for using with LazyList
+ """
+ container = value_dict['container']
+ objects = [obj for obj in self._get_objects(container)]
+
+ return (objects, None, True)
+
+ def list_container_objects(self, container):
+ """
+ Return a list of objects for the given container.
+
+ @param container: Container instance
+ @type container: L{Container}
+
+ @return: A list of Object instances.
+ @rtype: C{list} of L{Object}
+ """
+
+ value_dict = {'container': container}
+ return LazyList(get_more=self._get_more, value_dict=value_dict)
+
+ def get_container(self, container_name):
+ """
+ Return a container instance.
+
+ @param container_name: Container name.
+ @type container_name: C{str}
+
+ @return: L{Container} instance.
+ @rtype: L{Container}
+ """
+ return self._make_container(container_name)
+
+ def get_container_cdn_url(self, container, check=False):
+ """
+ Return a container CDN URL.
+
+ @param container: Container instance
+ @type container: L{Container}
+
+ @param check: Indicates if the path's existance must be checked
+ @type check: C{bool}
+
+ @return: A CDN URL for this container.
+ @rtype: C{str}
+ """
+ path = os.path.join(self.base_path, container.name)
+
+ if check and not os.path.isdir(path):
+ raise ContainerDoesNotExistError(value=None, driver=self,
+ container_name=container.name)
+
+ return path
+
+ def get_object(self, container_name, object_name):
+ """
+ Return an object instance.
+
+ @param container_name: Container name.
+ @type container_name: C{str}
+
+ @param object_name: Object name.
+ @type object_name: C{str}
+
+ @return: L{Object} instance.
+ @rtype: L{Object}
+ """
+ container = self._make_container(container_name)
+ return self._make_object(container, object_name)
+
+ def get_object_cdn_url(self, obj):
+ """
+ Return a object CDN URL.
+
+ @param obj: Object instance
+ @type obj: L{Object}
+
+ @return: A CDN URL for this object.
+ @rtype: C{str}
+ """
+ return os.path.join(self.base_path, obj.container.name, obj.name)
+
+ def enable_container_cdn(self, container):
+ """
+ Enable container CDN.
+
+ @param container: Container instance
+ @type container: L{Container}
+
+ @rtype: C{bool}
+ """
+
+ path = self.get_container_cdn_url(container)
+ lock = lockfile.MkdirFileLock(path, threaded=True)
+
+ with LockLocalStorage(path) as lock:
+ self._make_path(path)
+
+ return True
+
+ def enable_object_cdn(self, obj):
+ """
+ Enable object CDN.
+
+ @param obj: Object instance
+ @type obj: L{Object}
+
+ @rtype: C{bool}
+ """
+ path = self.get_object_cdn_url(obj)
+
+ with LockLocalStorage(path) as lock:
+ if os.path.exists(path):
+ return False
+ try:
+ obj_file = open(path, 'w')
+ obj_file.close()
+ except:
+ return False
+
+ return True
+
+ def download_object(self, obj, destination_path, overwrite_existing=False,
+ delete_on_failure=True):
+ """
+ Download an object to the specified destination path.
+
+ @param obj: Object instance.
+ @type obj: L{Object}
+
+ @param destination_path: Full path to a file or a directory where the
+ incoming file will be saved.
+ @type destination_path: C{str}
+
+ @param overwrite_existing: True to overwrite an existing file,
+ defaults to False.
+ @type overwrite_existing: C{bool}
+
+ @param delete_on_failure: True to delete a partially downloaded file if
+ the download was not successful (hash mismatch / file size).
+ @type delete_on_failure: C{bool}
+
+ @return: True if an object has been successfully downloaded, False
+ otherwise.
+ @rtype: C{bool}
+ """
+
+ obj_path = self.get_object_cdn_url(obj)
+ base_name = os.path.basename(destination_path)
+
+ if not base_name and not os.path.exists(destination_path):
+ raise LibcloudError(
+ value='Path %s does not exist' % (destination_path),
+ driver=self)
+
+ if not base_name:
+ file_path = os.path.join(destination_path, obj.name)
+ else:
+ file_path = destination_path
+
+ if os.path.exists(file_path) and not overwrite_existing:
+ raise LibcloudError(
+ value='File %s already exists, but ' % (file_path) +
+ 'overwrite_existing=False',
+ driver=self)
+
+ try:
+ shutil.copy(obj_path, file_path)
+ except IOError:
+ if delete_on_failure:
+ try:
+ os.unlink(file_path)
+ except Exception:
+ pass
+ return False
+
+ return True
+
+ def download_object_as_stream(self, obj, chunk_size=None):
+ """
+ Return a generator which yields object data.
+
+ @param obj: Object instance
+ @type obj: L{Object}
+
+ @param chunk_size: Optional chunk size (in bytes).
+ @type chunk_size: C{int}
+
+ @rtype: C{object}
+ """
+
+ path = self.get_object_cdn_url(obj)
+
+ with open(path) as obj_file:
+ for data in read_in_chunks(obj_file, chunk_size=chunk_size):
+ yield data
+
+ def upload_object(self, file_path, container, object_name, extra=None,
+ verify_hash=True):
+ """
+ Upload an object currently located on a disk.
+
+ @param file_path: Path to the object on disk.
+ @type file_path: C{str}
+
+ @param container: Destination container.
+ @type container: L{Container}
+
+ @param object_name: Object name.
+ @type object_name: C{str}
+
+ @param verify_hash: Verify hast
+ @type verify_hash: C{bool}
+
+ @param extra: (optional) Extra attributes (driver specific).
+ @type extra: C{dict}
+
+ @rtype: C{object}
+ """
+
+ path = self.get_container_cdn_url(container, check=True)
+ obj_path = os.path.join(path, object_name)
+ base_path = os.path.dirname(obj_path)
+
+ self._make_path(base_path)
+
+ with LockLocalStorage(obj_path) as lock:
+ shutil.copy(file_path, obj_path)
+
+ os.chmod(obj_path, int('664', 8))
+
+ return self._make_object(container, object_name)
+
+ def upload_object_via_stream(self, iterator, container,
+ object_name,
+ extra=None):
+ """
+ Upload an object using an iterator.
+
+ If a provider supports it, chunked transfer encoding is used and you
+ don't need to know in advance the amount of data to be uploaded.
+
+ Otherwise if a provider doesn't support it, iterator will be exhausted
+ so a total size for data to be uploaded can be determined.
+
+ Note: Exhausting the iterator means that the whole data must be
+ buffered in memory which might result in memory exhausting when
+ uploading a very large object.
+
+ If a file is located on a disk you are advised to use upload_object
+ function which uses fs.stat function to determine the file size and it
+ doesn't need to buffer whole object in the memory.
+
+ @type iterator: C{object}
+ @param iterator: An object which implements the iterator interface.
+
+ @type container: L{Container}
+ @param container: Destination container.
+
+ @type object_name: C{str}
+ @param object_name: Object name.
+
+ @type extra: C{dict}
+ @param extra: (optional) Extra attributes (driver specific). Note:
+ This dictionary must contain a 'content_type' key which represents
+ a content type of the stored object.
+
+ @rtype: C{object}
+ """
+
+ path = self.get_container_cdn_url(container, check=True)
+ obj_path = os.path.join(path, object_name)
+ base_path = os.path.dirname(obj_path)
+
+ self._make_path(base_path)
+
+ with LockLocalStorage(obj_path) as lock:
+ obj_file = open(obj_path, 'w')
+ for data in iterator:
+ obj_file.write(data)
+
+ obj_file.close()
+
+ os.chmod(obj_path, int('664', 8))
+
+ return self._make_object(container, object_name)
+
+ def delete_object(self, obj):
+ """
+ Delete an object.
+
+ @type obj: L{Object}
+ @param obj: Object instance.
+
+ @return: C{bool} True on success.
+ @rtype: C{bool}
+ """
+
+ path = self.get_object_cdn_url(obj)
+
+ with LockLocalStorage(path) as lock:
+ try:
+ os.unlink(path)
+ except Exception:
+ return False
+
+ # Check and delete the folder if required
+ path = os.path.dirname(path)
+
+ try:
+ if path != obj.container.get_cdn_url():
+ os.rmdir(path)
+ except Exception:
+ pass
+
+ return True
+
+ def create_container(self, container_name):
+ """
+ Create a new container.
+
+ @type container_name: C{str}
+ @param container_name: Container name.
+
+ @return: C{Container} instance on success.
+ @rtype: L{Container}
+ """
+
+ self._check_container_name(container_name)
+
+ path = os.path.join(self.base_path, container_name)
+
+ try:
+ self._make_path(path, ignore_existing=False)
+ except OSError:
+ exp = sys.exc_info()[1]
+ if exp.errno == errno.EEXIST:
+ raise ContainerAlreadyExistsError(
+ value='Container with this name already exists. The name '
+ 'must be unique among all the containers in the '
+ 'system',
+ container_name=container_name, driver=self)
+ else:
+ raise LibcloudError(
+ 'Error creating container %s' % container_name,
+ driver=self)
+ except Exception:
+ raise LibcloudError(
+ 'Error creating container %s' % container_name, driver=self)
+
+ return self._make_container(container_name)
+
+ def delete_container(self, container):
+ """
+ Delete a container.
+
+ @type container: L{Container}
+ @param container: Container instance
+
+ @return: True on success, False otherwise.
+ @rtype: C{bool}
+ """
+
+ # Check if there are any objects inside this
+ for obj in self._get_objects(container):
+ raise ContainerIsNotEmptyError(value='Container is not empty',
+ container_name=container.name, driver=self)
+
+ path = self.get_container_cdn_url(container, check=True)
+
+ with LockLocalStorage(path) as lock:
+ try:
+ shutil.rmtree(path)
+ except Exception:
+ return False
+
+ return True
Modified: libcloud/trunk/libcloud/storage/providers.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/storage/providers.py?rev=1406447&r1=1406446&r2=1406447&view=diff
==============================================================================
--- libcloud/trunk/libcloud/storage/providers.py (original)
+++ libcloud/trunk/libcloud/storage/providers.py Wed Nov 7 04:11:06 2012
@@ -43,7 +43,9 @@ DRIVERS = {
('libcloud.storage.drivers.cloudfiles',
'CloudFilesSwiftStorageDriver'),
Provider.NIMBUS:
- ('libcloud.storage.drivers.nimbus', 'NimbusStorageDriver')
+ ('libcloud.storage.drivers.nimbus', 'NimbusStorageDriver'),
+ Provider.LOCAL:
+ ('libcloud.storage.drivers.local', 'LocalStorageDriver')
}
Modified: libcloud/trunk/libcloud/storage/types.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/storage/types.py?rev=1406447&r1=1406446&r2=1406447&view=diff
==============================================================================
--- libcloud/trunk/libcloud/storage/types.py (original)
+++ libcloud/trunk/libcloud/storage/types.py Wed Nov 7 04:11:06 2012
@@ -42,6 +42,7 @@ class Provider(object):
@cvar GOOGLE_STORAGE Google Storage
@cvar S3_US_WEST_OREGON: Amazon S3 US West 2 (Oregon)
@cvar NIMBUS: Nimbus.io driver
+ @cvar LOCAL: Local storage driver
"""
DUMMY = 0
CLOUDFILES_US = 1
@@ -56,6 +57,7 @@ class Provider(object):
S3_US_WEST_OREGON = 10
CLOUDFILES_SWIFT = 11
NIMBUS = 12
+ LOCAL = 13
class ContainerError(LibcloudError):
Added: libcloud/trunk/libcloud/test/storage/test_local.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/storage/test_local.py?rev=1406447&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/storage/test_local.py (added)
+++ libcloud/trunk/libcloud/test/storage/test_local.py Wed Nov 7 04:11:06 2012
@@ -0,0 +1,314 @@
+# 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.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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 os
+import sys
+import shutil
+import unittest
+import tempfile
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.common.types import InvalidCredsError
+from libcloud.common.types import LibcloudError
+from libcloud.storage.base import Container, Object
+from libcloud.storage.types import ContainerDoesNotExistError
+from libcloud.storage.types import ContainerAlreadyExistsError
+from libcloud.storage.types import ContainerIsNotEmptyError
+from libcloud.storage.types import InvalidContainerNameError
+from libcloud.storage.types import ObjectDoesNotExistError
+from libcloud.storage.types import ObjectHashMismatchError
+from libcloud.storage.drivers.local import LocalStorageDriver
+from libcloud.storage.drivers.dummy import DummyIterator
+
+
+class LocalTests(unittest.TestCase):
+ driver_type = LocalStorageDriver
+
+ @classmethod
+ def create_driver(self):
+ self.key = tempfile.mkdtemp()
+ return self.driver_type((self.key, None))
+
+ def setUp(self):
+ self.driver = self.create_driver()
+
+ def tearDown(self):
+ shutil.rmtree(self.key)
+ self.key = None
+
+ def make_tmp_file(self):
+ tmppath = tempfile.mktemp()
+ tmpfile = open(tmppath, 'w')
+ tmpfile.write('blah' * 1024)
+ tmpfile.close()
+ return tmppath
+
+ def remove_tmp_file(self, tmppath):
+ os.unlink(tmppath)
+
+ def test_list_containers_empty(self):
+ containers = self.driver.list_containers()
+ self.assertEqual(len(containers), 0)
+
+ def test_containers_success(self):
+ self.driver.create_container('test1')
+ self.driver.create_container('test2')
+ containers = self.driver.list_containers()
+ self.assertEqual(len(containers), 2)
+
+ container = containers[1]
+
+ self.assertTrue('creation_time' in container.extra)
+ self.assertTrue('modify_time' in container.extra)
+ self.assertTrue('access_time' in container.extra)
+
+ objects = self.driver.list_container_objects(container=container)
+ self.assertEqual(len(objects), 0)
+
+ objects = container.list_objects()
+ self.assertEqual(len(objects), 0)
+
+ for container in containers:
+ self.driver.delete_container(container)
+
+ def test_objects_success(self):
+ tmppath = self.make_tmp_file()
+ tmpfile = open(tmppath)
+
+ container = self.driver.create_container('test3')
+ obj1 = container.upload_object(tmppath, 'object1')
+ obj2 = container.upload_object(tmppath, 'path/object2')
+ obj3 = container.upload_object(tmppath, 'path/to/object3')
+ obj4 = container.upload_object(tmppath, 'path/to/object4.ext')
+ obj5 = container.upload_object_via_stream(tmpfile, 'object5')
+
+ objects = self.driver.list_container_objects(container=container)
+ self.assertEqual(len(objects), 5)
+
+ for obj in objects:
+ self.assertEqual(obj.hash, None)
+ self.assertEqual(obj.size, 4096)
+ self.assertEqual(obj.container.name, 'test3')
+ self.assertTrue('creation_time' in obj.extra)
+ self.assertTrue('modify_time' in obj.extra)
+ self.assertTrue('access_time' in obj.extra)
+
+ obj1.delete()
+ obj2.delete()
+
+ objects = container.list_objects()
+ self.assertEqual(len(objects), 3)
+
+ container.delete_object(obj3)
+ container.delete_object(obj4)
+ container.delete_object(obj5)
+
+ objects = container.list_objects()
+ self.assertEqual(len(objects), 0)
+
+ container.delete()
+ tmpfile.close()
+ self.remove_tmp_file(tmppath)
+
+ def test_get_container_doesnt_exist(self):
+ try:
+ self.driver.get_container(container_name='container1')
+ except ContainerDoesNotExistError:
+ pass
+ else:
+ self.fail('Exception was not thrown')
+
+ def test_get_container_success(self):
+ self.driver.create_container('test4')
+ container = self.driver.get_container(container_name='test4')
+ self.assertTrue(container.name, 'test4')
+ container.delete()
+
+ def test_get_object_container_doesnt_exist(self):
+ try:
+ self.driver.get_object(container_name='test-inexistent',
+ object_name='test')
+ except ContainerDoesNotExistError:
+ pass
+ else:
+ self.fail('Exception was not thrown')
+
+ def test_get_object_success(self):
+ tmppath = self.make_tmp_file()
+ container = self.driver.create_container('test5')
+ container.upload_object(tmppath, 'test')
+
+ obj = self.driver.get_object(container_name='test5',
+ object_name='test')
+
+ self.assertEqual(obj.name, 'test')
+ self.assertEqual(obj.container.name, 'test5')
+ self.assertEqual(obj.size, 4096)
+ self.assertEqual(obj.hash, None)
+ self.assertTrue('creation_time' in obj.extra)
+ self.assertTrue('modify_time' in obj.extra)
+ self.assertTrue('access_time' in obj.extra)
+
+ obj.delete()
+ container.delete()
+ self.remove_tmp_file(tmppath)
+
+ def test_create_container_invalid_name(self):
+ try:
+ self.driver.create_container(container_name='new/container')
+ except InvalidContainerNameError:
+ pass
+ else:
+ self.fail('Exception was not thrown')
+
+ def test_create_container_already_exists(self):
+ container = self.driver.create_container(
+ container_name='new-container')
+ try:
+ self.driver.create_container(container_name='new-container')
+ except ContainerAlreadyExistsError:
+ pass
+ else:
+ self.fail('Exception was not thrown')
+
+ # success
+ self.driver.delete_container(container)
+
+ def test_create_container_success(self):
+ name = 'new_container'
+ container = self.driver.create_container(container_name=name)
+ self.assertEqual(container.name, name)
+ self.driver.delete_container(container)
+
+ def test_delete_container_doesnt_exist(self):
+ container = Container(name='new_container', extra=None,
+ driver=self.driver)
+ try:
+ self.driver.delete_container(container=container)
+ except ContainerDoesNotExistError:
+ pass
+ else:
+ self.fail('Exception was not thrown')
+
+ def test_delete_container_not_empty(self):
+ tmppath = self.make_tmp_file()
+ container = self.driver.create_container('test6')
+ obj = container.upload_object(tmppath, 'test')
+
+ try:
+ self.driver.delete_container(container=container)
+ except ContainerIsNotEmptyError:
+ pass
+ else:
+ self.fail('Exception was not thrown')
+
+ # success
+ obj.delete()
+ self.remove_tmp_file(tmppath)
+ self.assertTrue(self.driver.delete_container(container=container))
+
+ def test_delete_container_not_found(self):
+ container = Container(name='foo_bar_container', extra={},
+ driver=self.driver)
+ try:
+ self.driver.delete_container(container=container)
+ except ContainerDoesNotExistError:
+ pass
+ else:
+ self.fail('Container does not exist but an exception was not' +
+ 'thrown')
+
+ def test_delete_container_success(self):
+ container = self.driver.create_container('test7')
+ self.assertTrue(self.driver.delete_container(container=container))
+
+ def test_download_object_success(self):
+ tmppath = self.make_tmp_file()
+ container = self.driver.create_container('test6')
+ obj = container.upload_object(tmppath, 'test')
+
+ destination_path = tmppath + '.temp'
+ result = self.driver.download_object(obj=obj,
+ destination_path=destination_path,
+ overwrite_existing=False,
+ delete_on_failure=True)
+
+ self.assertTrue(result)
+
+ obj.delete()
+ container.delete()
+ self.remove_tmp_file(tmppath)
+ os.unlink(destination_path)
+
+ def test_download_object_and_overwrite(self):
+ tmppath = self.make_tmp_file()
+ container = self.driver.create_container('test6')
+ obj = container.upload_object(tmppath, 'test')
+
+ destination_path = tmppath + '.temp'
+ result = self.driver.download_object(obj=obj,
+ destination_path=destination_path,
+ overwrite_existing=False,
+ delete_on_failure=True)
+
+ self.assertTrue(result)
+
+ try:
+ self.driver.download_object(obj=obj,
+ destination_path=destination_path,
+ overwrite_existing=False,
+ delete_on_failure=True)
+ except LibcloudError:
+ pass
+ else:
+ self.fail('Exception was not thrown')
+
+ result = self.driver.download_object(obj=obj,
+ destination_path=destination_path,
+ overwrite_existing=True,
+ delete_on_failure=True)
+
+ self.assertTrue(result)
+
+ # success
+ obj.delete()
+ container.delete()
+ self.remove_tmp_file(tmppath)
+ os.unlink(destination_path)
+
+ def test_download_object_as_stream_success(self):
+ tmppath = self.make_tmp_file()
+ container = self.driver.create_container('test6')
+ obj = container.upload_object(tmppath, 'test')
+
+ stream = self.driver.download_object_as_stream(obj=obj,
+ chunk_size=1024)
+
+ self.assertTrue(hasattr(stream, '__iter__'))
+
+ data = ''
+ for buff in stream:
+ data += buff
+
+ self.assertTrue(len(data), 4096)
+
+ obj.delete()
+ container.delete()
+ self.remove_tmp_file(tmppath)
+
+
+if __name__ == '__main__':
+ sys.exit(unittest.main())
Modified: libcloud/trunk/setup.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/setup.py?rev=1406447&r1=1406446&r2=1406447&view=diff
==============================================================================
--- libcloud/trunk/setup.py (original)
+++ libcloud/trunk/setup.py Wed Nov 7 04:11:06 2012
@@ -130,6 +130,12 @@ class TestCommand(Command):
testfiles = []
for test_path in TEST_PATHS:
for t in glob(pjoin(self._dir, test_path, 'test_*.py')):
+ if sys.version_info >= (3, 2) and sys.version_info < (3, 3) \
+ and t.find('test_local'):
+ # Lockfile doesn't work with 3.2, temporary disable
+ # local_storage test with 3.2 until fixes have been
+ # submitted upstream
+ continue
testfiles.append('.'.join(
[test_path.replace('/', '.'), splitext(basename(t))[0]]))
Modified: libcloud/trunk/tox.ini
URL:
http://svn.apache.org/viewvc/libcloud/trunk/tox.ini?rev=1406447&r1=1406446&r2=1406447&view=diff
==============================================================================
--- libcloud/trunk/tox.ini (original)
+++ libcloud/trunk/tox.ini Wed Nov 7 04:11:06 2012
@@ -1,17 +1,22 @@
[tox]
+
envlist = py25,py26,py27,pypy,py32,py33
[testenv]
deps = mock
+ lockfile
commands = python setup.py test
[testenv:py25]
deps = mock
+ lockfile
ssl
simplejson
[testenv:py32]
deps = mock
+ lockfile
[testenv:py33]
deps = mock
+ lockfile