Sfoglia il codice sorgente

Make find method signature consistent

Closes: https://github.com/gvlproject/cloudbridge/issues/56
Nuwan Goonasekera 8 anni fa
parent
commit
9ca6534b34

+ 31 - 0
cloudbridge/cloud/base/helpers.py

@@ -21,3 +21,34 @@ def generate_key_pair():
         crypt_serialization.Encoding.OpenSSH,
         crypt_serialization.PublicFormat.OpenSSH).decode('utf-8')
     return public_key, private_key
+
+
+def filter_by(prop_name, kwargs, objs):
+    """
+    Utility method for filtering a list of objects by a property.
+    If the given property has a non empty value in kwargs, then
+    the list of objs is filtered by that value. Otherwise, the
+    list of objs is returned as is.
+    """
+    prop_val = kwargs.pop(prop_name, None)
+    if prop_val:
+        match = (o for o in objs if getattr(o, prop_name) == prop_val)
+        return match
+    return objs
+
+
+def generic_find(filter_names, kwargs, objs):
+    """
+    Utility method for filtering a list of objects by a list of filters.
+    """
+    matches = objs
+    for name in filter_names:
+        matches = filter_by(name, kwargs, matches)
+
+    # All kwargs should have been popped at this time.
+    if len(kwargs) > 0:
+        raise TypeError(
+            "Unrecognised parameters for search: %s. Supported attributes: %s"
+            % (kwargs, filter_names))
+
+    return matches

+ 12 - 43
cloudbridge/cloud/base/resources.py

@@ -9,6 +9,7 @@ import re
 import shutil
 import time
 
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.interfaces.exceptions \
     import InvalidConfigurationException
 from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
@@ -713,28 +714,11 @@ class BaseVMFirewallRuleContainer(BasePageableObjectMixin,
             return None
 
     def find(self, **kwargs):
-        matches = self
-
-        def filter_by(prop_name, rules):
-            prop_val = kwargs.pop(prop_name, None)
-            if prop_val:
-                match = [r for r in rules if getattr(r, prop_name) == prop_val]
-                return match
-            return rules
-
-        matches = filter_by('name', matches)
-        matches = filter_by('direction', matches)
-        matches = filter_by('protocol', matches)
-        matches = filter_by('from_port', matches)
-        matches = filter_by('to_port', matches)
-        matches = filter_by('cidr', matches)
-        matches = filter_by('src_dest_fw', matches)
-        matches = filter_by('src_dest_fw_id', matches)
-        limit = kwargs.pop('limit', None)
-        marker = kwargs.pop('marker', None)
-
-        return ClientPagedResultList(self._provider, matches,
-                                     limit=limit, marker=marker)
+        obj_list = self
+        filters = ['name', 'direction', 'protocol', 'from_port', 'to_port',
+                   'cidr', 'src_dest_fw', 'src_dest_fw_id']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
 
     def delete(self, rule_id):
         rule = self.get(rule_id)
@@ -863,10 +847,6 @@ class BaseBucketObject(BaseCloudResource, BucketObject):
                 "data.html#object-key-guidelines" % name)
 
     def save_content(self, target_stream):
-        """
-        Download this object and write its
-        contents to the target_stream.
-        """
         shutil.copyfileobj(self.iter_content(), target_stream)
 
     def __eq__(self, other):
@@ -988,11 +968,9 @@ class BaseSubnet(BaseCloudResource, BaseObjectLifeCycleMixin, Subnet):
             interval=interval)
 
 
-class BaseFloatingIPContainer(BaseCloudResource, FloatingIPContainer,
-                              BasePageableObjectMixin):
+class BaseFloatingIPContainer(FloatingIPContainer, BasePageableObjectMixin):
 
     def __init__(self, provider, gateway):
-        # super(BaseFloatingIPContainer, self).__init__(provider)
         self.__provider = provider
         self.gateway = gateway
 
