Преглед на файлове

Added implementation of block_store service for AWS + tests.

nuwan_ag преди 10 години
родител
ревизия
e34d34d61e

+ 2 - 1
cloudbridge/providers/aws/impl.py

@@ -12,6 +12,7 @@ from cloudbridge.providers.base import BaseCloudProvider
 from .services import AWSBlockStoreService
 from .services import AWSComputeService
 from .services import AWSImageService
+from .services import AWSObjectStoreService
 from .services import AWSSecurityService
 
 
@@ -42,7 +43,7 @@ class AWSCloudProviderV1(BaseCloudProvider):
         self._images = AWSImageService(self)
         self._security = AWSSecurityService(self)
         self._block_store = AWSBlockStoreService(self)
-        self._object_store = None  # AWSObjectStore(self)
+        self._object_store = AWSObjectStoreService(self)
 
     @property
     def compute(self):

+ 90 - 2
cloudbridge/providers/aws/resources.py

@@ -1,8 +1,9 @@
 """
 DataTypes used by this provider
 """
-
+import shutil
 from boto.exception import EC2ResponseError
+from boto.s3.key import Key
 
 from cloudbridge.providers.base import BaseInstance
 from cloudbridge.providers.base import BaseKeyPair
@@ -10,6 +11,8 @@ from cloudbridge.providers.base import BaseMachineImage
 from cloudbridge.providers.base import BaseSecurityGroup
 from cloudbridge.providers.base import BaseSnapshot
 from cloudbridge.providers.base import BaseVolume
+from cloudbridge.providers.interfaces import Container
+from cloudbridge.providers.interfaces import ContainerObject
 from cloudbridge.providers.interfaces import InstanceState
 from cloudbridge.providers.interfaces import InstanceType
 from cloudbridge.providers.interfaces import MachineImageState
@@ -411,4 +414,89 @@ class AWSSnapshot(BaseSnapshot):
         self._snapshot.delete()
 
     def __repr__(self):
-        return "<CB-AWSSnapshot: {0} ({1}>".format(self.snapshot_id, self.name)
+        return "<CB-AWSSnapshot: {0} ({1})>".format(self.snapshot_id,
+                                                    self.name)
+
+
+class AWSContainerObject(ContainerObject):
+
+    def __init__(self, provider, key):
+        self.provider = provider
+        self._key = key
+
+    @property
+    def name(self):
+        """
+        Get this object's name.
+        """
+        return self._key.name
+
+    def download(self, target_stream):
+        """
+        Download this object and write its
+        contents to the target_stream.
+        """
+        shutil.copyfileobj(self._key, target_stream)
+
+    def upload(self, data):
+        """
+        Set the contents of this object to the data read from the source
+        string.
+        """
+        self._key.set_contents_from_string(data)
+
+    def delete(self):
+        """
+        Delete this object.
+
+        :rtype: bool
+        :return: True if successful
+        """
+        self._key.delete()
+
+    def __repr__(self):
+        return "<CB-AWSContainerObject: {0}>".format(self.name)
+
+
+class AWSContainer(Container):
+
+    def __init__(self, provider, bucket):
+        self.provider = provider
+        self._bucket = bucket
+
+    @property
+    def name(self):
+        """
+        Get this container's name.
+        """
+        return self._bucket.name
+
+    def get(self, key):
+        """
+        Retrieve a given object from this container.
+        """
+        raise NotImplementedError(
+            'Container.list not implemented by this provider')
+
+    def list(self):
+        """
+        List all objects within this container.
+
+        :rtype: ContainerObject
+        :return: List of all available ContainerObjects within this container
+        """
+        objects = self._bucket.list()
+        return [AWSContainerObject(self.provider, obj) for obj in objects]
+
+    def delete(self, delete_contents=False):
+        """
+        Delete this container.
+        """
+        self._bucket.delete()
+
+    def create_object(self, name):
+        key = Key(self._bucket, name)
+        return AWSContainerObject(self.provider, key)
+
+    def __repr__(self):
+        return "<CB-AWSContainer: {0}>".format(self.name)

+ 42 - 0
cloudbridge/providers/aws/services.py

@@ -10,12 +10,14 @@ from cloudbridge.providers.interfaces import ImageService
 from cloudbridge.providers.interfaces import InstanceType
 from cloudbridge.providers.interfaces import KeyPair
 from cloudbridge.providers.interfaces import MachineImage
