Переглянути джерело

Merge master into middleware-refactor

almahmoud 7 роки тому
батько
коміт
538ee46a9d

+ 1 - 1
.travis.yml

@@ -57,7 +57,7 @@ before_install:
            }
            }
            ;;
            ;;
         *)
         *)
-           echo "Build triggered through API or CRON job. Running regardless of changes"
+           echo "Build triggered through API or CRON job. Running regardless of changes..."
            ;;
            ;;
       esac
       esac
 install:
 install:

+ 26 - 10
cloudbridge/cloud/base/events.py

@@ -14,9 +14,9 @@ class InterceptingEventHandler(EventHandler):
 
 
     def __init__(self, event_pattern, priority, callback):
     def __init__(self, event_pattern, priority, callback):
         self.__dispatcher = None
         self.__dispatcher = None
-        self.event_pattern = event_pattern
-        self.priority = priority
-        self.callback = callback
+        self.__event_pattern = event_pattern
+        self.__priority = priority
+        self.__callback = callback
 
 
     def __lt__(self, other):
     def __lt__(self, other):
         # This is required for the bisect module to insert
         # This is required for the bisect module to insert
@@ -33,14 +33,21 @@ class InterceptingEventHandler(EventHandler):
         else:
         else:
             return None
             return None
 
 
+    @property
     def event_pattern(self):
     def event_pattern(self):
-        pass
+        return self.__event_pattern
 
 
+    @property
     def priority(self):
     def priority(self):
-        pass
+        return self.__priority
 
 
+    @property
     def callback(self):
     def callback(self):
-        pass
+        return self.__callback
+
+    @callback.setter
+    def callback(self, value):
+        self.__callback = value
 
 
     @property
     @property
     def dispatcher(self):
     def dispatcher(self):
@@ -151,20 +158,29 @@ class SimpleEventDispatcher(EventDispatcher):
             raise HandlerException(message)
             raise HandlerException(message)
         return cache_list
         return cache_list
 
 
+    def _invalidate_cache(self, event_pattern=None):
+        if not event_pattern:
+            # invalidate entire cache
+            self.__handler_cache = {}
+        else:
+            # Smarter invalidation by only deleting events that
+            # are affected by the pattern
+            for key in list(self.__handler_cache.keys()):
+                if re.search(fnmatch.translate(event_pattern), key):
+                    del self.__handler_cache[key]
+
     def subscribe(self, event_handler):
     def subscribe(self, event_handler):
         event_handler.dispatcher = self
         event_handler.dispatcher = self
         handler_list = self.__events.get(event_handler.event_pattern, [])
         handler_list = self.__events.get(event_handler.event_pattern, [])
         handler_list.append(event_handler)
         handler_list.append(event_handler)
         self.__events[event_handler.event_pattern] = handler_list
         self.__events[event_handler.event_pattern] = handler_list
-        # invalidate cache
-        self.__handler_cache = {}
+        self._invalidate_cache(event_pattern=event_handler.event_pattern)
 
 
     def unsubscribe(self, event_handler):
     def unsubscribe(self, event_handler):
         handler_list = self.__events.get(event_handler.event_pattern, [])
         handler_list = self.__events.get(event_handler.event_pattern, [])
         handler_list.remove(event_handler)
         handler_list.remove(event_handler)
         event_handler.dispatcher = None
         event_handler.dispatcher = None
-        # invalidate cache
-        self.__handler_cache = {}
+        self._invalidate_cache(event_pattern=event_handler.event_pattern)
 
 
     def observe(self, event_pattern, priority, callback):
     def observe(self, event_pattern, priority, callback):
         handler = ObservingEventHandler(event_pattern, priority, callback)
         handler = ObservingEventHandler(event_pattern, priority, callback)

+ 5 - 3
cloudbridge/cloud/base/helpers.py

@@ -16,6 +16,8 @@ import six
 
 
 import cloudbridge
 import cloudbridge
 
 
+from ..interfaces.exceptions import InvalidParamException
+
 
 
 def generate_key_pair():
 def generate_key_pair():
     """
     """
@@ -69,7 +71,7 @@ def generic_find(filter_names, kwargs, objs):
 
 
     # All kwargs should have been popped at this time.
     # All kwargs should have been popped at this time.
     if len(kwargs) > 0:
     if len(kwargs) > 0:
-        raise TypeError(
+        raise InvalidParamException(
             "Unrecognised parameters for search: %s. Supported attributes: %s"
             "Unrecognised parameters for search: %s. Supported attributes: %s"
             % (kwargs, filter_names))
             % (kwargs, filter_names))
 
 
@@ -153,8 +155,8 @@ def rename_kwargs(func_name, kwargs, aliases):
     for alias, new in aliases.items():
     for alias, new in aliases.items():
         if alias in kwargs:
         if alias in kwargs:
             if new in kwargs:
             if new in kwargs:
-                raise TypeError('{} received both {} and {}'.format(
-                    func_name, alias, new))
+                raise InvalidParamException(
+                    '{} received both {} and {}'.format(func_name, alias, new))
             # Manually invoke the deprecated decorator with an empty lambda
             # Manually invoke the deprecated decorator with an empty lambda
             # to signal deprecation
             # to signal deprecation
             deprecated(deprecated_in='1.1',
             deprecated(deprecated_in='1.1',

+ 9 - 0
cloudbridge/cloud/base/provider.py

@@ -11,6 +11,7 @@ except ImportError:  # Python 2
 import six
 import six
 
 
 from ..base.events import SimpleEventDispatcher
 from ..base.events import SimpleEventDispatcher
+from ..base.middleware import ExceptionWrappingMiddleware
 from ..base.middleware import SimpleMiddlewareManager
 from ..base.middleware import SimpleMiddlewareManager
 from ..interfaces import CloudProvider
 from ..interfaces import CloudProvider
 from ..interfaces.exceptions import ProviderConnectionException
 from ..interfaces.exceptions import ProviderConnectionException
@@ -88,6 +89,7 @@ class BaseCloudProvider(CloudProvider):
         self._config_parser.read(CloudBridgeConfigLocations)
         self._config_parser.read(CloudBridgeConfigLocations)
         self._events = SimpleEventDispatcher()
         self._events = SimpleEventDispatcher()
         self._middleware = SimpleMiddlewareManager(self._events)
         self._middleware = SimpleMiddlewareManager(self._events)
+        self.add_required_middleware()
 
 
     @property
     @property
     def config(self):
     def config(self):
@@ -105,6 +107,13 @@ class BaseCloudProvider(CloudProvider):
     def middleware(self):
     def middleware(self):
         return self._middleware
         return self._middleware
 
 
+    def add_required_middleware(self):
+        """
+        Adds common middleware that is essential for cloudbridge to function.
+        Any other extra middleware can be added through the provider factory.
+        """
+        self.middleware.add(ExceptionWrappingMiddleware())
+
     def authenticate(self):
     def authenticate(self):
         """
         """
         A basic implementation which simply runs a low impact command to
         A basic implementation which simply runs a low impact command to

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

@@ -466,7 +466,10 @@ class BaseVolume(BaseCloudResource, BaseObjectLifeCycleMixin, Volume):
             interval=interval)
             interval=interval)
 
 
     def delete(self):
     def delete(self):
-        self._provider.storage.volumes.delete(self.id)
+        """
+        Delete this volume.
+        """
+        return self._provider.storage.volumes.delete(self)
 
 
 
 
 class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
 class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