@@ -1001,17 +979,10 @@ class BaseFloatingIPContainer(BaseCloudResource, FloatingIPContainer,
         return self.__provider
 
     def find(self, **kwargs):
-        if 'name' in kwargs:
-            name = kwargs.get('name')
-            log.info("Searching for FloatingIPContainer with the "
-                     "name: %s...", name)
-            if name:
-                return [fip for fip in self if fip.name == name]
-        else:
-            log.exception("TypeError exception raised. Invalid parameters "
-                          "used for search.")
-            raise TypeError(
-                "Invalid parameters for search. Supported attributes: {name}")
+        obj_list = self
+        filters = ['name', 'public_ip']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
 
     def delete(self, fip_id):
         floating_ip = self.get(fip_id)
@@ -1026,9 +997,7 @@ class BaseFloatingIP(BaseCloudResource, BaseObjectLifeCycleMixin, FloatingIP):
 
     @property
     def name(self):
-        """
-        VM firewall rules don't support names, so pass
-        """
+        # VM firewall rules don't support names, so pass
         return self.public_ip
 
     @property

+ 15 - 20
cloudbridge/cloud/base/services.py

@@ -3,6 +3,7 @@ Base implementation for services available through a provider
 """
 import logging
 
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.interfaces.resources import Router
 from cloudbridge.cloud.interfaces.services import BucketService
 from cloudbridge.cloud.interfaces.services import CloudService
@@ -24,6 +25,7 @@ from cloudbridge.cloud.interfaces.services import VMTypeService
 from cloudbridge.cloud.interfaces.services import VolumeService
 
 from .resources import BasePageableObjectMixin
+from .resources import ClientPagedResultList
 
 log = logging.getLogger(__name__)
 
@@ -127,15 +129,10 @@ class BaseVMTypeService(
         return next(vm_type, None)
 
     def find(self, **kwargs):
-        name = kwargs.get('name')
-        log.info("Searching for VMTypeService with the: name %s ...", name)
-        if name:
-            return [itype for itype in self if itype.name == name]
-        else:
-            log.exception("TypeError exception raised. Invalid parameters "
-                          "used for search.")
-            raise TypeError(
-                "Invalid parameters for search. Supported attributes: {name}")
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
 
 
 class BaseInstanceService(
@@ -151,8 +148,11 @@ class BaseRegionService(
     def __init__(self, provider):
         super(BaseRegionService, self).__init__(provider)
 
-    def find(self, name):
-        return [region for region in self if region.name == name]
+    def find(self, **kwargs):
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
 
 
 class BaseNetworkingService(NetworkingService, BaseCloudService):
@@ -186,15 +186,10 @@ class BaseSubnetService(
         super(BaseSubnetService, self).__init__(provider)
 
     def find(self, **kwargs):
-        name = kwargs.get('name')
-        log.info("Searching for SubnetService with the name: %s ...", name)
-        if name:
-            return [subnet for subnet in self if subnet.name == name]
-        else:
-            log.exception("TypeError exception raised. Invalid parameters "
-                          "used for search.")
-            raise TypeError(
-                "Invalid parameters for search. Supported attributes: {name}")
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
 
 
 class BaseRouterService(

+ 15 - 3
cloudbridge/cloud/interfaces/resources.py

@@ -979,10 +979,20 @@ class FloatingIPContainer(PageableObjectMixin):
         pass
 
     @abstractmethod
-    def find(self, name):
+    def find(self, **kwargs):
         """
         Searches for a FloatingIP by a given list of attributes.
 
+        Supported attributes: name, public_ip
+
+        Example:
+
+        .. code-block:: python
+
+            fip = provider.networking.gateways.get('id').floating_ips.find(
+                        public_ip='public_ip')
+
+
         :rtype: List of ``object`` of :class:`.FloatingIP`
         :return: A list of FloatingIP objects matching the supplied attributes.
         """
@@ -2219,9 +2229,11 @@ class BucketContainer(PageableObjectMixin):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
-        Searches for an object by a given name
+        Searches for an object by a given list of attributes.
+
+        Supported attributes: name
 
         :rtype: List of ``objects`` of :class:`.BucketObject`
         :return: A list of BucketObjects matching the supplied attributes.

+ 35 - 24
cloudbridge/cloud/interfaces/services.py

@@ -160,26 +160,15 @@ class InstanceService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Searches for an instance by a given list of attributes.
 
+        Supported attributes: name
+
         :type  name: ``str``
         :param name: The name to search for
 
-        :type  limit: ``int``
-        :param limit: The maximum number of objects to return. Note that the
-                      maximum is not guaranteed to be honoured, and a lower
-                      maximum may be enforced depending on the provider. In
-                      such a case, the returned ResultList's is_truncated
-                      property can be used to determine whether more records
-                      are available.
-
-        :type  marker: ``str``
-        :param marker: The marker is an opaque identifier used to assist
-                       in paging through very long lists of objects. It is
-                       returned on each invocation of the list method.
-
         :rtype: List of ``object`` of :class:`.Instance`
         :return: A list of Instance objects matching the supplied attributes.
         """
@@ -318,10 +307,12 @@ class VolumeService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Searches for a volume by a given list of attributes.
 
+        Supported attributes: name
+
         :rtype: ``object`` of :class:`.Volume`
         :return: a Volume object or ``None`` if not found.
         """
@@ -383,10 +374,12 @@ class SnapshotService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Searches for a snapshot by a given list of attributes.
 
+        Supported attributes: name
+
         :rtype: list of :class:`.Snapshot`
         :return: a Snapshot object or an empty list if none found.
         """
@@ -519,10 +512,12 @@ class ImageService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Searches for an image by a given list of attributes
 
+        Supported attributes: name
+
         :rtype: ``object`` of :class:`.Image`
         :return:  an Image instance
         """
@@ -628,10 +623,12 @@ class NetworkService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Searches for a network by a given list of attributes.
 
+        Supported attributes: name
+
         :rtype: List of ``object`` of :class:`.Network`
         :return: A list of Network objects matching the supplied attributes.
         """
@@ -729,10 +726,12 @@ class SubnetService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Searches for a subnet by a given list of attributes.
 
+        Supported attributes: name
+
         :rtype: List of ``object`` of :class:`.Subnet`
         :return: A list of Subnet objects matching the supplied attributes.
         """
@@ -826,10 +825,12 @@ class RouterService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Searches for a router by a given list of attributes.
 
+        Supported attributes: name
+
         :rtype: List of ``object`` of :class:`.Router`
         :return: A list of Router objects matching the supplied attributes.
         """
@@ -940,10 +941,12 @@ class BucketService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Searches for a bucket by a given list of attributes.
 
+        Supported attributes: name
+
         Example:
 
         .. code-block:: python
@@ -1094,10 +1097,12 @@ class KeyPairService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Searches for a key pair by a given list of attributes.
 
+        Supported attributes: name
+
         :rtype: ``object`` of :class:`.KeyPair`
         :return:  a KeyPair object
         """
@@ -1194,10 +1199,12 @@ class VMFirewallService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         Get VM firewalls associated with your account filtered by name.
 
+        Supported attributes: name
+
         :type name: str
         :param name: The name of the VM firewall to retrieve.
 
@@ -1254,6 +1261,8 @@ class VMTypeService(PageableObjectMixin, CloudService):
         """
         Searches for an instance by a given list of attributes.
 
+        Supported attributes: name
+
         :rtype: ``object`` of :class:`.VMType`
         :return: an Instance object
         """
@@ -1301,10 +1310,12 @@ class RegionService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def find(self, name):
+    def find(self, **kwargs):
         """
         Searches for a region by a given list of attributes.
 
+        Supported attributes: name
+
         :rtype: ``object`` of :class:`.Region`
         :return: a Region object
         """

+ 7 - 5
cloudbridge/cloud/providers/aws/resources.py

@@ -7,6 +7,7 @@ import logging
 
 from botocore.exceptions import ClientError
 
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
 from cloudbridge.cloud.base.resources import BaseBucketContainer
@@ -829,11 +830,12 @@ class AWSBucketContainer(BaseBucketContainer):
         return ClientPagedResultList(self._provider, objects,
                                      limit=limit, marker=marker)
 
-    def find(self, name, limit=None, marker=None):
-        objects = [obj for obj in self if obj.name == name]
-
-        return ClientPagedResultList(self._provider, objects,
-                                     limit=limit, marker=marker)
+    def find(self, **kwargs):
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches),
+                                     limit=None, marker=None)
 
     def create(self, name):
         # pylint:disable=protected-access

+ 95 - 53
cloudbridge/cloud/providers/aws/services.py

@@ -88,11 +88,16 @@ class AWSKeyPairService(BaseKeyPairService):
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for Key Pair %s with the params "
-                  "[Limit: %s Marker: %s]", name, limit, marker)
-        return self.svc.find(filter_name='key-name', filter_value=name,
-                             limit=limit, marker=marker)
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
+        log.debug("Searching for Key Pair %s", name)
+        return self.svc.find(filter_name='key-name', filter_value=name)
 
     def create(self, name, public_key_material=None):
         log.debug("Creating Key Pair Service %s", name)
@@ -129,11 +134,16 @@ class AWSVMFirewallService(BaseVMFirewallService):
         return self.svc.create('create_security_group', GroupName=name,
                                Description=description, VpcId=network_id)
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for Firewall Service %s with the params "
-                  "[Limit: %s Marker: %s]", name, limit, marker)
-        return self.svc.find(filter_name='group-name', filter_value=name,
-                             limit=limit, marker=marker)
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
+        log.debug("Searching for Firewall Service %s", name)
+        return self.svc.find(filter_name='group-name', filter_value=name)
 
     def delete(self, firewall_id):
         log.info("Deleting Firewall Service with the id %s", firewall_id)
@@ -178,12 +188,16 @@ class AWSVolumeService(BaseVolumeService):
                   volume_id)
         return self.svc.get(volume_id)
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for AWS Volume Service %s with "
-                  "the params  [Limit: %s Marker: %s]", name,
-                  limit, marker)
-        return self.svc.find(filter_name='tag:Name', filter_value=name,
-                             limit=limit, marker=marker)
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
+        log.debug("Searching for AWS Volume Service %s", name)
+        return self.svc.find(filter_name='tag:Name', filter_value=name)
 
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
@@ -223,12 +237,16 @@ class AWSSnapshotService(BaseSnapshotService):
                   snapshot_id)
         return self.svc.get(snapshot_id)
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for AWS Snapshot Service %s with "
-                  " the params [Limit: %s Marker: %s]", name,
-                  limit, marker)
-        return self.svc.find(filter_name='tag:Name', filter_value=name,
-                             limit=limit, marker=marker)
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
+        log.debug("Searching for AWS Snapshot Service %s", name)
+        return self.svc.find(filter_name='tag:Name', filter_value=name)
 
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
@@ -291,14 +309,12 @@ class AWSBucketService(BaseBucketService):
         # For all other responses, it's assumed that the bucket does not exist.
         return None
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for AWS Bucket %s with the params "
-                  "[Limit: %s Marker: %s]", name, limit, marker)
-        buckets = [bucket
-                   for bucket in self
-                   if name == bucket.name]
-        return ClientPagedResultList(self.provider, buckets,
-                                     limit=limit, marker=marker)
+    def find(self, **kwargs):
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches),
+                                     limit=None, marker=None)
 
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
@@ -333,11 +349,16 @@ class AWSImageService(BaseImageService):
         log.debug("Getting AWS Image Service with the id: %s", image_id)
         return self.svc.get(image_id)
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for AWS Image Service %s with the params "
-                  "[Limit: %s Marker: %s]", name, limit, marker)
-        return self.svc.find(filter_name='name', filter_value=name,
-                             limit=limit, marker=marker)
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
+        log.debug("Searching for AWS Image Service %s", name)
+        return self.svc.find(filter_name='name', filter_value=name)
 
     def list(self, filter_by_owner=True, limit=None, marker=None):
         return self.svc.list(Owners=['self'] if filter_by_owner else [],
@@ -515,9 +536,15 @@ class AWSInstanceService(BaseInstanceService):
     def get(self, instance_id):
         return self.svc.get(instance_id)
 
-    def find(self, name, limit=None, marker=None):
-        return self.svc.find(filter_name='tag:Name', filter_value=name,
-                             limit=limit, marker=marker)
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
+        return self.svc.find(filter_name='tag:Name', filter_value=name)
 
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
@@ -623,11 +650,16 @@ class AWSNetworkService(BaseNetworkService):
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for AWS Network Service %s with the "
-                  " params [Limit: %s Marker: %s]", name, limit, marker)
-        return self.svc.find(filter_name='tag:Name', filter_value=name,
-                             limit=limit, marker=marker)
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
+        log.debug("Searching for AWS Network Service %s", name)
+        return self.svc.find(filter_name='tag:Name', filter_value=name)
 
     def create(self, name, cidr_block):
         log.debug("Creating AWS Network Service with the params "
@@ -663,11 +695,16 @@ class AWSSubnetService(BaseSubnetService):
         else:
             return self.svc.list(limit=limit, marker=marker)
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for AWS Subnet Service %s with the params "
-                  "[Limit: %s Marker: %s]", name, limit, marker)
-        return self.svc.find(filter_name='tag:Name', filter_value=name,
-                             limit=limit, marker=marker)
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
+        log.debug("Searching for AWS Subnet Service %s", name)
+        return self.svc.find(filter_name='tag:Name', filter_value=name)
 
     def create(self, name, network, cidr_block, zone=None):
         log.debug("Creating AWS Subnet Service with the params "
@@ -736,11 +773,16 @@ class AWSRouterService(BaseRouterService):
         log.debug("Getting AWS Router Service with the id: %s", router_id)
         return self.svc.get(router_id)
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for AWS Router Service %s with the params "
-                  "[Limit: %s Marker: %s]", name, limit, marker)
-        return self.svc.find(filter_name='tag:Name', filter_value=name,
-                             limit=limit, marker=marker)
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
+        log.debug("Searching for AWS Router Service %s", name)
+        return self.svc.find(filter_name='tag:Name', filter_value=name)
 
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)

+ 6 - 4
cloudbridge/cloud/providers/azure/resources.py

@@ -9,6 +9,7 @@ import uuid
 from azure.common import AzureException
 from azure.mgmt.network.models import NetworkSecurityGroup
 
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo, \
     BaseBucket, BaseBucketContainer, BaseBucketObject, BaseFloatingIP, \
     BaseFloatingIPContainer, BaseInstance, BaseInternetGateway, BaseKeyPair, \
@@ -433,10 +434,11 @@ class AzureBucketContainer(BaseBucketContainer):
         return ClientPagedResultList(self._provider, objects,
                                      limit=limit, marker=marker)
 
-    def find(self, name, limit=None, marker=None):
-        objects = [obj for obj in self if obj.name == name]
-        return ClientPagedResultList(self._provider, objects,
-                                     limit=limit, marker=marker)
+    def find(self, **kwargs):
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
 
     def create(self, name):
         self._provider.azure_client.create_blob_from_text(

+ 79 - 48
cloudbridge/cloud/providers/azure/services.py

@@ -104,17 +104,19 @@ class AzureVMFirewallService(BaseVMFirewallService):
         cb_fw = AzureVMFirewall(self.provider, fw)
         return cb_fw
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for a security group by a given list of attributes.
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         filters = {'Name': name}
         fws = [AzureVMFirewall(self.provider, vm_firewall)
                for vm_firewall in azure_helpers.filter_by_tag(
                 self.provider.azure_client.list_vm_firewall(), filters)]
-
-        return ClientPagedResultList(self.provider, fws,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, fws)
 
     def delete(self, group_id):
         try:
@@ -155,11 +157,17 @@ class AzureKeyPairService(BaseKeyPairService):
                                      supports_total=False,
                                      data=results)
 
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         key_pair = self.get(name)
         return ClientPagedResultList(self.provider,
-                                     [key_pair] if key_pair else [],
-                                     limit, marker)
+                                     [key_pair] if key_pair else [])
 
     def create(self, name, public_key_material=None):
         AzureKeyPair.assert_valid_resource_name(name)
@@ -204,15 +212,18 @@ class AzureBucketService(BaseBucketService):
             log.exception(error)
             return None
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for a bucket by a given list of attributes.
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         buckets = [AzureBucket(self.provider, bucket)
                    for bucket in
                    self.provider.azure_client.list_containers(prefix=name)]
-        return ClientPagedResultList(self.provider, buckets,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, buckets)
 
     def list(self, limit=None, marker=None):
         """
@@ -270,16 +281,19 @@ class AzureVolumeService(BaseVolumeService):
             log.exception(cloudError.message)
             return None
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for a volume by a given list of attributes.
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         filters = {'Name': name}
         cb_vols = [AzureVolume(self.provider, volume)
                    for volume in azure_helpers.filter_by_tag(
                 self.provider.azure_client.list_disks(), filters)]
-        return ClientPagedResultList(self.provider, cb_vols,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, cb_vols)
 
     def list(self, limit=None, marker=None):
         """
@@ -349,16 +363,19 @@ class AzureSnapshotService(BaseSnapshotService):
             log.exception(cloudError.message)
             return None
 
-    def find(self, name, limit=None, marker=None):
-        """
-             Searches for a snapshot by a given list of attributes.
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         filters = {'Name': name}
         cb_snapshots = [AzureSnapshot(self.provider, snapshot)
                         for snapshot in azure_helpers.filter_by_tag(
                 self.provider.azure_client.list_snapshots(), filters)]
-        return ClientPagedResultList(self.provider, cb_snapshots,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, cb_snapshots)
 
     def list(self, limit=None, marker=None):
         """
@@ -691,19 +708,19 @@ class AzureInstanceService(BaseInstanceService):
             log.exception(cloudError.message)
             return None
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for an instance by a given list of attributes.
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
 
-        :rtype: ``object`` of :class:`.Instance`
-        :return: an Instance object
-        """
         filtr = {'Name': name}
         instances = [AzureInstance(self.provider, inst)
                      for inst in azure_helpers.filter_by_tag(
                 self.provider.azure_client.list_vm(), filtr)]
-        return ClientPagedResultList(self.provider, instances,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, instances)
 
 
 class AzureImageService(BaseImageService):
@@ -722,17 +739,19 @@ class AzureImageService(BaseImageService):
             log.exception(cloudError.message)
             return None
 
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
 
-        """
-         Searches for a image by a given list of attributes.
-        """
         filters = {'Name': name}
         cb_images = [AzureMachineImage(self.provider, image)
                      for image in azure_helpers.filter_by_tag(
                 self.provider.azure_client.list_images(), filters)]
-        return ClientPagedResultList(self.provider, cb_images,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, cb_images)
 
     def list(self, limit=None, marker=None):
         """
@@ -813,13 +832,19 @@ class AzureNetworkService(BaseNetworkService):
         return ClientPagedResultList(self.provider, networks,
                                      limit=limit, marker=marker)
 
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         filters = {'Name': name}
         networks = [AzureNetwork(self.provider, network)
                     for network in azure_helpers.filter_by_tag(
                 self.provider.azure_client.list_networks(), filters)]
-        return ClientPagedResultList(self.provider, networks,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, networks)
 
     def create(self, name, cidr_block):
         # Azure requires CIDR block to be specified when creating a network
@@ -1024,14 +1049,20 @@ class AzureRouterService(BaseRouterService):
             log.exception(cloudError.message)
             return None
 
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         filters = {'Name': name}
         routes = [AzureRouter(self.provider, route)
                   for route in azure_helpers.filter_by_tag(
                 self.provider.azure_client.list_route_tables(), filters)]
 
-        return ClientPagedResultList(self.provider, routes,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, routes)
 
     def list(self, limit=None, marker=None):
         routes = [AzureRouter(self.provider, route)

+ 2 - 2
cloudbridge/cloud/providers/openstack/helpers.py

@@ -7,7 +7,7 @@ import logging as log
 from cloudbridge.cloud.base.resources import ServerPagedResultList
 
 
-def os_result_limit(provider, requested_limit):
+def os_result_limit(provider, requested_limit=None):
     """
     Calculates the limit for OpenStack.
     """
@@ -21,7 +21,7 @@ def os_result_limit(provider, requested_limit):
     return limit + 1
 
 
-def to_server_paged_list(provider, objects, limit):
+def to_server_paged_list(provider, objects, limit=None):
     """
     A convenience function for wrapping a list of OpenStack native objects in
     a ServerPagedResultList. OpenStack

+ 6 - 4
cloudbridge/cloud/providers/openstack/resources.py

@@ -6,6 +6,7 @@ import ipaddress
 import logging
 import os
 
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
 from cloudbridge.cloud.base.resources import BaseBucketContainer
@@ -1363,10 +1364,11 @@ class OpenStackBucketContainer(BaseBucketContainer):
             cb_objects,
             limit)
 
