Przeglądaj źródła

Made bucket object operations uniform

Nuwan Goonasekera 8 lat temu
rodzic
commit
23fc4b0297

+ 16 - 2
cloudbridge/cloud/base/resources.py

@@ -15,6 +15,7 @@ from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
 from cloudbridge.cloud.interfaces.exceptions import WaitStateException
 from cloudbridge.cloud.interfaces.resources import AttachmentInfo
 from cloudbridge.cloud.interfaces.resources import Bucket
+from cloudbridge.cloud.interfaces.resources import BucketContainer
 from cloudbridge.cloud.interfaces.resources import BucketObject
 from cloudbridge.cloud.interfaces.resources import CloudResource
 from cloudbridge.cloud.interfaces.resources import FloatingIP
@@ -40,6 +41,7 @@ from cloudbridge.cloud.interfaces.resources import Subnet
 from cloudbridge.cloud.interfaces.resources import SubnetState
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMFirewallRule
+from cloudbridge.cloud.interfaces.resources import VMFirewallRuleContainer
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import Volume
 from cloudbridge.cloud.interfaces.resources import VolumeState
@@ -667,7 +669,8 @@ class BaseVMFirewall(BaseCloudResource, VMFirewall):
                                             self.id, self.name)
 
 
-class BaseVMFirewallRuleContainer(BasePageableObjectMixin):
+class BaseVMFirewallRuleContainer(BasePageableObjectMixin,
+                                  VMFirewallRuleContainer):
 
     def __init__(self, provider, firewall):
         self.__provider = provider
@@ -853,7 +856,7 @@ class BaseBucketObject(BaseCloudResource, BucketObject):
                                       self.name)
 
 
-class BaseBucket(BaseCloudResource, BasePageableObjectMixin, Bucket):
+class BaseBucket(BaseCloudResource, Bucket):
 
     # Regular expression for valid bucket names.
     # They, must match the following criteria: http://docs.aws.amazon.com/aws
@@ -891,6 +894,17 @@ class BaseBucket(BaseCloudResource, BasePageableObjectMixin, Bucket):
                                       self.name)
 
 