@@ -491,7 +494,10 @@ class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
             interval=interval)
             interval=interval)
 
 
     def delete(self):
     def delete(self):
-        self._provider.storage.snapshots.delete(self.id)
+        """
+        Delete this snapshot.
+        """
+        return self._provider.storage.snapshots.delete(self)
 
 
 
 
 class BaseKeyPair(BaseCloudResource, KeyPair):
 class BaseKeyPair(BaseCloudResource, KeyPair):

+ 14 - 7
cloudbridge/cloud/base/services.py

@@ -16,6 +16,7 @@ from cloudbridge.cloud.base.resources import BaseVMFirewall
 from cloudbridge.cloud.base.resources import BaseVolume
 from cloudbridge.cloud.base.resources import BaseVolume
 from cloudbridge.cloud.interfaces.exceptions import \
 from cloudbridge.cloud.interfaces.exceptions import \
     InvalidConfigurationException
     InvalidConfigurationException
+from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
 from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.services import BucketObjectService
 from cloudbridge.cloud.interfaces.services import BucketObjectService
 from cloudbridge.cloud.interfaces.services import BucketService
 from cloudbridge.cloud.interfaces.services import BucketService
@@ -152,6 +153,12 @@ class BaseKeyPairService(
                              name,  public_key_material=public_key_material)
                              name,  public_key_material=public_key_material)
 
 
     def delete(self, key_pair_id):
     def delete(self, key_pair_id):
+        return self.dispatch(self, "provider.security.key_pairs.delete",
+                             key_pair_id)
+
+    @implement(event_pattern="provider.security.key_pairs.delete",
+               priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
+    def _delete(self, key_pair_id):
         """
         """
         Delete an existing key pair.
         Delete an existing key pair.
 
 
@@ -234,9 +241,9 @@ class BaseVMFirewallService(
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs,
-                                                           ", ".join(filters)))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, ", ".join(filters)))
 
 
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])
@@ -391,7 +398,7 @@ class BaseBucketService(
 
 
     # Generic find will be used for providers where we have not implemented
     # Generic find will be used for providers where we have not implemented
     # provider-specific querying for find method
     # provider-specific querying for find method
-    @implement(event_pattern="*.storage.buckets.find",
+    @implement(event_pattern="provider.storage.buckets.find",
                priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
                priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
     def _find(self, **kwargs):
     def _find(self, **kwargs):
         obj_list = self
         obj_list = self
@@ -400,9 +407,9 @@ class BaseBucketService(
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs,
-                                                           ", ".join(filters)))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, ", ".join(filters)))
 
 
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])

+ 12 - 1
cloudbridge/cloud/interfaces/exceptions.py

@@ -94,8 +94,19 @@ class DuplicateResourceException(CloudBridgeBaseException):
     pass
     pass
 
 
 
 
+class InvalidParamException(InvalidNameException):
+    """
+    Marker interface for an invalid or unexpected parameter, for example,
+    to a service.find() method.
+    """
+
+    def __init__(self, msg):
+        super(InvalidParamException, self).__init__(msg)
+
+
 class HandlerException(CloudBridgeBaseException):
 class HandlerException(CloudBridgeBaseException):
     """
     """
     Marker interface for event handler exceptions.
     Marker interface for event handler exceptions.
     """
     """
-    pass
+    def __init__(self, msg):
+        super(HandlerException, self).__init__(msg)

+ 41 - 25
cloudbridge/cloud/interfaces/resources.py

@@ -33,7 +33,7 @@ class CloudResource(object):
 
 
     This interface has a  _provider property that can be used to access the
     This interface has a  _provider property that can be used to access the
     provider associated with the resource, which is only intended for use by
     provider associated with the resource, which is only intended for use by
-    subclasses. Every cloudbridge resource also has an id, a name and a
+    subclasses. Every CloudBridge resource also has an id, a name and a
     label property. The id property is a unique identifier for the resource.
     label property. The id property is a unique identifier for the resource.
     The name is a more user-friendly version of an id, suitable for
     The name is a more user-friendly version of an id, suitable for
     display to an end-user. However, it cannot be used in place of id. See
     display to an end-user. However, it cannot be used in place of id. See
@@ -60,11 +60,11 @@ class CloudResource(object):
         Get the resource identifier.
         Get the resource identifier.
 
 
         The id property is used to uniquely identify the resource, and is an
         The id property is used to uniquely identify the resource, and is an
-        opaque value which should not be interpreted by cloudbridge clients,
+        opaque value which should not be interpreted by CloudBridge clients,
         and is a value meaningful to the underlying cloud provider.
         and is a value meaningful to the underlying cloud provider.
 
 
-        :rtype: ``str`` :return: ID for this resource as returned by the cloud
-        middleware.
+        :rtype: ``str``
+        :return: ID for this resource as returned by the cloud middleware.
         """
         """
         pass
         pass
 
 
@@ -74,30 +74,32 @@ class CloudResource(object):
         Get the name id for the resource.
         Get the name id for the resource.
 
 
         The name property is typically a user-friendly id value for the
         The name property is typically a user-friendly id value for the
-        resource. The name is different from the id property in the
-        following ways:
-        1. The name property is often a more user-friendly value to
-           display to the user than the id property.
-        2. The name may sometimes be the same as the id, but should never
-           be used in place of the id.
-        3. The id is what will uniquely identify a resource, and will be used
-           internally by cloudbridge for all get operations etc.
-        4. All resources have a name.
-        5. The name is read-only.
-        6. However, the name may not necessarily be unique, which is the
-           reason why it should not be used for uniquely identifying a
-           resource.
+        resource. The name is different from the id property in the following
+        ways:
+
+         1. The name property is often a more user-friendly value to
+            display to the user than the id property.
+         2. The name may sometimes be the same as the id, but should never
+            be used in place of the id.
+         3. The id is what will uniquely identify a resource, and will be used
+            internally by CloudBridge for all get operations etc.
+         4. All resources have a name.
+         5. The name is read-only.
+         6. However, the name may not necessarily be unique, which is the
+            reason why it should not be used for uniquely identifying a
+            resource.
+
         Example:
         Example:
-        The AWS machine image name maps to a cloudbridge name. It is not
-        editable and is a user friendly name such as 'Ubuntu 14.04' and
+        The AWS machine image name maps to a CloudBridge name. It is not
+        editable and is a user friendly name such as 'Ubuntu 18.04' and
         corresponds to the ami-name. It is distinct from the ami-id, which
         corresponds to the ami-name. It is distinct from the ami-id, which
-        maps to cloudbridge's id property. The ami-name cannot be edited, and
+        maps to CloudBridge's id property. The ami-name cannot be edited, and
         is set at creation time. It is not necessarily unique.
         is set at creation time. It is not necessarily unique.
-        In Azure, the machine image's name corresponds to cloudbridge's name
+        In Azure, the machine image's name corresponds to CloudBridge's name
         property. In Azure, it also happens to be the same as the id property.
         property. In Azure, it also happens to be the same as the id property.
 
 
         The name property and the label property share the same character
         The name property and the label property share the same character
-        restrictions. see :py:attr:`~LabeledCloudResource.label`
+        restrictions. See :py:attr:`~LabeledCloudResource.label`.
         """
         """
         pass
         pass
 
 
@@ -121,6 +123,7 @@ class LabeledCloudResource(CloudResource):
         in the underlying cloud provider, or be simulated through tags/labels.
         in the underlying cloud provider, or be simulated through tags/labels.
 
 
         The label property adheres to the following restrictions:
         The label property adheres to the following restrictions:
+
         * Must be at least 3 characters in length.
         * Must be at least 3 characters in length.
         * Cannot be longer than 63 characters.
         * Cannot be longer than 63 characters.
         * May only contain ASCII characters comprising of lowercase letters,
         * May only contain ASCII characters comprising of lowercase letters,
@@ -129,18 +132,19 @@ class LabeledCloudResource(CloudResource):
           (i.e. cannot begin or end with a dash)
           (i.e. cannot begin or end with a dash)
 
 
         Some resources may not support labels, in which case, a
         Some resources may not support labels, in which case, a
-        NotImplementedError will be thrown.
+        ``NotImplementedError`` will be thrown.
 
 
         :rtype: ``str``
         :rtype: ``str``
         :return: Label for this resource as returned by the cloud middleware.
         :return: Label for this resource as returned by the cloud middleware.
-        :throws NotImplementedError if this resource does not support labels
+        :raise: ``NotImplementedError`` if this resource does not support
+                labels.
         """
         """
         pass
         pass
 
 
 
 
 class Configuration(dict):
 class Configuration(dict):
     """
     """
-    Represents a cloudbridge configuration object
+    Represents a CloudBridge configuration object
     """
     """
 
 
     @abstractproperty
     @abstractproperty
@@ -507,6 +511,9 @@ class Instance(ObjectLifeCycleMixin, LabeledCloudResource):
     def label(self, value):
     def label(self, value):
         """
         """
         Set the instance label.
         Set the instance label.
+
+        :type value: ``str``
+        :param value: The value to set the label to.
         """
         """
         pass
         pass
 
 
@@ -895,6 +902,9 @@ class Network(ObjectLifeCycleMixin, LabeledCloudResource):
     def label(self, value):
     def label(self, value):
         """
         """
         Set the resource label.
         Set the resource label.
+
+        :type value: ``str``
+        :param value: The value to set the label to.
         """
         """
         pass
         pass
 
 
@@ -1013,6 +1023,9 @@ class Subnet(ObjectLifeCycleMixin, LabeledCloudResource):
     def label(self, value):
     def label(self, value):
         """
         """
         Set the resource label.
         Set the resource label.
+
+        :type value: ``str``
+        :param value: The value to set the label to.
         """
         """
         pass
         pass
 
 
@@ -1232,6 +1245,9 @@ class Router(LabeledCloudResource):
     def label(self, value):
     def label(self, value):
         """
         """
         Set the resource label.
         Set the resource label.
+
+        :type value: ``str``
+        :param value: The value to set the label to.
         """
         """
         pass
         pass
 
 

+ 20 - 2
cloudbridge/cloud/interfaces/services.py

@@ -362,6 +362,15 @@ class VolumeService(PageableObjectMixin, CloudService):
         """
         """
         pass
         pass
 
 
+    def delete(self, volume):
+        """
+        Delete an existing volume.
+
+        :type volume: ``str`` or :class:`Volume`
+        :param volume: The object or ID of the volume to be deleted.
+        """
+        pass
+
 
 
 class SnapshotService(PageableObjectMixin, CloudService):
 class SnapshotService(PageableObjectMixin, CloudService):
     """
     """
@@ -370,7 +379,7 @@ class SnapshotService(PageableObjectMixin, CloudService):
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
     @abstractmethod
     @abstractmethod
-    def get(self, volume_id):
+    def get(self, snapshot_id):
         """
         """
         Returns a snapshot given its id.
         Returns a snapshot given its id.
 
 
@@ -422,6 +431,15 @@ class SnapshotService(PageableObjectMixin, CloudService):
         """
         """
         pass
         pass
 
 
+    def delete(self, snapshot):
+        """
+        Delete an existing snapshot.
+
+        :type snapshot: ``str`` or :class:`Snapshot`
+        :param snapshot: The object or ID of the snapshot to be deleted.
+        """
+        pass
+
 
 
 class StorageService(CloudService):
 class StorageService(CloudService):
 
 
@@ -701,7 +719,7 @@ class SubnetService(PageableObjectMixin, CloudService):
         :param subnet_id: The ID of the subnet to retrieve.
         :param subnet_id: The ID of the subnet to retrieve.
 
 
         :rtype: ``object`` of :class:`.Subnet`
         :rtype: ``object`` of :class:`.Subnet`
-        return: a Subnet object
+        :return: a Subnet object
         """
         """
         pass
         pass
 
 

+ 31 - 17
cloudbridge/cloud/providers/aws/services.py

@@ -32,6 +32,7 @@ from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.cloud.interfaces.exceptions import \
 from cloudbridge.cloud.interfaces.exceptions import \
     InvalidConfigurationException
     InvalidConfigurationException
+from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import Network
@@ -106,8 +107,9 @@ class AWSKeyPairService(BaseKeyPairService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'name'))
 
 
         log.debug("Searching for Key Pair %s", name)
         log.debug("Searching for Key Pair %s", name)
         return self.svc.find(filter_name='key-name', filter_value=name)
         return self.svc.find(filter_name='key-name', filter_value=name)
@@ -176,8 +178,9 @@ class AWSVMFirewallService(BaseVMFirewallService):
         log.debug("Searching for Firewall Service %s", label)
         log.debug("Searching for Firewall Service %s", label)
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
         return self.svc.find(filter_name='tag:Name',
         return self.svc.find(filter_name='tag:Name',
                              filter_value=label)
                              filter_value=label)
 
 