+from cloudbridge.providers.interfaces import ObjectStoreService
 from cloudbridge.providers.interfaces import PlacementZone
 from cloudbridge.providers.interfaces import SecurityGroup
 from cloudbridge.providers.interfaces import SecurityService
 from cloudbridge.providers.interfaces import SnapshotService
 from cloudbridge.providers.interfaces import VolumeService
 
+from .resources import AWSContainer
 from .resources import AWSInstance
 from .resources import AWSMachineImage
 from .resources import AWSSnapshot
@@ -110,6 +112,46 @@ class AWSVolumeService(VolumeService):
         return cb_vol
 
 
+class AWSObjectStoreService(ObjectStoreService):
+
+    def __init__(self, provider):
+        self.provider = provider
+
+    def get_container(self, container_id):
+        """
+        Returns a container given its id. Returns None if the container
+        does not exist.
+        """
+        bucket = self.provider.s3_conn.lookup(container_id)
+        if bucket:
+            return AWSContainer(self.provider, bucket)
+        else:
+            return None
+
+    def find_container(self, name):
+        """
+        Searches for a container by a given list of attributes
+        """
+        raise NotImplementedError(
+            'find_container not implemented by this provider')
+
+    def list_containers(self):
+        """
+        List all containers.
+        """
+        buckets = self.provider.s3_conn.get_all_buckets()
+        return [AWSContainer(self.provider, bucket) for bucket in buckets]
+
+    def create_container(self, name, location=None):
+        """
+        Create a new container.
+        """
+        bucket = self.provider.s3_conn.create_bucket(
+            name,
+            location=location if location else '')
+        return AWSContainer(self.provider, bucket)
+
+
 class AWSSnapshotService(SnapshotService):
 
     def __init__(self, provider):

+ 2 - 0
cloudbridge/providers/interfaces/__init__.py

@@ -3,6 +3,8 @@ Public interface exports
 """
 from .impl import CloudProvider
 from .resources import CloudProviderServiceType
+from .resources import Container
+from .resources import ContainerObject
 from .resources import Instance
 from .resources import InstanceState
 from .resources import InstanceType

+ 101 - 0
cloudbridge/providers/interfaces/resources.py

@@ -695,3 +695,104 @@ class SecurityGroup(object):
         """
         raise NotImplementedError(
             'add_rule not implemented by this provider')
+
+
+class ContainerObject(object):
+
+    """
+    Represents an object stored within a container.
+    """
+
+    @property
+    def name(self):
+        """
+        Get this object's name.
+
+        :rtype: ``str``
+        :return: Name of this object as returned by the cloud middleware.
+        """
+        raise NotImplementedError(
+            'ContainerObject.name not implemented by this provider')
+
+    def download(self, target_stream):
+        """
+        Download this object and write its
+        contents to the target_stream.
+
+        :rtype: bool
+        :return: True if successful
+        """
+        raise NotImplementedError(
+            'ContainerObject.download not implemented by this provider')
+
+    def upload(self, source_stream):
+        """
+        Set the contents of this object to the data read from the source
+        stream.
+
+        :rtype: bool
+        :return: True if successful
+        """
+        raise NotImplementedError(
+            'ContainerObject.upload not implemented by this provider')
+
+    def delete(self):
+        """
+        Delete this object.
+
+        :rtype: bool
+        :return: True if successful
+        """
+        raise NotImplementedError(
+            'ContainerObject.delete not implemented by this provider')
+
+
+class Container(object):
+
+    @property
+    def name(self):
+        """
+        Get this container's name.
+
+        :rtype: ``str``
+        :return: Name of this container as returned by the cloud middleware.
+        """
+        raise NotImplementedError(
+            'Container.name not implemented by this provider')
+
+    def get(self, key):
+        """
+        Retrieve a given object from this container.
+
+        :type key: ``str``
+        :param key: the identifier of the object to retrieve
+
+        :rtype: ContainerObject
+        :return: The ContainerObject or None if it cannot be found.
+        """
+        raise NotImplementedError(
+            'Container.list not implemented by this provider')
+
+    def list(self):
+        """
+        List all objects within this container.
+
+        :rtype: ContainerObject
+        :return: List of all available ContainerObjects within this container
+        """
+        raise NotImplementedError(
+            'Container.list not implemented by this provider')
+
+    def delete(self, delete_contents=False):
+        """
+        Delete this container.
+
+        :type delete_contents: ``bool``
+        :param delete_contents: If True, all objects within the container will
+        be deleted.
+
+        :rtype: bool
+        :return: True if successful
+        """
+        raise NotImplementedError(
+            'Container.delete not implemented by this provider')