+class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
+
+    def __init__(self, provider, bucket):
+        self.__provider = provider
+        self.bucket = bucket
+
+    @property
+    def _provider(self):
+        return self.__provider
+
+
 class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
 
     CB_DEFAULT_NETWORK_NAME = os.environ.get('CB_DEFAULT_NETWORK_NAME',

+ 58 - 23
cloudbridge/cloud/interfaces/resources.py

@@ -1699,15 +1699,17 @@ class VMFirewall(CloudResource):
     @abstractproperty
     def rules(self):
         """
-        Get the list of rules for this VM firewall.
+        Get a container for the rules belonging to this VM firewall. This
+        object can be used for further operations on rules, such as get,
+        list, create etc.
 
-        :rtype: list of :class:`.VMFirewallRule`
-        :return: A list of VM firewall rule objects.
+        :rtype: An object of :class:`.VMFirewallRuleContainer`
+        :return: A VMFirewallRuleContainer for further operations
         """
         pass
 
 
-class VMFirewallRuleContainer(PageableObjectMixin, CloudResource):
+class VMFirewallRuleContainer(PageableObjectMixin):
     """
     Base interface for Firewall rules.
     """
@@ -2036,7 +2038,7 @@ class BucketObject(CloudResource):
         pass
 
 
-class Bucket(PageableObjectMixin, CloudResource):
+class Bucket(CloudResource):
 
     __metaclass__ = ABCMeta
 
@@ -2052,6 +2054,53 @@ class Bucket(PageableObjectMixin, CloudResource):
         """
         pass
 
+    @abstractproperty
+    def objects(self):
+        """
+        Get a container for the objects belonging to this Buckets. This
+        object can be used to iterate through bucket objects, as well as
+        perform further operations on buckets, such as get, list, create etc.
+
+        .. code-block:: python
+
+            # Show all objects in bucket
+            print(list(bucket.objects))
+
+            # Find an object by name
+            print(bucket.objects.find(name='my_obj.txt'))
+
+            # Get first page of bucket list
+            print(bucket.objects.list())
+
+            # Create a new object within this bucket
+            obj = bucket.objects.create('my_obj.txt')
+
+        :rtype: :class:`.BucketContainer`
+        :return: A BucketContainer for further operations.
+        """
+        pass
+
+    @abstractmethod
+    def delete(self, delete_contents=False):
+        """
+        Delete this bucket.
+
+        :type delete_contents: ``bool``
+        :param delete_contents: If ``True``, all objects within the bucket
+                                will be deleted.
+
+        :rtype: ``bool``
+        :return: ``True`` if successful.
+        """
+        pass
+
+
+class BucketContainer(PageableObjectMixin):
+    """
+    A container service for objects within a bucket
+    """
+    __metaclass__ = ABCMeta
+
     @abstractmethod
     def get(self, name):
         """
@@ -2079,7 +2128,7 @@ class Bucket(PageableObjectMixin, CloudResource):
         :type prefix: ``str``
         :param prefix: Prefix criteria by which to filter listed objects.
 
-        :rtype: :class:``.BucketObject``
+        :rtype: List of ``objects`` of :class:``.BucketObject``
         :return: List of all available BucketObjects within this bucket.
         """
         pass
@@ -2087,29 +2136,15 @@ class Bucket(PageableObjectMixin, CloudResource):
     @abstractmethod
     def find(self, name):
         """
-        Searches for an instance by a given name
+        Searches for an object by a given name
 
-        :rtype: List of ``object`` of :class:`.BucketObject`
+        :rtype: List of ``objects`` of :class:`.BucketObject`
         :return: A list of BucketObjects matching the supplied attributes.
         """
         pass
 
     @abstractmethod
-    def delete(self, delete_contents=False):
-        """
-        Delete this bucket.
-
-        :type delete_contents: ``bool``
-        :param delete_contents: If ``True``, all objects within the bucket
-                                will be deleted.
-
-        :rtype: ``bool``
-        :return: ``True`` if successful.
-        """
-        pass
-
-    @abstractmethod
-    def create_object(self, name):
+    def create(self, name):
         """
         Create a new object within this bucket.
 

+ 20 - 8
cloudbridge/cloud/providers/aws/resources.py

@@ -8,6 +8,7 @@ from botocore.exceptions import ClientError
 
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
+from cloudbridge.cloud.base.resources import BaseBucketContainer
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseInstance
@@ -776,6 +777,7 @@ class AWSBucket(BaseBucket):
     def __init__(self, provider, bucket):
         super(AWSBucket, self).__init__(provider)
         self._bucket = bucket
+        self._object_container = AWSBucketContainer(provider, self)
 
     @property
     def id(self):
@@ -785,9 +787,22 @@ class AWSBucket(BaseBucket):
     def name(self):
         return self._bucket.name
 
+    @property
+    def objects(self):
+        return self._object_container
+
+    def delete(self, delete_contents=False):
+        self._bucket.delete()
+
+
+class AWSBucketContainer(BaseBucketContainer):
+
+    def __init__(self, provider, bucket):
+        super(AWSBucketContainer, self).__init__(provider, bucket)
+
     def get(self, name):
         try:
-            obj = self._bucket.Object(name)
+            obj = self.bucket._bucket.Object(name)
             # load() throws an error if object does not exist
             obj.load()
             return AWSBucketObject(self._provider, obj)
@@ -796,9 +811,9 @@ class AWSBucket(BaseBucket):
 
     def list(self, limit=None, marker=None, prefix=None):
         if prefix:
-            boto_objs = self._bucket.objects.filter(Prefix=prefix)
+            boto_objs = self.bucket._bucket.objects.filter(Prefix=prefix)
         else:
-            boto_objs = self._bucket.objects.all()
+            boto_objs = self.bucket._bucket.objects.all()
         objects = [AWSBucketObject(self._provider, obj)
                    for obj in boto_objs]
 
@@ -811,11 +826,8 @@ class AWSBucket(BaseBucket):
         return ClientPagedResultList(self._provider, objects,
                                      limit=limit, marker=marker)
 
-    def delete(self, delete_contents=False):
-        self._bucket.delete()
-
-    def create_object(self, name):
-        obj = self._bucket.Object(name)
+    def create(self, name):
+        obj = self.bucket._bucket.Object(name)
         return AWSBucketObject(self._provider, obj)
 
 

+ 22 - 15
cloudbridge/cloud/providers/openstack/resources.py

@@ -8,6 +8,7 @@ import os
 
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
+from cloudbridge.cloud.base.resources import BaseBucketContainer
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseInstance
@@ -1253,6 +1254,7 @@ class OpenStackBucket(BaseBucket):
     def __init__(self, provider, bucket):
         super(OpenStackBucket, self).__init__(provider)
         self._bucket = bucket
+        self._object_container = OpenStackBucketContainer(provider, self)
 
     @property
     def id(self):
@@ -1260,11 +1262,21 @@ class OpenStackBucket(BaseBucket):
 
     @property
     def name(self):
-        """
-        Get this bucket's name.
-        """
         return self._bucket.get("name")
 
+    @property
+    def objects(self):
+        return self._object_container
+
+    def delete(self, delete_contents=False):
+        self._provider.swift.delete_container(self.name)
+
+
+class OpenStackBucketContainer(BaseBucketContainer):
+
+    def __init__(self, provider, bucket):
+        super(OpenStackBucketContainer, self).__init__(provider, bucket)
+
     def get(self, name):
         """
         Retrieve a given object from this bucket.
@@ -1274,9 +1286,9 @@ class OpenStackBucket(BaseBucket):
         return the first element.
         """
         _, object_list = self._provider.swift.get_container(
-            self.name, prefix=name)
+            self.bucket.name, prefix=name)
         if object_list:
-            return OpenStackBucketObject(self._provider, self,
+            return OpenStackBucketObject(self._provider, self.bucket,
                                          object_list[0])
         else:
             return None
@@ -1289,10 +1301,11 @@ class OpenStackBucket(BaseBucket):
         :return: List of all available BucketObjects within this bucket.
         """
         _, object_list = self._provider.swift.get_container(
-            self.name, limit=oshelpers.os_result_limit(self._provider, limit),
+            self.bucket.name,
+            limit=oshelpers.os_result_limit(self._provider, limit),
             marker=marker, prefix=prefix)
         cb_objects = [OpenStackBucketObject(
-            self._provider, self, obj) for obj in object_list]
+            self._provider, self.bucket, obj) for obj in object_list]
 
         return oshelpers.to_server_paged_list(
             self._provider,
@@ -1304,12 +1317,6 @@ class OpenStackBucket(BaseBucket):
         return ClientPagedResultList(self._provider, objects,
                                      limit=limit, marker=marker)
 
-    def delete(self, delete_contents=False):
-        """
-        Delete this bucket.
-        """
-        self._provider.swift.delete_container(self.name)
-
-    def create_object(self, object_name):
-        self._provider.swift.put_object(self.name, object_name, None)
+    def create(self, object_name):
+        self._provider.swift.put_object(self.bucket.name, object_name, None)
         return self.get(object_name)

+ 14 - 13
test/test_object_store_service.py

@@ -58,7 +58,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
         test_bucket = None
 
         def create_bucket_obj(name):
-            obj = test_bucket.create_object(name)
+            obj = test_bucket.objects.create(name)
             # 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
@@ -73,7 +73,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
             name = "cb-crudbucketobj-{0}".format(uuid.uuid4())
             test_bucket = self.provider.storage.buckets.create(name)
 
-            sit.check_crud(self, test_bucket, BucketObject,
+            sit.check_crud(self, test_bucket.objects, BucketObject,
                            "cb_bucketobj", create_bucket_obj,
                            cleanup_bucket_obj, skip_name_check=True)
 
@@ -88,13 +88,13 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
         test_bucket = self.provider.storage.buckets.create(name)
 
         # ensure that the bucket is empty
-        objects = test_bucket.list()
+        objects = test_bucket.objects.list()
         self.assertEqual([], objects)
 
         with helpers.cleanup_action(lambda: test_bucket.delete()):
             obj_name_prefix = "hello"
             obj_name = obj_name_prefix + "_world.txt"
-            obj = test_bucket.create_object(obj_name)
+            obj = test_bucket.objects.create(obj_name)
 
             with helpers.cleanup_action(lambda: obj.delete()):
                 # TODO: This is wrong. We shouldn't have to have a separate
@@ -102,7 +102,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
                 # the content. Maybe the create_object method should accept
                 # the file content as a parameter.
                 obj.upload("dummy content")
-                objs = test_bucket.list()
+                objs = test_bucket.objects.list()
 
                 self.assertTrue(
                     isinstance(objs[0].size, int),
@@ -115,22 +115,23 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
                     .format(objs[0].last_modified))
 
                 # check iteration
-                iter_objs = list(test_bucket)
+                iter_objs = list(test_bucket.objects)
                 self.assertListEqual(iter_objs, objs)
 
-                obj_too = test_bucket.get(obj_name)
+                obj_too = test_bucket.objects.get(obj_name)
                 self.assertTrue(
                     isinstance(obj_too, BucketObject),
                     "Did not get object {0} of expected type.".format(obj_too))
 
-                prefix_filtered_list = test_bucket.list(prefix=obj_name_prefix)
+                prefix_filtered_list = test_bucket.objects.list(
+                    prefix=obj_name_prefix)
                 self.assertTrue(
                     len(objs) == len(prefix_filtered_list) == 1,
                     'The number of objects returned by list function, '
                     'with and without a prefix, are expected to be equal, '
                     'but its detected otherwise.')
 
-            sit.check_delete(self, test_bucket, obj)
+            sit.check_delete(self, test_bucket.objects, obj)
 
     @helpers.skipIfNoService(['storage.buckets'])
     def test_upload_download_bucket_content(self):
@@ -139,7 +140,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
 
         with helpers.cleanup_action(lambda: test_bucket.delete()):
             obj_name = "hello_upload_download.txt"
-            obj = test_bucket.create_object(obj_name)
+            obj = test_bucket.objects.create(obj_name)
 
             with helpers.cleanup_action(lambda: obj.delete()):
                 content = b"Hello World. Here's some content."
@@ -165,7 +166,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
 
         with helpers.cleanup_action(lambda: test_bucket.delete()):
             obj_name = "hello_upload_download.txt"
-            obj = test_bucket.create_object(obj_name)
+            obj = test_bucket.objects.create(obj_name)
 
             with helpers.cleanup_action(lambda: obj.delete()):
                 content = b"Hello World. Generate a url."
@@ -187,7 +188,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
 
         with helpers.cleanup_action(lambda: test_bucket.delete()):
             obj_name = "hello_upload_download.txt"
-            obj = test_bucket.create_object(obj_name)
+            obj = test_bucket.objects.create(obj_name)
 
             with helpers.cleanup_action(lambda: obj.delete()):
                 test_file = os.path.join(
@@ -216,7 +217,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
             bucket_name = "cbtestbucketlargeobjs-{0}".format(uuid.uuid4())
             test_bucket = self.provider.storage.buckets.create(bucket_name)
             with helpers.cleanup_action(lambda: test_bucket.delete()):
-                test_obj = test_bucket.create_object(file_name)
+                test_obj = test_bucket.objects.create(file_name)
                 with helpers.cleanup_action(lambda: test_obj.delete()):
                     file_uploaded = test_obj.upload_from_file(six_gig_file)
                     self.assertTrue(file_uploaded, "Could not upload object?")