-    def find(self, name, limit=None, marker=None):
-        objects = [obj for obj in self if obj.name == name]
-        return ClientPagedResultList(self._provider, objects,
-                                     limit=limit, marker=marker)
+    def find(self, **kwargs):
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
 
     def create(self, object_name):
         self._provider.swift.put_object(self.bucket.name, object_name, None)

+ 85 - 52
cloudbridge/cloud/providers/openstack/services.py

@@ -152,16 +152,19 @@ class OpenStackKeyPairService(BaseKeyPairService):
         return ClientPagedResultList(self.provider, results,
                                      limit=limit, marker=marker)
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for a key pair by a given list of attributes.
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         keypairs = self.provider.nova.keypairs.findall(name=name)
         results = [OpenStackKeyPair(self.provider, kp)
                    for kp in keypairs]
         log.debug("Searching for %s in: %s", name, keypairs)
-        return ClientPagedResultList(self.provider, results,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, results)
 
     def create(self, name, public_key_material=None):
         """
@@ -223,13 +226,19 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
             return OpenStackVMFirewall(self.provider, sg)
         return None
 
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         log.debug("Searching for %s", name)
         sgs = [self.provider.os_conn.network.find_security_group(name)]
         results = [OpenStackVMFirewall(self.provider, sg)
                    for sg in sgs if sg]
-        return ClientPagedResultList(self.provider, results,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, results)
 
     def delete(self, group_id):
         log.debug("Deleting OpenStack Firewall with the id: %s", group_id)
@@ -257,10 +266,14 @@ class OpenStackImageService(BaseImageService):
                       image_id)
             return None
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for an image by a given list of attributes
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         log.debug("Searching for the OpenStack image with the name: %s", name)
         regex = fnmatch.translate(name)
         cb_images = [
@@ -268,7 +281,7 @@ class OpenStackImageService(BaseImageService):
             for img in self
             if img.name and re.search(regex, img.name)]
 
-        return oshelpers.to_server_paged_list(self.provider, cb_images, limit)
+        return oshelpers.to_server_paged_list(self.provider, cb_images)
 
     def list(self, filter_by_owner=True, limit=None, marker=None):
         """