+ 8 - 1
cloudbridge/providers/interfaces/services.py

@@ -354,9 +354,16 @@ class ObjectStoreService(ProviderService):
         raise NotImplementedError(
             'list_containers not implemented by this provider')
 
-    def create_container(self):
+    def create_container(self, name, location=None):
         """
         Create a new container.
+
+        :type name: str
+        :param name: The name of this container
+
+        :type location: ``object`` of :class:`.Region`
+        :param location: The region in which to place this container
+
         :return:  a Container object
         :rtype: ``object`` of :class:`.Container`
         """

+ 4 - 1
test/__init__.py

@@ -32,6 +32,8 @@ from test.test_provider_block_store_service import \
     ProviderBlockStoreServiceTestCase
 from test.test_provider_image_service import ProviderImageServiceTestCase
 from test.test_provider_interface import ProviderInterfaceTestCase
+from test.test_provider_object_store_service import \
+    ProviderObjectStoreServiceTestCase
 from test.test_provider_security_service import ProviderSecurityServiceTestCase
 
 
@@ -40,7 +42,8 @@ PROVIDER_TESTS = [
     ProviderSecurityServiceTestCase,
     ProviderComputeServiceTestCase,
     ProviderImageServiceTestCase,
-    ProviderBlockStoreServiceTestCase
+    ProviderBlockStoreServiceTestCase,
+    ProviderObjectStoreServiceTestCase
 ]
 
 

+ 82 - 0
test/test_provider_object_store_service.py

@@ -0,0 +1,82 @@
+import StringIO
+import uuid
+
+from test.helpers import ProviderTestBase
+import test.helpers as helpers
+
+
+class ProviderObjectStoreServiceTestCase(ProviderTestBase):
+
+    def __init__(self, methodName, provider):
+        super(ProviderObjectStoreServiceTestCase, self).__init__(
+            methodName=methodName, provider=provider)
+
+    def test_crud_container(self):
+        """
+        Create a new container, check whether the expected values are set,
+        and delete it
+        """
+        name = "cbtestcreatecontainer-{0}".format(uuid.uuid4())
+        test_container = self.provider.object_store.create_container(name)
+        with helpers.exception_action(lambda x: test_container.delete()):
+            containers = self.provider.object_store.list_containers()
+            found_containers = [c for c in containers if c.name == name]
+            self.assertTrue(
+                len(found_containers) == 1,
+                "List containers does not return the expected container %s" %
+                name)
+            test_container.delete()
+
+    def test_crud_container_objects(self):
+        """
+        Create a new container, upload some contents into the container, and
+        check whether list properly detects the new content.
+        Delete everything afterwards.
+        """
+        name = "cbtestcontainerobjs-{0}".format(uuid.uuid4())
+        test_container = self.provider.object_store.create_container(name)
+
+        # ensure that the container is empty
+        objects = test_container.list()
+        self.assertEqual([], objects)
+
+        with helpers.exception_action(lambda x: test_container.delete()):
+            obj_name = "hello_world.txt"
+            obj = test_container.create_object(obj_name)
+
+            with helpers.exception_action(lambda x: obj.delete()):
+                # TODO: This is wrong. We shouldn't have to have a separate
+                # call to upload some content before being able to delete
+                # the content. Maybe the create_object method should accept
+                # the file content as a parameter.
+                obj.upload("dummy content")
+                objs = test_container.list()
+                found_objs = [o for o in objs if o.name == obj_name]
+                print "FOUND: ", found_objs
+                self.assertTrue(
+                    len(found_objs) == 1,
+                    "List container objects does not return the expected"
+                    " object %s" % obj_name)
+                obj.delete()
+            test_container.delete()
+
+    def test_upload_download_container_content(self):
+
+        name = "cbtestcontainerobjs-{0}".format(uuid.uuid4())
+        test_container = self.provider.object_store.create_container(name)
+
+        with helpers.exception_action(lambda x: test_container.delete()):
+            obj_name = "hello_upload_download.txt"
+            obj = test_container.create_object(obj_name)
+
+            with helpers.exception_action(lambda x: obj.delete()):
+                content = "Hello World. Here's some content"
+                # TODO: Upload and download methods accept different parameter
+                # types. Need to make this consistent - possibly provider
+                # multiple methods like upload_from_file, from_stream etc.
+                obj.upload(content)
+                target_stream = StringIO.StringIO()
+                obj.download(target_stream)
+                self.assertEqual(target_stream.getvalue(), content)
+                obj.delete()
+            test_container.delete()