@@ -236,8 +239,9 @@ class AWSVolumeService(BaseVolumeService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         log.debug("Searching for AWS Volume Service %s", label)
         log.debug("Searching for AWS Volume Service %s", label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
@@ -267,7 +271,10 @@ class AWSVolumeService(BaseVolumeService):
     @implement(event_pattern="provider.storage.volumes.delete",
     @implement(event_pattern="provider.storage.volumes.delete",
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
     def _delete(self, volume_id):
     def _delete(self, volume_id):
-        aws_vol = self.svc.get_raw(volume_id)
+        if isinstance(volume_id, AWSVolume):
+            aws_vol = volume_id._volume
+        else:
+            aws_vol = self.svc.get_raw(volume_id)
         aws_vol.delete()
         aws_vol.delete()
 
 
 
 
@@ -323,6 +330,8 @@ class AWSSnapshotService(BaseSnapshotService):
     @implement(event_pattern="provider.storage.snapshots.delete",
     @implement(event_pattern="provider.storage.snapshots.delete",
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
     def _delete(self, snapshot_id):
     def _delete(self, snapshot_id):
+        if isinstance(snapshot_id, AWSSnapshot):
+            aws_snap = snapshot_id._snapshot
         aws_snap = self.svc.get_raw(snapshot_id)
         aws_snap = self.svc.get_raw(snapshot_id)
         aws_snap.delete()
         aws_snap.delete()
 
 
@@ -499,8 +508,9 @@ class AWSImageService(BaseImageService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         extra_args = {}
         extra_args = {}
         if owner:
         if owner:
@@ -676,8 +686,9 @@ class AWSInstanceService(BaseInstanceService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         return self.svc.find(filter_name='tag:Name', filter_value=label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
 
 
@@ -807,8 +818,9 @@ class AWSNetworkService(BaseNetworkService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         log.debug("Searching for AWS Network Service %s", label)
         log.debug("Searching for AWS Network Service %s", label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
@@ -882,8 +894,9 @@ class AWSSubnetService(BaseSubnetService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         log.debug("Searching for AWS Subnet Service %s", label)
         log.debug("Searching for AWS Subnet Service %s", label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
@@ -1034,8 +1047,9 @@ class AWSRouterService(BaseRouterService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         log.debug("Searching for AWS Router Service %s", label)
         log.debug("Searching for AWS Router Service %s", label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)

+ 25 - 20
cloudbridge/cloud/providers/azure/services.py

@@ -29,6 +29,7 @@ from cloudbridge.cloud.base.services import BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
+from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
 from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import Network
@@ -188,9 +189,9 @@ class AzureKeyPairService(BaseKeyPairService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs,
-                                                           ", ".join(filters)))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, ", ".join(filters)))
 
 
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])
@@ -278,9 +279,9 @@ class AzureVolumeService(BaseVolumeService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs,
-                                                           ", ".join(filters)))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, ", ".join(filters)))
 
 
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])
@@ -342,7 +343,9 @@ class AzureVolumeService(BaseVolumeService):
     @implement(event_pattern="provider.storage.volumes.delete",
     @implement(event_pattern="provider.storage.volumes.delete",
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
     def _delete(self, volume_id):
     def _delete(self, volume_id):
-        self.provider.azure_client.delete_disk(volume_id)
+        vol_id = (volume_id.id if isinstance(volume_id, AzureVolume)
+                  else volume_id)
+        self.provider.azure_client.delete_disk(vol_id)
 
 
 
 
 class AzureSnapshotService(BaseSnapshotService):
 class AzureSnapshotService(BaseSnapshotService):
@@ -369,9 +372,9 @@ class AzureSnapshotService(BaseSnapshotService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs,
-                                                           ", ".join(filters)))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, ", ".join(filters)))
 
 
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])
@@ -413,7 +416,9 @@ class AzureSnapshotService(BaseSnapshotService):
     @implement(event_pattern="provider.storage.snapshots.delete",
     @implement(event_pattern="provider.storage.snapshots.delete",
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
     def _delete(self, snapshot_id):
     def _delete(self, snapshot_id):
-        self.provider.azure_client.delete_snapshot(snapshot_id)
+        snap_id = (snapshot_id.id if isinstance(snapshot_id, AzureSnapshot)
+                   else snapshot_id)
+        self.provider.azure_client.delete_snapshot(snap_id)
 
 
 
 
 class AzureBucketService(BaseBucketService):
 class AzureBucketService(BaseBucketService):
@@ -554,9 +559,9 @@ class AzureImageService(BaseImageService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs,
-                                                           ", ".join(filters)))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, ", ".join(filters)))
 
 
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])
@@ -885,9 +890,9 @@ class AzureInstanceService(BaseInstanceService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs,
-                                                           ", ".join(filters)))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, ", ".join(filters)))
 
 
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])
@@ -1161,9 +1166,9 @@ class AzureRouterService(BaseRouterService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs,
-                                                           ", ".join(filters)))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, ", ".join(filters)))
 
 
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])

+ 83 - 38
cloudbridge/cloud/providers/gce/services.py

@@ -30,6 +30,7 @@ from cloudbridge.cloud.base.services import BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
+from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.providers.gce import helpers
 from cloudbridge.cloud.providers.gce import helpers
@@ -113,9 +114,9 @@ class GCEKeyPairService(BaseKeyPairService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs,
-                                                           ", ".join(filters)))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, ", ".join(filters)))
 
 
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])
@@ -247,7 +248,8 @@ class GCEVMTypeService(BaseVMTypeService):
             is_match = True
             is_match = True
             for key, value in kwargs.items():
             for key, value in kwargs.items():
                 if key not in inst_type:
                 if key not in inst_type:
-                    raise TypeError("The attribute key is not valid.")
+                    raise InvalidParamException(
+                        "Unrecognised parameters for search: %s." % key)
                 if inst_type.get(key) != value:
                 if inst_type.get(key) != value:
                     is_match = False
                     is_match = False
                     break
                     break
@@ -334,13 +336,20 @@ class GCEImageService(BaseImageService):
                 return public_image
                 return public_image
         return None
         return None
 
 
-    def find(self, label, limit=None, marker=None):
+    def find(self, limit=None, marker=None, **kwargs):
         """
         """
         Searches for an image by a given list of attributes
         Searches for an image by a given list of attributes
         """
         """