@@ -343,20 +356,24 @@ class OpenStackVolumeService(BaseVolumeService):
             log.debug("Volume %s was not found.", volume_id)
             return None
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for a volume by a given list of attributes.
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         log.debug("Searching for an OpenStack Volume with the name %s", name)
         search_opts = {'name': name}
         cb_vols = [
             OpenStackVolume(self.provider, vol)
             for vol in self.provider.cinder.volumes.list(
                 search_opts=search_opts,
-                limit=oshelpers.os_result_limit(self.provider, limit),
-                marker=marker)]
+                limit=oshelpers.os_result_limit(self.provider),
+                marker=None)]
 
-        return oshelpers.to_server_paged_list(self.provider, cb_vols, limit)
+        return oshelpers.to_server_paged_list(self.provider, cb_vols)
 
     def list(self, limit=None, marker=None):
         """
@@ -407,14 +424,17 @@ class OpenStackSnapshotService(BaseSnapshotService):
             log.debug("Snapshot %s was not found.", snapshot_id)
             return None
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for a volume by a given list of attributes.
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         search_opts = {'name': name,  # TODO: Cinder is ignoring name
-                       'limit': oshelpers.os_result_limit(self.provider,
-                                                          limit),
-                       'marker': marker}
+                       'limit': oshelpers.os_result_limit(self.provider),
+                       'marker': None}
         log.debug("Searching for an OpenStack volume with the following "
                   "params: %s", search_opts)
         cb_snaps = [
@@ -422,7 +442,7 @@ class OpenStackSnapshotService(BaseSnapshotService):
             snap in self.provider.cinder.volume_snapshots.list(search_opts)
             if snap.name == name]
 
-        return oshelpers.to_server_paged_list(self.provider, cb_snaps, limit)
+        return oshelpers.to_server_paged_list(self.provider, cb_snaps)
 
     def list(self, limit=None, marker=None):
         """
