Przeglądaj źródła

Added implementation of block_store service for AWS + tests.

nuwan_ag 10 lat temu
rodzic
commit
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 AWSBlockStoreService
 from .services import AWSComputeService
 from .services import AWSComputeService
 from .services import AWSImageService
 from .services import AWSImageService
+from .services import AWSObjectStoreService
 from .services import AWSSecurityService
 from .services import AWSSecurityService
 
 
 
 
@@ -42,7 +43,7 @@ class AWSCloudProviderV1(BaseCloudProvider):
         self._images = AWSImageService(self)
         self._images = AWSImageService(self)
         self._security = AWSSecurityService(self)
         self._security = AWSSecurityService(self)
         self._block_store = AWSBlockStoreService(self)
         self._block_store = AWSBlockStoreService(self)
-        self._object_store = None  # AWSObjectStore(self)
+        self._object_store = AWSObjectStoreService(self)
 
 
     @property
     @property
     def compute(self):
     def compute(self):

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

@@ -1,8 +1,9 @@
 """
 """
 DataTypes used by this provider
 DataTypes used by this provider
 """
 """
-
+import shutil
 from boto.exception import EC2ResponseError
 from boto.exception import EC2ResponseError
+from boto.s3.key import Key
 
 
 from cloudbridge.providers.base import BaseInstance
 from cloudbridge.providers.base import BaseInstance
 from cloudbridge.providers.base import BaseKeyPair
 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 BaseSecurityGroup
 from cloudbridge.providers.base import BaseSnapshot
 from cloudbridge.providers.base import BaseSnapshot
 from cloudbridge.providers.base import BaseVolume
 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 InstanceState
 from cloudbridge.providers.interfaces import InstanceType
 from cloudbridge.providers.interfaces import InstanceType
 from cloudbridge.providers.interfaces import MachineImageState
 from cloudbridge.providers.interfaces import MachineImageState
@@ -411,4 +414,89 @@ class AWSSnapshot(BaseSnapshot):
         self._snapshot.delete()
         self._snapshot.delete()
 
 
     def __repr__(self):
     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 InstanceType
 from cloudbridge.providers.interfaces import KeyPair
 from cloudbridge.providers.interfaces import KeyPair
 from cloudbridge.providers.interfaces import MachineImage
 from cloudbridge.providers.interfaces import MachineImage
+from cloudbridge.providers.interfaces import ObjectStoreService
 from cloudbridge.providers.interfaces import PlacementZone
 from cloudbridge.providers.interfaces import PlacementZone
 from cloudbridge.providers.interfaces import SecurityGroup
 from cloudbridge.providers.interfaces import SecurityGroup
 from cloudbridge.providers.interfaces import SecurityService
 from cloudbridge.providers.interfaces import SecurityService
 from cloudbridge.providers.interfaces import SnapshotService
 from cloudbridge.providers.interfaces import SnapshotService
 from cloudbridge.providers.interfaces import VolumeService
 from cloudbridge.providers.interfaces import VolumeService
 
 
+from .resources import AWSContainer
 from .resources import AWSInstance
 from .resources import AWSInstance
 from .resources import AWSMachineImage
 from .resources import AWSMachineImage
 from .resources import AWSSnapshot
 from .resources import AWSSnapshot
@@ -110,6 +112,46 @@ class AWSVolumeService(VolumeService):
         return cb_vol
         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):
 class AWSSnapshotService(SnapshotService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):

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

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

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

@@ -695,3 +695,104 @@ class SecurityGroup(object):
         """
         """
         raise NotImplementedError(
         raise NotImplementedError(
             'add_rule not implemented by this provider')
             '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(
         raise NotImplementedError(
             'list_containers not implemented by this provider')
             'list_containers not implemented by this provider')
 
 
-    def create_container(self):
+    def create_container(self, name, location=None):
         """
         """
         Create a new container.
         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
         :return:  a Container object
         :rtype: ``object`` of :class:`.Container`
         :rtype: ``object`` of :class:`.Container`
         """
         """

+ 4 - 1
test/__init__.py

@@ -32,6 +32,8 @@ from test.test_provider_block_store_service import \
     ProviderBlockStoreServiceTestCase
     ProviderBlockStoreServiceTestCase
 from test.test_provider_image_service import ProviderImageServiceTestCase
 from test.test_provider_image_service import ProviderImageServiceTestCase
 from test.test_provider_interface import ProviderInterfaceTestCase
 from test.test_provider_interface import ProviderInterfaceTestCase
+from test.test_provider_object_store_service import \
+    ProviderObjectStoreServiceTestCase
 from test.test_provider_security_service import ProviderSecurityServiceTestCase
 from test.test_provider_security_service import ProviderSecurityServiceTestCase
 
 
 
 
@@ -40,7 +42,8 @@ PROVIDER_TESTS = [
     ProviderSecurityServiceTestCase,
     ProviderSecurityServiceTestCase,
     ProviderComputeServiceTestCase,
     ProviderComputeServiceTestCase,
     ProviderImageServiceTestCase,
     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()