-        filters = {'label': label}
+        label = kwargs.pop('label', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
+
         # Retrieve all available images by setting limit to sys.maxsize
         # Retrieve all available images by setting limit to sys.maxsize
-        images = [image for image in self if image.label == filters['label']]
+        images = [image for image in self if image.label == label]
         return ClientPagedResultList(self.provider, images,
         return ClientPagedResultList(self.provider, images,
                                      limit=limit, marker=marker)
                                      limit=limit, marker=marker)
 
 
@@ -532,16 +541,23 @@ class GCEInstanceService(BaseInstanceService):
 
 
     @implement(event_pattern="provider.compute.instances.find",
     @implement(event_pattern="provider.compute.instances.find",
                priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
                priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
-    def _find(self, label, limit=None, marker=None):
+    def _find(self, limit=None, marker=None, **kwargs):
         """
         """
         Searches for instances by instance label.
         Searches for instances by instance label.
         :return: a list of Instance objects
         :return: a list of Instance objects
         """
         """
+        label = kwargs.pop('label', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
+
         instances = [instance for instance in self.list()
         instances = [instance for instance in self.list()
                      if instance.label == label]
                      if instance.label == label]
-        if limit and len(instances) > limit:
-            instances = instances[:limit]
-        return instances
+        return ClientPagedResultList(self.provider, instances,
+                                     limit=limit, marker=marker)
 
 
     @implement(event_pattern="provider.compute.instances.list",
     @implement(event_pattern="provider.compute.instances.list",
                priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
                priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
@@ -655,7 +671,8 @@ class GCENetworkService(BaseNetworkService):
         obj_list = self
         obj_list = self
         filters = ['name', 'label']
         filters = ['name', 'label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
-        return ClientPagedResultList(self._provider, list(matches))
+        return ClientPagedResultList(self._provider, list(matches),
+                                     limit=limit, marker=marker)
 
 
     @implement(event_pattern="provider.networking.networks.list",
     @implement(event_pattern="provider.networking.networks.list",
                priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
                priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
@@ -748,11 +765,12 @@ class GCERouterService(BaseRouterService):
 
 
     @implement(event_pattern="provider.networking.routers.find",
     @implement(event_pattern="provider.networking.routers.find",
                priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
                priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    def find(self, limit=None, marker=None, **kwargs):
         obj_list = self
         obj_list = self
         filters = ['name', 'label']
         filters = ['name', 'label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
-        return ClientPagedResultList(self._provider, list(matches))
+        return ClientPagedResultList(self._provider, list(matches),
+                                     limit=limit, marker=marker)
 
 
     @implement(event_pattern="provider.networking.routers.list",
     @implement(event_pattern="provider.networking.routers.list",
                priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
                priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
@@ -1039,7 +1057,18 @@ class GCEVolumeService(BaseVolumeService):
 
 
     @implement(event_pattern="provider.storage.volumes.find",
     @implement(event_pattern="provider.storage.volumes.find",
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _find(self, label, limit=None, marker=None):
+    def _find(self, limit=None, marker=None, **kwargs):
+        """
+        Searches for a volume by a given list of attributes.
+        """
+        label = kwargs.pop('label', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
+
         filtr = 'labels.cblabel eq ' + label
         filtr = 'labels.cblabel eq ' + label
         max_result = limit if limit is not None and limit < 500 else 500
         max_result = limit if limit is not None and limit < 500 else 500
         response = (self.provider
         response = (self.provider
@@ -1063,6 +1092,13 @@ class GCEVolumeService(BaseVolumeService):
     @implement(event_pattern="provider.storage.volumes.list",
     @implement(event_pattern="provider.storage.volumes.list",
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
     def _list(self, limit=None, marker=None):
     def _list(self, limit=None, marker=None):
+        """
+        List all volumes.
+
+        limit: The maximum number of volumes to return. The returned
+               ResultList's is_truncated property can be used to determine
+               whether more records are available.
+        """
         # For GCE API, Acceptable values are 0 to 500, inclusive.
         # For GCE API, Acceptable values are 0 to 500, inclusive.
         # (Default: 500).
         # (Default: 500).
         max_result = limit if limit is not None and limit < 500 else 500
         max_result = limit if limit is not None and limit < 500 else 500
@@ -1116,13 +1152,17 @@ class GCEVolumeService(BaseVolumeService):
     @implement(event_pattern="provider.storage.volumes.delete",
     @implement(event_pattern="provider.storage.volumes.delete",
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
     def _delete(self, volume_id):
     def _delete(self, volume_id):
-        vol = self.provider.get_resource('disks', volume_id)
-        zone_name = self.provider.parse_url(vol.get('zone')).parameters['zone']
+        if isinstance(volume_id, GCEVolume):
+            gce_vol = volume_id._volume
+        else:
+            gce_vol = self.provider.get_resource('disks', volume_id)
+        zone_name = (self.provider.parse_url(gce_vol.get('zone'))
+                         .parameters['zone'])
         (self._provider.gce_compute
         (self._provider.gce_compute
                        .disks()
                        .disks()
                        .delete(project=self.provider.project_name,
                        .delete(project=self.provider.project_name,
                                zone=zone_name,
                                zone=zone_name,
-                               disk=vol.get('name'))
+                               disk=gce_vol.get('name'))
                        .execute())
                        .execute())
 
 
 
 
@@ -1139,7 +1179,15 @@ class GCESnapshotService(BaseSnapshotService):
 
 
     @implement(event_pattern="provider.storage.snapshots.find",
     @implement(event_pattern="provider.storage.snapshots.find",
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
-    def _find(self, label, limit=None, marker=None):
+    def _find(self, limit=None, marker=None, **kwargs):
+        label = kwargs.pop('label', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
+
         filtr = 'labels.cblabel eq ' + label
         filtr = 'labels.cblabel eq ' + label
         max_result = limit if limit is not None and limit < 500 else 500
         max_result = limit if limit is not None and limit < 500 else 500
         response = (self.provider
         response = (self.provider
@@ -1207,7 +1255,10 @@ class GCESnapshotService(BaseSnapshotService):
     @implement(event_pattern="provider.storage.snapshots.delete",
     @implement(event_pattern="provider.storage.snapshots.delete",
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
     def _delete(self, snapshot_id):
     def _delete(self, snapshot_id):
-        gce_snap = self.provider.get_resource('snapshots', snapshot_id)
+        if isinstance(snapshot_id, GCESnapshot):
+            gce_snap = snapshot_id._snapshot
+        else:
+            gce_snap = self.provider.get_resource('snapshots', snapshot_id)
         (self.provider
         (self.provider
              .gce_compute
              .gce_compute
              .snapshots()
              .snapshots()
@@ -1234,10 +1285,15 @@ class GCSBucketService(BaseBucketService):
 
 
     @implement(event_pattern="provider.storage.buckets.find",
     @implement(event_pattern="provider.storage.buckets.find",
                priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
                priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _find(self, name, limit=None, marker=None):
-        """
-        Searches in bucket names for a substring.
-        """
+    def _find(self, limit=None, marker=None, **kwargs):
+        name = kwargs.pop('name', None)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'name'))
+
         buckets = [bucket for bucket in self if name in bucket.name]
         buckets = [bucket for bucket in self if name in bucket.name]
         return ClientPagedResultList(self.provider, buckets, limit=limit,
         return ClientPagedResultList(self.provider, buckets, limit=limit,
                                      marker=marker)
                                      marker=marker)
@@ -1350,22 +1406,11 @@ class GCSBucketObjectService(BaseBucketObjectService):
                                      response.get('nextPageToken'),
                                      response.get('nextPageToken'),
                                      False, data=objects)
                                      False, data=objects)
 
 
-    def find(self, bucket, **kwargs):
-        master_list = []
-        obj_list = self.list(bucket=bucket, limit=500)
-        if obj_list.supports_server_paging:
-            master_list.extend(obj_list)
-            while obj_list.is_truncated:
-                obj_list = self.list(marker=obj_list.marker,
-                                     **kwargs)
-                master_list.extend(obj_list)
-        else:
-            master_list.extend(obj_list.data)
-
+    def find(self, bucket, limit=None, marker=None, **kwargs):
         filters = ['name']
         filters = ['name']
-        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        matches = cb_helpers.generic_find(filters, kwargs, bucket.objects)
         return ClientPagedResultList(self._provider, list(matches),
         return ClientPagedResultList(self._provider, list(matches),
-                                     limit=None, marker=None)
+                                     limit=limit, marker=marker)
 
 
     def _create_object_with_media_body(self, bucket, name, media_body):
     def _create_object_with_media_body(self, bucket, name, media_body):
         response = (self.provider
         response = (self.provider

+ 28 - 15
cloudbridge/cloud/providers/openstack/services.py

@@ -38,6 +38,7 @@ from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.interfaces.exceptions \
 from cloudbridge.cloud.interfaces.exceptions \
     import DuplicateResourceException
     import DuplicateResourceException
+from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import Network
@@ -169,8 +170,9 @@ class OpenStackKeyPairService(BaseKeyPairService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'name'))
 
 
         keypairs = self.provider.nova.keypairs.findall(name=name)
         keypairs = self.provider.nova.keypairs.findall(name=name)
         results = [OpenStackKeyPair(self.provider, kp)
         results = [OpenStackKeyPair(self.provider, kp)
@@ -314,8 +316,9 @@ class OpenStackVolumeService(BaseVolumeService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         log.debug("Searching for an OpenStack Volume with the label %s", label)
         log.debug("Searching for an OpenStack Volume with the label %s", label)
         search_opts = {'name': label}
         search_opts = {'name': label}
@@ -354,7 +357,10 @@ class OpenStackVolumeService(BaseVolumeService):
     @implement(event_pattern="provider.storage.volumes.delete",
     @implement(event_pattern="provider.storage.volumes.delete",
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
                priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
     def _delete(self, volume_id):
     def _delete(self, volume_id):
-        os_vol = self.provider.cinder.volumes.get(volume_id)
+        if isinstance(volume_id, OpenStackVolume):
+            os_vol = volume_id._volume
+        else:
+            os_vol = self.provider.cinder.volumes.get(volume_id)
         os_vol.delete()
         os_vol.delete()
 
 
 
 
@@ -381,8 +387,9 @@ class OpenStackSnapshotService(BaseSnapshotService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         search_opts = {'name': label,  # TODO: Cinder is ignoring name
         search_opts = {'name': label,  # TODO: Cinder is ignoring name
                        'limit': oshelpers.os_result_limit(self.provider),
                        'limit': oshelpers.os_result_limit(self.provider),
@@ -421,7 +428,10 @@ class OpenStackSnapshotService(BaseSnapshotService):
     @implement(event_pattern="provider.storage.snapshots.delete",
     @implement(event_pattern="provider.storage.snapshots.delete",
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
                priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
     def _delete(self, snapshot_id):
     def _delete(self, snapshot_id):
-        os_snap = self.provider.cinder.volume_snapshots.get(snapshot_id)
+        if isinstance(snapshot_id, OpenStackSnapshot):
+            os_snap = snapshot_id._snapshot
+        else:
+            os_snap = self.provider.cinder.volume_snapshots.get(snapshot_id)
         os_snap.delete()
         os_snap.delete()
 
 
 
 
@@ -454,8 +464,9 @@ class OpenStackBucketService(BaseBucketService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'name'))
         _, container_list = self.provider.swift.get_account()
         _, container_list = self.provider.swift.get_account()
         cb_buckets = [OpenStackBucket(self.provider, c)
         cb_buckets = [OpenStackBucket(self.provider, c)
                       for c in container_list
                       for c in container_list
@@ -757,8 +768,9 @@ class OpenStackInstanceService(BaseInstanceService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         search_opts = {'name': label}
         search_opts = {'name': label}
         cb_insts = [
         cb_insts = [
@@ -918,8 +930,9 @@ class OpenStackNetworkService(BaseNetworkService):
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'label'))
+            raise InvalidParamException(
+                "Unrecognised parameters for search: %s. Supported "
+                "attributes: %s" % (kwargs, 'label'))
 
 
         log.debug("Searching for OpenStack Network with label: %s", label)
         log.debug("Searching for OpenStack Network with label: %s", label)
         networks = [OpenStackNetwork(self.provider, network)
         networks = [OpenStackNetwork(self.provider, network)
@@ -1018,7 +1031,7 @@ class OpenStackSubnetService(BaseSubnetService):
             sn = self.provider.networking.subnets.create(
             sn = self.provider.networking.subnets.create(
                 label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL,
                 label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL,
                 cidr_block=OpenStackSubnet.CB_DEFAULT_SUBNET_IPV4RANGE,
                 cidr_block=OpenStackSubnet.CB_DEFAULT_SUBNET_IPV4RANGE,
-                network=net)
+                network=net, zone=zone)
             router = self.provider.networking.routers.get_or_create_default(
             router = self.provider.networking.routers.get_or_create_default(
                 net)
                 net)
             router.attach_subnet(sn)
             router.attach_subnet(sn)

+ 2 - 2
docs/api_docs/cloud/services.rst

@@ -55,7 +55,7 @@ SubnetService
 
 
 FloatingIPService
 FloatingIPService
 -----------------
 -----------------
-.. autoclass:: cloudbridge.cloud.interfaces.services.FloatingIPService
+.. autoclass:: cloudbridge.cloud.interfaces.resources.FloatingIPContainer
     :members:
     :members:
 
 
 RouterService
 RouterService
@@ -65,7 +65,7 @@ RouterService
 
 
 GatewayService
 GatewayService
 -----------------
 -----------------
-.. autoclass:: cloudbridge.cloud.interfaces.services.GatewayService
+.. autoclass:: cloudbridge.cloud.interfaces.resources.GatewayContainer
     :members:
     :members:
 
 
 BucketService
 BucketService

+ 4 - 2
docs/conf.py

@@ -121,7 +121,9 @@ html_theme = 'sphinx_rtd_theme'
 # Theme options are theme-specific and customize the look and feel of a theme
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # further.  For a list of options available for each theme, see the
 # documentation.
 # documentation.
-#html_theme_options = {}
+html_theme_options = {
+    'style_external_links': True
+}
 
 
 # Add any paths that contain custom themes here, relative to this directory.
 # Add any paths that contain custom themes here, relative to this directory.
 #html_theme_path = []
 #html_theme_path = []
@@ -146,7 +148,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
 # Add any paths that contain custom static files (such as style sheets) here,
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+# html_static_path = ['_static']
 
 
 # Add any extra paths that contain custom files (such as robots.txt or
 # Add any extra paths that contain custom files (such as robots.txt or
 # .htaccess) here, relative to this directory. These files are copied
 # .htaccess) here, relative to this directory. These files are copied

Різницю між файлами не показано, бо вона завелика
+ 183 - 16
docs/extras/_images/object_relationships_detailed.svg


+ 1 - 1
docs/getting_started.rst

@@ -214,7 +214,7 @@ listed in order to help map each resource with the service that handles it.
 Note that labeled resources allow to find by label, while unlabeled
 Note that labeled resources allow to find by label, while unlabeled
 resources find by name or their special properties (eg: public_ip for
 resources find by name or their special properties (eg: public_ip for
 floating IPs). For more detailed information on the types of resources and
 floating IPs). For more detailed information on the types of resources and
-their provider mappings, see :doc:`topics/resource_types_and_mappings`.
+their provider mappings, see :doc:`topics/resource_types_and_mapping`.
 
 
 .. code-block:: python
 .. code-block:: python
 
 

+ 4 - 1
docs/topics/aws_mapping.rst

@@ -1,3 +1,6 @@
+Detailed AWS Type and Resource Mappings
+=======================================
+
 AWS Dashboard
 AWS Dashboard
 -------------
 -------------
 AWS has a particular dashboard as resources are found within different
 AWS has a particular dashboard as resources are found within different
@@ -88,7 +91,7 @@ web portal.
    resources in AWS. By default, this label will appear in the first
    resources in AWS. By default, this label will appear in the first
    column.
    column.
 
 
-.. figure:: captures/az-ami-dash.png
+.. figure:: captures/aws-ami-dash.png
    :alt: name, ID, and label properties for AWS EC2 AMIs
    :alt: name, ID, and label properties for AWS EC2 AMIs
 
 
    When an AWS resource allows for an unchangeable name, the CloudBridge
    When an AWS resource allows for an unchangeable name, the CloudBridge

+ 7 - 3
docs/topics/azure_mapping.rst

@@ -1,3 +1,6 @@
+Detailed Azure Type and Resource Mappings
+=========================================
+
 Azure - Labeled Resources
 Azure - Labeled Resources
 -------------------------
 -------------------------
 +---------------------------------------+------------------------+-------+------------------------+------------------------------------+
 +---------------------------------------+------------------------+-------+------------------------+------------------------------------+
@@ -106,8 +109,9 @@ It is also important to note that some of these resources are paid and
 required a plan to use, while others are free but likewise require accepting
 required a plan to use, while others are free but likewise require accepting
 certain terms before being used. These plans and terms are passed and
 certain terms before being used. These plans and terms are passed and
 accepted silently by CloudBridge in order to keep the code cloud-independent.
 accepted silently by CloudBridge in order to keep the code cloud-independent.
-We therefore encourage using the `marketplace website<https://azuremarketplace.microsoft.com/en-us>`_
-to view image and plan details before using them in CloudBridge.
+We therefore encourage using the
+`marketplace website <https://azuremarketplace.microsoft.com/en-us>`_
+to view the images and plan details before using them in CloudBridge.
 
 
 Additionally, Subnets are a particular resource in Azure because they are
 Additionally, Subnets are a particular resource in Azure because they are
 not simply found in the Resource Group like most resources, but are rather
 not simply found in the Resource Group like most resources, but are rather
@@ -214,4 +218,4 @@ Finally, Firewall Rules in Azure differ from traditional unlabeled
 resources by the fact that they do not take a `name` parameter at creation.
 resources by the fact that they do not take a `name` parameter at creation.
 These rules can be found within each Firewall (i.e. Security Group) in the
 These rules can be found within each Firewall (i.e. Security Group) in the
 Azure web portal, and will have an automatically generated `name` of the form
 Azure web portal, and will have an automatically generated `name` of the form
-'cb-rule-[int]'.
+'cb-rule-[int]'.

+ 1 - 1
docs/topics/object_storage.rst

@@ -1,5 +1,5 @@
 Working with object storage
 Working with object storage
-==========================
+===========================
 Object storage provides a simple way to store and retrieve large amounts of
 Object storage provides a simple way to store and retrieve large amounts of
 unstructured data over HTTP. Object Storage is also referred to as Blob (Binary
 unstructured data over HTTP. Object Storage is also referred to as Blob (Binary
 Large OBject) Storage by Azure, and Simple Storage Service (S3) by Amazon.
 Large OBject) Storage by Azure, and Simple Storage Service (S3) by Amazon.

+ 22 - 19
docs/topics/os_mapping.rst

@@ -1,5 +1,8 @@
+Detailed OpenStack Type and Resource Mappings
+=============================================
+
 OpenStack - Labeled Resources
 OpenStack - Labeled Resources
------------------------
+-----------------------------
 +------------------------+------------------------+-----------+----------------+----------+
 +------------------------+------------------------+-----------+----------------+----------+
 | Labeled Resource       | OS Resource Type       | CB ID     | CB Name        | CB Label |
 | Labeled Resource       | OS Resource Type       | CB ID     | CB Name        | CB Label |
 +========================+========================+===========+================+==========+
 +========================+========================+===========+================+==========+
@@ -22,32 +25,32 @@ OpenStack - Labeled Resources
 
 
 The resources listed above are labeled, they thus have both the `name` and
 The resources listed above are labeled, they thus have both the `name` and
 `label` properties in CloudBridge. These resources require a mandatory `label`
 `label` properties in CloudBridge. These resources require a mandatory `label`
-parameter at creation. For all labeled resources, the `label` property in OpenStack
-maps to the Name attribute. However, unlike in Azure or AWS, no resource has
-an unchangeable name by which to identify it in our OpenStack implementation.
-The `name` property will therefore map to the ID, preserving its role as an unchangeable 
-identifier even though not easily readable in this context. Finally, labeled resources
-support a `label` parameter for the `find` method in their corresponding services.
-The below screenshots will help map these properties to OpenStack objects in the
-web portal.
-Additionally, although OpenStack Security Groups are not associated with a
-specific network, such an association is done in CloudBridge, due to its
-necessity in AWS. As such, the VMFirewall creation method requires a
-`network` parameter and the association is accomplished in OpenStack through
-the description, by appending the following string to the user-provided description
-(if any) at creation: "[CB-AUTO-associated-network-id: associated_net_id]"
+parameter at creation. For all labeled resources, the `label` property in
+OpenStack maps to the Name attribute. However, unlike in Azure or AWS, no
+resource has an unchangeable name by which to identify it in our OpenStack
+implementation. The `name` property will therefore map to the ID, preserving
+its role as an unchangeable identifier even though not easily readable in this
+context. Finally, labeled resources support a `label` parameter for the `find`
+method in their corresponding services. The below screenshots will help map
+these properties to OpenStack objects in the web portal. Additionally, although
+OpenStack Security Groups are not associated with a specific network, such an
+association is done in CloudBridge, due to its necessity in AWS. As such, the
+VMFirewall creation method requires a `network` parameter and the association
+is accomplished in OpenStack through the description, by appending the
+following string to the user-provided description (if any) at creation:
+"[CB-AUTO-associated-network-id: associated_net_id]"
 
 
 .. figure:: captures/os-instance-dash.png
 .. figure:: captures/os-instance-dash.png
    :alt: name, ID, and label properties for OS Instances
    :alt: name, ID, and label properties for OS Instances
 
 
    The CloudBridge `name` and `ID` properties map to the unchangeable
    The CloudBridge `name` and `ID` properties map to the unchangeable
    resource ID in OpenStack as resources do not allow for an unchangeable
    resource ID in OpenStack as resources do not allow for an unchangeable
-   name. The `label` property maps to the 'Name' for all resources in 
+   name. The `label` property maps to the 'Name' for all resources in
    OpenStack. By default, this label will appear in the first column.
    OpenStack. By default, this label will appear in the first column.
 
 
 
 
 OpenStack - Unlabeled Resources
 OpenStack - Unlabeled Resources
----------------------------
+-------------------------------
 +-----------------------+------------------------+-------+---------+----------+
 +-----------------------+------------------------+-------+---------+----------+
 | Unlabeled Resource    | OS Resource Type       | CB ID | CB Name | CB Label |
 | Unlabeled Resource    | OS Resource Type       | CB ID | CB Name | CB Label |
 +=======================+========================+=======+=========+==========+
 +=======================+========================+=======+=========+==========+
@@ -76,7 +79,7 @@ services.
 
 
 
 
 OpenStack - Special Unlabeled Resources
 OpenStack - Special Unlabeled Resources
------------------------------------
+---------------------------------------
 +--------------------------+------------------------+-------+------------------------------------------------------------------------+----------+
 +--------------------------+------------------------+-------+------------------------------------------------------------------------+----------+
 | Unlabeled Resource       | OS Resource Type       | CB ID | CB Name                                                                | CB Label |
 | Unlabeled Resource       | OS Resource Type       | CB ID | CB Name                                                                | CB Label |
 +==========================+========================+=======+========================================================================+==========+
 +==========================+========================+=======+========================================================================+==========+
@@ -105,4 +108,4 @@ Finally, Firewall Rules in OpenStack differ from traditional unlabeled resources
 by the fact that they do not take a `name` parameter at creation, and the
 by the fact that they do not take a `name` parameter at creation, and the
 `name` property is automatically generated from the rule's properties, as
 `name` property is automatically generated from the rule's properties, as
 shown above. These rules can be found within each Firewall (i.e. Security
 shown above. These rules can be found within each Firewall (i.e. Security
-Group) in the web portal, and will not have any name in the OpenStack dashboard.
+Group) in the web portal, and will not have any name in the OpenStack dashboard.

+ 1 - 0
docs/topics/overview.rst

@@ -14,4 +14,5 @@ Introductions to all the key parts of CloudBridge you'll need to know:
     Paging and iteration <paging_and_iteration.rst>
     Paging and iteration <paging_and_iteration.rst>
     Using block storage <block_storage.rst>
     Using block storage <block_storage.rst>
     Using object storage <object_storage.rst>
     Using object storage <object_storage.rst>
+    Resource types and mapping <resource_types_and_mapping.rst>
     Troubleshooting <troubleshooting.rst>
     Troubleshooting <troubleshooting.rst>

+ 6 - 6
docs/topics/provider_development.rst

@@ -56,8 +56,8 @@ You should see the tests fail with the following message:
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
-    TypeError: Can't instantiate abstract class GCECloudProvider with abstract
-    methods storage, compute, security, network
+    "TypeError: Can't instantiate abstract class GCECloudProvider with abstract
+    methods storage, compute, security, network."
 
 
 6. Therefore, our next step is to implement these methods. We can start off by
 6. Therefore, our next step is to implement these methods. We can start off by
 implementing these methods in ``provider.py`` and raising a
 implementing these methods in ``provider.py`` and raising a
@@ -125,8 +125,8 @@ tests to fail:
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
-    TypeError: Can't instantiate abstract class GCESecurityService with abstract
-    methods key_pairs, security_groups
+    "TypeError: Can't instantiate abstract class GCESecurityService with abstract
+    methods key_pairs, security_groups."
 
 
 The Abstract Base Classes are doing their job and flagging all methods that
 The Abstract Base Classes are doing their job and flagging all methods that
 need to be implemented.
 need to be implemented.
@@ -180,8 +180,8 @@ Once again, running the tests will complain of missing methods:
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
-    TypeError: Can't instantiate abstract class GCEKeyPairService with abstract
-    methods create, find, get, list
+    "TypeError: Can't instantiate abstract class GCEKeyPairService with abstract
+    methods create, find, get, list."
 
 
 11. Keep implementing the methods till the security service works, and the
 11. Keep implementing the methods till the security service works, and the
 tests pass.
 tests pass.

+ 29 - 18
docs/topics/resource_types_and_mapping.rst

@@ -10,30 +10,33 @@ providers' resources and features. Notably, in order to create a robust and
 conceptually consistent cross-cloud library, resources were separated into
 conceptually consistent cross-cloud library, resources were separated into
 `labeled` and `unlabeled resources,` and were given three main properties:
 `labeled` and `unlabeled resources,` and were given three main properties:
 `ID`, `name`, and `label`.
 `ID`, `name`, and `label`.
+
 The `ID` corresponds to a unique identifier that can be reliably used to
 The `ID` corresponds to a unique identifier that can be reliably used to
 reference a resource. Users can safely use an ID knowing that it will always
 reference a resource. Users can safely use an ID knowing that it will always
 point to the same resource. All resources have an `ID` property, thus making
 point to the same resource. All resources have an `ID` property, thus making
-it the recommended oproperty for reliably identifying a resource.
+it the recommended property for reliably identifying a resource.
+
 The `label` property, conversely, is a modifiable value that does not need
 The `label` property, conversely, is a modifiable value that does not need
-to be unique. Unlike the name property, it is not used to identify a
+to be unique. Unlike the `name` property, it is not used to identify a
 particular resource, but rather label a resource for easier distinction.
 particular resource, but rather label a resource for easier distinction.
-Only labeled resources have the label property, and these resources require
+Only labeled resources have the `label` property, and these resources require
 a `label` parameter be set at creation time.
 a `label` parameter be set at creation time.
-The `name` property corresponds to an unchangeable and unique designation for
-a particular resource. This property is meant to be, in some ways, a more
+
+The `name` property corresponds to an unchangeable and unique designation for a
+particular resource. This property is meant to be, in some ways, a more
 human-readable identifier. Thus, when no conceptually comparable property
 human-readable identifier. Thus, when no conceptually comparable property
-exists for a given resource in a particular provider, the ID is returned
-instead, as is the case for all OpenStack and some AWS resources. Given the 
-discrepancy between providers, using the `name` property is not advisable 
-for cross-cloud usage of the library. Labeled resources will use the label
-given at creation as a prefix to the set name, when this property is separable
-from the ID as is the case in Azure and some AWS resources. Finally, unlabeled
-resources will always support a `name`, and some unlabeled resources will require
-a name parameter at creation. Below is a list of all resources classified by
-whether they support a `label` property.
+exists for a given resource in a particular provider, the `ID` is returned
+instead, as is the case for all OpenStack and some AWS resources. Given the
+discrepancy between providers, using the `name` property is not advisable for
+cross-cloud usage of the library. Labeled resources will use the label given at
+creation as a prefix to the set `name`, when this property is separable from
+the `ID` as is the case in Azure and some AWS resources. Finally, unlabeled
+resources will always support a `name`, and some unlabeled resources will
+require a `name` parameter at creation. Below is a list of all resources
+classified by whether they support a `label` property.
 
 
 +-------------------+---------------------+
 +-------------------+---------------------+
-| Labeled Resources | Unlabeled Resources | 
+| Labeled Resources | Unlabeled Resources |
 +===================+=====================+
 +===================+=====================+
 | Instance          | Key Pair            |
 | Instance          | Key Pair            |
 +-------------------+---------------------+
 +-------------------+---------------------+
@@ -60,6 +63,14 @@ properties to provider objects, as well as some useful dashboard navigation.
 These sections will thus present summary tables delineating the different types of
 These sections will thus present summary tables delineating the different types of
 CloudBridge resources, as well as present some design decisions made to
 CloudBridge resources, as well as present some design decisions made to
 preserve consistency across providers:
 preserve consistency across providers:
--`Detailed Azure Mappings <azure_mapping.html>`_
--`Detailed AWS Mappings <aws_mapping.html>`_
--`Detailed OpenStack Mappings <os_mapping.html>`_
+
+.. toctree::
+   :maxdepth: 1
+
+   Detailed AWS Mappings <aws_mapping.rst>
+   Detailed Azure Mappings <azure_mapping.rst>
+   Detailed OpenStack Mappings <os_mapping.rst>
+
+.. - `Detailed Azure Mappings <azure_mapping.html>`_
+.. - `Detailed AWS Mappings <aws_mapping.html>`_
+.. - `Detailed OpenStack Mappings <os_mapping.html>`_

+ 2 - 1
test/helpers/standard_interface_tests.py

@@ -11,6 +11,7 @@ import tenacity
 
 
 from cloudbridge.cloud.interfaces.exceptions \
 from cloudbridge.cloud.interfaces.exceptions \
     import InvalidNameException
     import InvalidNameException
+from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
 from cloudbridge.cloud.interfaces.resources import LabeledCloudResource
 from cloudbridge.cloud.interfaces.resources import LabeledCloudResource
 from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
 from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
 from cloudbridge.cloud.interfaces.resources import ResultList
 from cloudbridge.cloud.interfaces.resources import ResultList
@@ -100,7 +101,7 @@ def check_find_non_existent(test, service, obj):
         find_objs = service.find(label="random_imagined_obj_name", **args)
         find_objs = service.find(label="random_imagined_obj_name", **args)
     else:
     else:
         find_objs = service.find(name="random_imagined_obj_name")
         find_objs = service.find(name="random_imagined_obj_name")
-    with test.assertRaises(TypeError):
+    with test.assertRaises(InvalidParamException):
         service.find(notaparameter="random_imagined_obj_name")
         service.find(notaparameter="random_imagined_obj_name")
     test.assertTrue(
     test.assertTrue(
         len(find_objs) == 0,
         len(find_objs) == 0,

+ 3 - 0
test/test_event_system.py

@@ -29,6 +29,9 @@ class EventSystemTestCase(unittest.TestCase):
         dispatcher = SimpleEventDispatcher()
         dispatcher = SimpleEventDispatcher()
         handler = dispatcher.observe(event_pattern=EVENT_NAME, priority=1000,
         handler = dispatcher.observe(event_pattern=EVENT_NAME, priority=1000,
                                      callback=my_callback)
                                      callback=my_callback)
+        assert handler.event_pattern == EVENT_NAME
+        assert handler.priority == 1000
+        assert handler.callback == my_callback
         self.assertIsInstance(handler, EventHandler)
         self.assertIsInstance(handler, EventHandler)
         result = dispatcher.dispatch(self, EVENT_NAME, 'first_pos_arg',
         result = dispatcher.dispatch(self, EVENT_NAME, 'first_pos_arg',
                                      a_keyword_arg='another_thing')
                                      a_keyword_arg='another_thing')

Деякі файли не було показано, через те що забагато файлів було змінено