@@ -473,18 +493,22 @@ class OpenStackBucketService(BaseBucketService):
             log.debug("Bucket %s was not found.", bucket_id)
             return None
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for a bucket by a given list of attributes.
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         log.debug("Searching for the OpenStack Bucket with the name: %s", name)
         _, container_list = self.provider.swift.get_account(
-            limit=oshelpers.os_result_limit(self.provider, limit),
-            marker=marker)
+            limit=oshelpers.os_result_limit(self.provider),
+            marker=None)
         cb_buckets = [OpenStackBucket(self.provider, c)
                       for c in container_list
                       if name in c.get("name")]
-        return oshelpers.to_server_paged_list(self.provider, cb_buckets, limit)
+        return oshelpers.to_server_paged_list(self.provider, cb_buckets)
 
     def list(self, limit=None, marker=None):
         """
@@ -711,18 +735,22 @@ class OpenStackInstanceService(BaseInstanceService):
     def create_launch_config(self):
         return BaseLaunchConfig(self.provider)
 
-    def find(self, name, limit=None, marker=None):
-        """
-        Searches for an instance by a given list of attributes.
-        """
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         search_opts = {'name': name}
         cb_insts = [
             OpenStackInstance(self.provider, inst)
             for inst in self.provider.nova.servers.list(
                 search_opts=search_opts,
-                limit=oshelpers.os_result_limit(self.provider, limit),
-                marker=marker)]
-        return oshelpers.to_server_paged_list(self.provider, cb_insts, limit)
+                limit=oshelpers.os_result_limit(self.provider),
+                marker=None)]
+        return oshelpers.to_server_paged_list(self.provider, cb_insts)
 
     def list(self, limit=None, marker=None):
         """
