Return-Path: X-Original-To: apmail-libcloud-commits-archive@www.apache.org Delivered-To: apmail-libcloud-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 775A4DBA8 for ; Wed, 7 Nov 2012 04:11:37 +0000 (UTC) Received: (qmail 67759 invoked by uid 500); 7 Nov 2012 04:11:37 -0000 Delivered-To: apmail-libcloud-commits-archive@libcloud.apache.org Received: (qmail 67511 invoked by uid 500); 7 Nov 2012 04:11:32 -0000 Mailing-List: contact commits-help@libcloud.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@libcloud.apache.org Delivered-To: mailing list commits@libcloud.apache.org Received: (qmail 67442 invoked by uid 99); 7 Nov 2012 04:11:31 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 07 Nov 2012 04:11:31 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 07 Nov 2012 04:11:28 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id D33712388A5B; Wed, 7 Nov 2012 04:11:08 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1406447 - in /libcloud/trunk: CHANGES libcloud/storage/drivers/local.py libcloud/storage/providers.py libcloud/storage/types.py libcloud/test/storage/test_local.py setup.py tox.ini Date: Wed, 07 Nov 2012 04:11:07 -0000 To: commits@libcloud.apache.org From: tomaz@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20121107041108.D33712388A5B@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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