@@ -790,15 +818,21 @@ class OpenStackNetworkService(BaseNetworkService):
         return ClientPagedResultList(self.provider, networks,
                                      limit=limit, marker=marker)
 
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs, 'name'))
+
         log.debug("Searching for the OpenStack Network with the "
                   "name: %s", name)
         networks = [OpenStackNetwork(self.provider, network)
                     for network in self.provider.neutron.list_networks(
                         name=name)
                     .get('networks') if network]
-        return ClientPagedResultList(self.provider, networks,
-                                     limit=limit, marker=marker)
+        return ClientPagedResultList(self.provider, networks)
 
     def create(self, name, cidr_block):
         log.debug("Creating OpenStack Network with the params: "
@@ -900,12 +934,11 @@ class OpenStackRouterService(BaseRouterService):
         return ClientPagedResultList(self.provider, os_routers, limit=limit,
                                      marker=marker)
 
-    def find(self, name, limit=None, marker=None):
-        log.debug("Searching for OpenStack Router with the params: "
-                  "[name: %s, limit: %s, marker: %s]", name, limit, marker)
-        os_routers = [r for r in self if r.name == name]
-        return ClientPagedResultList(self.provider, os_routers, limit=limit,
-                                     marker=marker)
+    def find(self, **kwargs):
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
 
     def create(self, name, network):
         """

+ 2 - 2
docs/topics/object_storage.rst

@@ -34,8 +34,8 @@ To locate and download this uploaded file again, you can do the following:
 
 .. code-block:: python
 
-    bucket = provider.storage.buckets.find('my-bucket')[0]
-    obj = bucket.objects.find('my-data.txt')[0]
+    bucket = provider.storage.buckets.find(name='my-bucket')[0]
+    obj = bucket.objects.find(name='my-data.txt')[0]
     print("Size: {0}, Modified: {1}".format(obj.size, obj.last_modified))
     with open('/tmp/myfile.txt', 'wb') as f:
         obj.save_content(f)