Преглед изворни кода

Merge pull request #184 from CloudVE/middleware-refactor

Middleware refactor
Nuwan Goonasekera пре 7 година
родитељ
комит
dd20d93114

+ 8 - 0
cloudbridge/cloud/base/events.py

@@ -10,6 +10,14 @@ from ..interfaces.exceptions import HandlerException
 log = logging.getLogger(__name__)
 
 
+class PlaceHoldingEventHandler(object):
+    def __init__(self, event_pattern, priority, callback, handler_class):
+        self.event_pattern = event_pattern
+        self.priority = priority
+        self.callback = callback
+        self.handler_class = handler_class
+
+
 class BaseEventHandler(EventHandler):
 
     def __init__(self, event_pattern, priority, callback):

+ 21 - 12
cloudbridge/cloud/base/middleware.py

@@ -8,7 +8,7 @@ import six
 from ..base.events import ImplementingEventHandler
 from ..base.events import InterceptingEventHandler
 from ..base.events import ObservingEventHandler
-from ..interfaces.events import EventHandler
+from ..base.events import PlaceHoldingEventHandler
 from ..interfaces.exceptions import CloudBridgeBaseException
 from ..interfaces.exceptions import HandlerException
 from ..interfaces.middleware import Middleware
@@ -22,8 +22,8 @@ def intercept(event_pattern, priority):
         # Mark function as having an event_handler so we can discover it
         # The callback cannot be set to f as it is not bound yet and will be
         # set during auto discovery
-        f.__event_handler = InterceptingEventHandler(
-            event_pattern, priority, f)
+        f.__event_handler = PlaceHoldingEventHandler(
+            event_pattern, priority, f, InterceptingEventHandler)
         return f
     return deco
 
@@ -33,8 +33,8 @@ def observe(event_pattern, priority):
         # Mark function as having an event_handler so we can discover it
         # The callback cannot be set to f as it is not bound yet and will be
         # set during auto discovery
-        f.__event_handler = ObservingEventHandler(
-            event_pattern, priority, f)
+        f.__event_handler = PlaceHoldingEventHandler(
+            event_pattern, priority, f, ObservingEventHandler)
         return f
     return deco
 
@@ -45,13 +45,13 @@ def implement(event_pattern, priority):
         # The callback will be unbound since we do not have access to `self`
         # yet, and must be bound before invocation. This binding is done
         # during middleware auto discovery
-        f.__event_handler = ImplementingEventHandler(
-            event_pattern, priority, f)
+        f.__event_handler = PlaceHoldingEventHandler(
+            event_pattern, priority, f, ImplementingEventHandler)
         return f
     return deco
 
 
-def dispatch_event(event):
+def dispatch(event, priority):
     """
     The event decorator combines the functionality of the implement decorator
     and a manual event dispatch into a single decorator.
@@ -71,7 +71,8 @@ def dispatch_event(event):
         # Mark function as having an event_handler so we can discover it
         # The callback f is unbound and will be bound during middleware
         # auto discovery
-        wrapper.__event_handler = ImplementingEventHandler(event, 2500, f)
+        wrapper.__event_handler = PlaceHoldingEventHandler(
+            event, priority, f, ImplementingEventHandler)
         return wrapper
     return deco
 
@@ -144,11 +145,19 @@ class BaseMiddleware(Middleware):
         discovered_handlers = []
         for _, func in getmembers_static(class_or_obj, inspect.ismethod):
             handler = getattr(func, "__event_handler", None)
-            if handler and isinstance(handler, EventHandler):
+            if handler and isinstance(handler, PlaceHoldingEventHandler):
+                # create a new handler that mimics the original one,
+                # essentially deep-copying the handler, so that the bound
+                # method is never stored in the function itself, preventing
+                # further bonding
+                new_handler = handler.handler_class(handler.event_pattern,
+                                                    handler.priority,
+                                                    handler.callback)
                 # Bind the currently unbound method
                 # and set the bound method as the callback
-                handler.callback = handler.callback.__get__(class_or_obj)
-                discovered_handlers.append(handler)
+                new_handler.callback = (new_handler.callback
+                                                   .__get__(class_or_obj))
+                discovered_handlers.append(new_handler)
         return discovered_handlers
 
 

+ 14 - 10
cloudbridge/cloud/base/resources.py

@@ -323,6 +323,9 @@ class BaseInstance(BaseCloudResource, BaseObjectLifeCycleMixin, Instance):
             timeout=timeout,
             interval=interval)
 
+    def delete(self):
+        self._provider.compute.instances.delete(self)
+
 
 class BaseLaunchConfig(LaunchConfig):
 
@@ -534,15 +537,7 @@ class BaseKeyPair(BaseCloudResource, KeyPair):
         self._private_material = value
 
     def delete(self):
-        """
-        Delete this KeyPair.
-
-        :rtype: bool
-        :return: True if successful, otherwise False.
-        """
-        # This implementation assumes the `delete` method exists across
-        #  multiple providers.
-        self._key_pair.delete()
+        self._provider.security.key_pairs.delete(self)
 
 
 class BaseVMFirewall(BaseCloudResource, VMFirewall):
@@ -591,7 +586,7 @@ class BaseVMFirewall(BaseCloudResource, VMFirewall):
         """
         Delete this VM firewall.
         """
-        return self._vm_firewall.delete()
+        return self._provider.security.vm_firewalls.delete(self)
 
 
 class BaseVMFirewallRuleContainer(BasePageableObjectMixin,
@@ -835,6 +830,9 @@ class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
             timeout=timeout,
             interval=interval)
 
+    def delete(self):
+        self._provider.networking.networks.delete(self)
+
     def create_subnet(self, label, cidr_block, zone):
         return self._provider.networking.subnets.create(
             label=label, network=self, cidr_block=cidr_block, zone=zone)
@@ -873,6 +871,9 @@ class BaseSubnet(BaseCloudResource, BaseObjectLifeCycleMixin, Subnet):
             timeout=timeout,
             interval=interval)
 
+    def delete(self):
+        self._provider.networking.subnets.delete(self)
+
 
 class BaseFloatingIPContainer(FloatingIPContainer, BasePageableObjectMixin):
 
@@ -938,6 +939,9 @@ class BaseRouter(BaseCloudResource, Router):
                 self._provider == other._provider and
                 self.id == other.id)
 
+    def delete(self):
+        self._provider.networking.routers.delete(self)
+
 
 class BaseInternetGateway(BaseCloudResource, BaseObjectLifeCycleMixin,
                           InternetGateway):

+ 38 - 193
cloudbridge/cloud/base/services.py

@@ -4,7 +4,7 @@ Base implementation for services available through a provider
 import logging
 
 import cloudbridge.cloud.base.helpers as cb_helpers
-from cloudbridge.cloud.base.middleware import implement
+from cloudbridge.cloud.base.middleware import dispatch
 from cloudbridge.cloud.base.resources import BaseBucket
 from cloudbridge.cloud.base.resources import BaseNetwork
 from cloudbridge.cloud.base.resources import BaseRouter
@@ -13,7 +13,6 @@ from cloudbridge.cloud.interfaces.exceptions import \
     InvalidConfigurationException
 from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
 from cloudbridge.cloud.interfaces.resources import Network
-from cloudbridge.cloud.interfaces.resources import Router
 from cloudbridge.cloud.interfaces.services import BucketObjectService
 from cloudbridge.cloud.interfaces.services import BucketService
 from cloudbridge.cloud.interfaces.services import CloudService
@@ -53,40 +52,9 @@ class BaseCloudService(CloudService):
     def provider(self):
         return self._provider
 
-    def dispatch(self, sender, event, *args, **kwargs):
-        return self._provider.events.dispatch(sender, event, *args, **kwargs)
-
-    def _generate_event_pattern(self, func_name):
-        return ".".join((self._service_event_pattern, func_name))
-
-    def observe_function(self, func_name, priority, callback):
-        event_pattern = self._generate_event_pattern(func_name)
-        self.provider.events.observe(event_pattern, priority, callback)
-
-    def intercept_function(self, func_name, priority, callback):
-        event_pattern = self._generate_event_pattern(func_name)
-        self.provider.events.intercept(event_pattern, priority, callback)
-
-    def implement_function(self, func_name, priority, callback):
-        event_pattern = self._generate_event_pattern(func_name)
-        self.provider.events.implement(event_pattern, priority, callback)
-
-    def dispatch_function(self, sender, func_name, *args, **kwargs):
-        """
-        Emits the event corresponding to the given function name for the
-        current service
-
-        :type sender: CloudService
-        :param sender: The CloudBridge Service object sending the emit signal
-        :type func_name: str
-        :param func_name: The name of the function to be emitted. e.g.: 'get'
-        :type args: CloudService
-
-        :return:  The return value resulting from the handler chain invocations
-        """
-        full_event_name = self._generate_event_pattern(func_name)
-        return self._provider.events.dispatch(sender, full_event_name,
-                                              *args, **kwargs)
+    @property
+    def events(self):
+        return self._provider.events
 
 
 class BaseSecurityService(SecurityService, BaseCloudService):
@@ -102,46 +70,6 @@ class BaseKeyPairService(
         super(BaseKeyPairService, self).__init__(provider)
         self._service_event_pattern += ".security.key_pairs"
 
-    def get(self, key_pair_id):
-        return self.dispatch(self, "provider.security.key_pairs.get",
-                             key_pair_id)
-
-    def list(self, limit=None, marker=None):
-        return self.dispatch(self, "provider.security.key_pairs.list",
-                             limit=limit, marker=marker)
-
-    def find(self, **kwargs):
-        return self.dispatch(self, "provider.security.key_pairs.find",
-                             **kwargs)
-
-    def create(self, name, public_key_material=None):
-        return self.dispatch(self, "provider.security.key_pairs.create",
-                             name, public_key_material=public_key_material)
-
-    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.
-
-        :type key_pair_id: str
-        :param key_pair_id: The id of the key pair to be deleted.
-
-        :rtype: ``bool``
-        :return:  ``True`` if the key does not exist. Note that this implies
-                  that the key may not have been deleted by this method but
-                  instead has not existed in the first place.
-        """
-        log.info("Deleting the existing key pair %s", key_pair_id)
-        kp = self.get(key_pair_id)
-        if kp:
-            kp.delete()
-        return True
-
 
 class BaseVMFirewallService(
         BasePageableObjectMixin, VMFirewallService, BaseCloudService):
@@ -150,6 +78,8 @@ class BaseVMFirewallService(
         super(BaseVMFirewallService, self).__init__(provider)
         self._service_event_pattern += ".security.vm_firewalls"
 
+    @dispatch(event="provider.security.vm_firewalls.find",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         obj_list = self
         filters = ['label']
@@ -178,27 +108,6 @@ class BaseVolumeService(
         super(BaseVolumeService, self).__init__(provider)
         self._service_event_pattern += ".storage.volumes"
 
-    def get(self, volume_id):
-        return self.dispatch(self, "provider.storage.volumes.get",
-                             volume_id)
-
-    def list(self, limit=None, marker=None):
-        return self.dispatch(self, "provider.storage.volumes.list",
-                             limit=limit, marker=marker)
-
-    def find(self, **kwargs):
-        return self.dispatch(self, "provider.storage.volumes.find",
-                             **kwargs)
-
-    def create(self, label, size, zone, snapshot=None, description=None):
-        return self.dispatch(self, "provider.storage.volumes.create",
-                             label, size, zone, snapshot=snapshot,
-                             description=description)
-
-    def delete(self, volume):
-        return self.dispatch(self, "provider.storage.volumes.delete",
-                             volume)
-
 
 class BaseSnapshotService(
         BasePageableObjectMixin, SnapshotService, BaseCloudService):
@@ -207,26 +116,6 @@ class BaseSnapshotService(
         super(BaseSnapshotService, self).__init__(provider)
         self._service_event_pattern += ".storage.snapshots"
 
-    def get(self, snapshot_id):
-        return self.dispatch(self, "provider.storage.snapshots.get",
-                             snapshot_id)
-
-    def list(self, limit=None, marker=None):
-        return self.dispatch(self, "provider.storage.snapshots.list",
-                             limit=limit, marker=marker)
-
-    def find(self, **kwargs):
-        return self.dispatch(self, "provider.storage.snapshots.find",
-                             **kwargs)
-
-    def create(self, label, volume, description=None):
-        return self.dispatch(self, "provider.storage.snapshots.create",
-                             label, volume=volume, description=description)
-
-    def delete(self, snapshot):
-        return self.dispatch(self, "provider.storage.snapshots.delete",
-                             snapshot)
-
 
 class BaseBucketService(
         BasePageableObjectMixin, BucketService, BaseCloudService):
@@ -237,9 +126,9 @@ class BaseBucketService(
 
     # Generic find will be used for providers where we have not implemented
     # provider-specific querying for find method
-    @implement(event_pattern="provider.storage.buckets.find",
-               priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.storage.buckets.find",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         obj_list = self
         filters = ['name']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
@@ -253,60 +142,6 @@ class BaseBucketService(
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
-    def get(self, bucket_id):
-        """
-        Returns a bucket given its ID. Returns ``None`` if the bucket
-        does not exist.
-
-        :type bucket_id: str
-        :param bucket_id: The id of the desired bucket.
-
-        :rtype: ``Bucket``
-        :return:  ``None`` is returned if the bucket does not exist, and
-                  the bucket's provider-specific CloudBridge object is
-                  returned if the bucket is found.
-        """
-        return self.dispatch(self, "provider.storage.buckets.get", bucket_id)
-
-    def find(self, **kwargs):
-        """
-        Returns a list of buckets filtered by the given keyword arguments.
-        Accepted search arguments are: 'name'
-        """
-        return self.dispatch(self, "provider.storage.buckets.find", **kwargs)
-
-    def list(self, limit=None, marker=None):
-        """
-        List all buckets.
-        """
-        return self.dispatch(self, "provider.storage.buckets.list",
-                             limit=limit, marker=marker)
-
-    def create(self, name, location=None):
-        """
-        Create a new bucket.
-
-        :type name: str
-        :param name: The name of the bucket to be created. Note that names
-                     must be unique, and are unchangeable.
-
-        :rtype: ``Bucket``
-        :return:  The created bucket's provider-specific CloudBridge object.
-        """
-        BaseBucket.assert_valid_resource_name(name)
-        return self.dispatch(self, "provider.storage.buckets.create",
-                             name, location=location)
-
-    def delete(self, bucket_id):
-        """
-        Delete an existing bucket.
-
-        :type bucket_id: str
-        :param bucket_id: The ID of the bucket to be deleted.
-        """
-        return self.dispatch(self, "provider.storage.buckets.delete",
-                             bucket_id)
-
 
 class BaseBucketObjectService(
         BasePageableObjectMixin, BucketObjectService, BaseCloudService):
@@ -372,10 +207,14 @@ class BaseVMTypeService(
         super(BaseVMTypeService, self).__init__(provider)
         self._service_event_pattern += ".compute.vm_types"
 
+    @dispatch(event="provider.compute.vm_types.get",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
     def get(self, vm_type_id):
         vm_type = (t for t in self if t.id == vm_type_id)
         return next(vm_type, None)
 
+    @dispatch(event="provider.compute.vm_types.find",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         obj_list = self
         filters = ['name']
@@ -390,6 +229,8 @@ class BaseRegionService(
         super(BaseRegionService, self).__init__(provider)
         self._service_event_pattern += ".compute.regions"
 
+    @dispatch(event="provider.compute.regions.find",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         obj_list = self
         filters = ['name']
@@ -415,12 +256,6 @@ class BaseNetworkService(
         return [subnet for subnet in self.provider.subnets
                 if subnet.network_id == self.id]
 
-    def delete(self, network_id):
-        network = self.get(network_id)
-        if network:
-            log.info("Deleting network %s", network_id)
-            network.delete()
-
     def get_or_create_default(self):
         networks = self.provider.networking.networks.find(
             label=BaseNetwork.CB_DEFAULT_NETWORK_LABEL)
@@ -433,6 +268,22 @@ class BaseNetworkService(
             return self.provider.networking.networks.create(
                 BaseNetwork.CB_DEFAULT_NETWORK_LABEL, '10.0.0.0/16')
 
+    @dispatch(event="provider.networking.networks.find",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
+        obj_list = self
+        filters = ['label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
+
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
+
 
 class BaseSubnetService(
         BasePageableObjectMixin, SubnetService, BaseCloudService):
@@ -441,8 +292,13 @@ class BaseSubnetService(
         super(BaseSubnetService, self).__init__(provider)
         self._service_event_pattern += ".networking.subnets"
 
-    def find(self, **kwargs):
-        obj_list = self
+    @dispatch(event="provider.networking.subnets.find",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
+    def find(self, network=None, **kwargs):
+        if not network:
+            obj_list = self
+        else:
+            obj_list = network.subnets
         filters = ['label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         return ClientPagedResultList(self._provider, list(matches))
@@ -467,17 +323,6 @@ class BaseRouterService(
         super(BaseRouterService, self).__init__(provider)
         self._service_event_pattern += ".networking.routers"
 
-    def delete(self, router):
-        if isinstance(router, Router):
-            log.info("Router %s successful deleted.", router)
-            router.delete()
-        else:
-            log.info("Getting router %s", router)
-            router = self.get(router)
-            if router:
-                log.info("Router %s successful deleted.", router)
-                router.delete()
-
     def get_or_create_default(self, network):
         net_id = network.id if isinstance(network, Network) else network
         routers = self.provider.networking.routers.find(

+ 10 - 10
cloudbridge/cloud/interfaces/services.py

@@ -671,12 +671,12 @@ class NetworkService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def delete(self, network_id):
+    def delete(self, network):
         """
         Delete an existing Network.
 
-        :type network_id: ``str``
-        :param network_id: The ID of the network to be deleted.
+        :type network: ``str`` or :class:`.Network`
+        :param network: The object or id of the network to be deleted.
         """
         pass
 
@@ -1188,12 +1188,12 @@ class KeyPairService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def delete(self, key_pair_id):
+    def delete(self, key_pair):
         """
-        Delete an existing VMFirewall.
+        Delete an existing keypair.
 
-        :type key_pair_id: str
-        :param key_pair_id: The id of the key pair to be deleted.
+        :type key_pair: ``str`` or :class:`.KeyPair`
+        :param key_pair: The object or id of the key pair to be deleted.
 
         :rtype: ``bool``
         :return:  ``True`` if the key does not exist, ``False`` otherwise. Note
@@ -1274,12 +1274,12 @@ class VMFirewallService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def delete(self, group_id):
+    def delete(self, firewall):
         """
         Delete an existing VMFirewall.
 
-        :type group_id: str
-        :param group_id: The VM firewall ID to be deleted.
+        :type firewall: ``str`` or :class:`.VMFirewall`
+        :param firewall: The object or VM firewall ID to be deleted.
         """
         pass
 

+ 27 - 0
cloudbridge/cloud/providers/aws/helpers.py

@@ -96,12 +96,39 @@ class BotoGenericService(object):
             if sr.resource.model.name == collection_model.resource.model.name)
         return getattr(self.boto_conn, resource_model.name)
 
+    def get_raw(self, resource_id):
+        """
+        Returns a single resource.
+
+        :type resource_id: ``str``
+        :param resource_id: ID of the boto resource to fetch
+
+        :returns An unwrapped AWS resource
+        """
+        try:
+            log.debug("Retrieving resource: %s with id: %s",
+                      self.boto_collection_model.name, resource_id)
+            obj = self.boto_resource(resource_id)
+            obj.load()
+            log.debug("Successfully Retrieved: %s", obj)
+            return obj
+        except ClientError as exc:
+            error_code = exc.response['Error']['Code']
+            if any(status in error_code for status in
+                   ('NotFound', 'InvalidParameterValue', 'Malformed', '404')):
+                log.debug("Object not found: %s", resource_id)
+                return None
+            else:
+                raise exc
+
     def get(self, resource_id):
         """
         Returns a single resource.
 
         :type resource_id: ``str``
         :param resource_id: ID of the boto resource to fetch
+
+        :returns A CloudBridge wrapped resource
         """
         try:
             log.debug("Retrieving resource: %s with id: %s",

+ 0 - 12
cloudbridge/cloud/providers/aws/resources.py

@@ -280,9 +280,6 @@ class AWSInstance(BaseInstance):
     def reboot(self):
         self._ec2_instance.reboot()
 
-    def delete(self):
-        self._ec2_instance.terminate()
-
     @property
     def image_id(self):
         return self._ec2_instance.image_id
@@ -974,9 +971,6 @@ class AWSNetwork(BaseNetwork):
     def cidr_block(self):
         return self._vpc.cidr_block
 
-    def delete(self):
-        self._vpc.delete()
-
     @property
     def subnets(self):
         return [AWSSubnet(self._provider, s) for s in self._vpc.subnets.all()]
@@ -1044,9 +1038,6 @@ class AWSSubnet(BaseSubnet):
         return AWSPlacementZone(self._provider, self._subnet.availability_zone,
                                 self._provider.region_name)
 
-    def delete(self):
-        self._subnet.delete()
-
     @property
     def state(self):
         if self._unknown_state:
@@ -1162,9 +1153,6 @@ class AWSRouter(BaseRouter):
     def network_id(self):
         return self._route_table.vpc_id
 
-    def delete(self):
-        self._route_table.delete()
-
     def attach_subnet(self, subnet):
         subnet_id = subnet.id if isinstance(subnet, AWSSubnet) else subnet
         self._route_table.associate_with_subnet(SubnetId=subnet_id)

+ 207 - 165
cloudbridge/cloud/providers/aws/services.py

@@ -10,7 +10,7 @@ import cachetools
 import requests
 
 import cloudbridge.cloud.base.helpers as cb_helpers
-from cloudbridge.cloud.base.middleware import implement
+from cloudbridge.cloud.base.middleware import dispatch
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketObjectService
 from cloudbridge.cloud.base.services import BaseBucketService
@@ -89,20 +89,20 @@ class AWSKeyPairService(BaseKeyPairService):
                                   cb_resource=AWSKeyPair,
                                   boto_collection_name='key_pairs')
 
-    @implement(event_pattern="provider.security.key_pairs.get",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _get(self, key_pair_id):
+    @dispatch(event="provider.security.key_pairs.get",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def get(self, key_pair_id):
         log.debug("Getting Key Pair Service %s", key_pair_id)
         return self.svc.get(key_pair_id)
 
-    @implement(event_pattern="provider.security.key_pairs.list",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
+    @dispatch(event="provider.security.key_pairs.list",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
-    @implement(event_pattern="provider.security.key_pairs.find",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.security.key_pairs.find",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         name = kwargs.pop('name', None)
 
         # All kwargs should have been popped at this time.
@@ -114,10 +114,9 @@ class AWSKeyPairService(BaseKeyPairService):
         log.debug("Searching for Key Pair %s", name)
         return self.svc.find(filter_name='key-name', filter_value=name)
 
-    @implement(event_pattern="provider.security.key_pairs.create",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _create(self, name, public_key_material=None):
-        log.debug("Creating Key Pair Service %s", name)
+    @dispatch(event="provider.security.key_pairs.create",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def create(self, name, public_key_material=None):
         AWSKeyPair.assert_valid_resource_name(name)
         private_key = None
         if not public_key_material:
@@ -134,6 +133,14 @@ class AWSKeyPairService(BaseKeyPairService):
             else:
                 raise e
 
+    @dispatch(event="provider.security.key_pairs.delete",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def delete(self, kp):
+        key_pair = kp if isinstance(kp, AWSKeyPair) else self.get(kp)
+        if key_pair:
+            # pylint:disable=protected-access
+            key_pair._key_pair.delete()
+
 
 class AWSVMFirewallService(BaseVMFirewallService):
 
@@ -143,19 +150,21 @@ class AWSVMFirewallService(BaseVMFirewallService):
                                   cb_resource=AWSVMFirewall,
                                   boto_collection_name='security_groups')
 
-    def get(self, firewall_id):
-        log.debug("Getting Firewall Service with the id: %s", firewall_id)
-        return self.svc.get(firewall_id)
+    @dispatch(event="provider.security.vm_firewalls.get",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
+    def get(self, vm_firewall_id):
+        log.debug("Getting Firewall Service with the id: %s", vm_firewall_id)
+        return self.svc.get(vm_firewall_id)
 
+    @dispatch(event="provider.security.vm_firewalls.list",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
     @cb_helpers.deprecated_alias(network_id='network')
+    @dispatch(event="provider.security.vm_firewalls.create",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network, description=None):
-        log.debug("Creating Firewall Service with the parameters "
-                  "[label: %s id: %s description: %s]", label, network,
-                  description)
-        AWSVMFirewall.assert_valid_resource_label(label)
         name = AWSVMFirewall._generate_name_from_label(label, 'cb-fw')
         network_id = network.id if isinstance(network, Network) else network
         obj = self.svc.create('create_security_group', GroupName=name,
@@ -164,6 +173,8 @@ class AWSVMFirewallService(BaseVMFirewallService):
         obj.label = label
         return obj
 
+    @dispatch(event="provider.security.vm_firewalls.find",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         # Filter by name or label
         label = kwargs.pop('label', None)
@@ -176,11 +187,13 @@ class AWSVMFirewallService(BaseVMFirewallService):
         return self.svc.find(filter_name='tag:Name',
                              filter_value=label)
 
-    def delete(self, firewall_id):
-        log.info("Deleting Firewall Service with the id %s", firewall_id)
-        firewall = self.svc.get(firewall_id)
+    @dispatch(event="provider.security.vm_firewalls.delete",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
+    def delete(self, vmf):
+        firewall = vmf if isinstance(vmf, AWSVMFirewall) else self.get(vmf)
         if firewall:
-            firewall.delete()
+            # pylint:disable=protected-access
+            firewall._vm_firewall.delete()
 
 
 class AWSStorageService(BaseStorageService):
@@ -219,16 +232,14 @@ class AWSVolumeService(BaseVolumeService):
                                   cb_resource=AWSVolume,
                                   boto_collection_name='volumes')
 
-    @implement(event_pattern="provider.storage.volumes.get",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _get(self, volume_id):
-        log.debug("Getting AWS Volume Service with the id: %s",
-                  volume_id)
+    @dispatch(event="provider.storage.volumes.get",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def get(self, volume_id):
         return self.svc.get(volume_id)
 
-    @implement(event_pattern="provider.storage.volumes.find",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.storage.volumes.find",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         label = kwargs.pop('label', None)
 
         # All kwargs should have been popped at this time.
@@ -240,20 +251,14 @@ class AWSVolumeService(BaseVolumeService):
         log.debug("Searching for AWS Volume Service %s", label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
 
-    @implement(event_pattern="provider.storage.volumes.list",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
+    @dispatch(event="provider.storage.volumes.list",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
-    @implement(event_pattern="provider.storage.volumes.create",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _create(self, label, size, zone, snapshot=None, description=None):
-        log.debug("Creating AWS Volume Service with the parameters "
-                  "[label: %s size: %s zone: %s snapshot: %s "
-                  "description: %s]", label, size, zone, snapshot,
-                  description)
-        AWSVolume.assert_valid_resource_label(label)
-
+    @dispatch(event="provider.storage.volumes.create",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, size, zone, snapshot=None, description=None):
         zone_id = zone.id if isinstance(zone, PlacementZone) else zone
         snapshot_id = snapshot.id if isinstance(
             snapshot, AWSSnapshot) and snapshot else snapshot
@@ -268,12 +273,13 @@ class AWSVolumeService(BaseVolumeService):
             cb_vol.description = description
         return cb_vol
 
-    @implement(event_pattern="provider.storage.volumes.delete",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, volume):
-        cb_vol = volume if isinstance(volume, AWSVolume) else self.get(volume)
-        # pylint:disable=protected-access
-        cb_vol._volume.delete()
+    @dispatch(event="provider.storage.volumes.delete",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def delete(self, vol):
+        volume = vol if isinstance(vol, AWSVolume) else self.get(vol)
+        if volume:
+            # pylint:disable=protected-access
+            volume._volume.delete()
 
 
 class AWSSnapshotService(BaseSnapshotService):
@@ -284,16 +290,14 @@ class AWSSnapshotService(BaseSnapshotService):
                                   cb_resource=AWSSnapshot,
                                   boto_collection_name='snapshots')
 
-    @implement(event_pattern="provider.storage.snapshots.get",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _get(self, snapshot_id):
-        log.debug("Getting AWS Snapshot Service with the id: %s",
-                  snapshot_id)
+    @dispatch(event="provider.storage.snapshots.get",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def get(self, snapshot_id):
         return self.svc.get(snapshot_id)
 
-    @implement(event_pattern="provider.storage.snapshots.find",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.storage.snapshots.find",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         # Filter by description or label
         label = kwargs.get('label', None)
 
@@ -308,23 +312,15 @@ class AWSSnapshotService(BaseSnapshotService):
         filters = ['label']
         return cb_helpers.generic_find(filters, kwargs, obj_list)
 
-    @implement(event_pattern="provider.storage.snapshots.list",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
+    @dispatch(event="provider.storage.snapshots.list",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker,
                              OwnerIds=['self'])
 
-    @implement(event_pattern="provider.storage.snapshots.create",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _create(self, label, volume, description=None):
-        """
-        Creates a new snapshot of a given volume.
-        """
-        log.debug("Creating a new AWS snapshot Service with the "
-                  "parameters [label: %s volume: %s description: %s]",
-                  label, volume, description)
-        AWSSnapshot.assert_valid_resource_label(label)
-
+    @dispatch(event="provider.storage.snapshots.create",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, volume, description=None):
         volume_id = volume.id if isinstance(volume, AWSVolume) else volume
 
         cb_snap = self.svc.create('create_snapshot', VolumeId=volume_id)
@@ -335,13 +331,13 @@ class AWSSnapshotService(BaseSnapshotService):
             cb_snap.description = description
         return cb_snap
 
-    @implement(event_pattern="provider.storage.snapshots.delete",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, snapshot):
-        cb_snap = (snapshot if isinstance(snapshot, AWSSnapshot)
-                   else self.get(snapshot))
-        # pylint:disable=protected-access
-        cb_snap._snapshot.delete()
+    @dispatch(event="provider.storage.snapshots.delete",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def delete(self, snap):
+        snapshot = snap if isinstance(snap, AWSSnapshot) else self.get(snap)
+        if snapshot:
+            # pylint:disable=protected-access
+            snapshot._snapshot.delete()
 
 
 class AWSBucketService(BaseBucketService):
@@ -352,9 +348,9 @@ class AWSBucketService(BaseBucketService):
                                  cb_resource=AWSBucket,
                                  boto_collection_name='buckets')
 
-    @implement(event_pattern="provider.storage.buckets.get",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _get(self, bucket_id):
+    @dispatch(event="provider.storage.buckets.get",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def get(self, bucket_id):
         """
         Returns a bucket given its ID. Returns ``None`` if the bucket
         does not exist.
@@ -382,14 +378,14 @@ class AWSBucketService(BaseBucketService):
         # For all other responses, it's assumed that the bucket does not exist.
         return None
 
-    @implement(event_pattern="provider.storage.buckets.list",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit, marker):
+    @dispatch(event="provider.storage.buckets.list",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
-    @implement(event_pattern="provider.storage.buckets.create",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _create(self, name, location):
+    @dispatch(event="provider.storage.buckets.create",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def create(self, name, location=None):
         AWSBucket.assert_valid_resource_name(name)
         location = location or self.provider.region_name
         # Due to an API issue in S3, specifying us-east-1 as a
@@ -422,12 +418,13 @@ class AWSBucketService(BaseBucketService):
                 else:
                     raise
 
-    @implement(event_pattern="provider.storage.buckets.delete",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, bucket_id):
-        bucket = self._get(bucket_id)
-        if bucket:
-            bucket._bucket.delete()
+    @dispatch(event="provider.storage.buckets.delete",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def delete(self, bucket):
+        b = bucket if isinstance(bucket, AWSBucket) else self.get(bucket)
+        if b:
+            # pylint:disable=protected-access
+            b._bucket.delete()
 
 
 class AWSBucketObjectService(BaseBucketObjectService):
@@ -551,56 +548,6 @@ class AWSInstanceService(BaseInstanceService):
                                   cb_resource=AWSInstance,
                                   boto_collection_name='instances')
 
-    def create(self, label, image, vm_type, subnet, zone,
-               key_pair=None, vm_firewalls=None, user_data=None,
-               launch_config=None, **kwargs):
-        log.debug("Creating AWS Instance Service with the params "
-                  "[label: %s image: %s type: %s subnet: %s zone: %s "
-                  "key pair: %s firewalls: %s user data: %s config %s "
-                  "others: %s]", label, image, vm_type, subnet, zone,
-                  key_pair, vm_firewalls, user_data, launch_config, kwargs)
-        AWSInstance.assert_valid_resource_label(label)
-
-        image_id = image.id if isinstance(image, MachineImage) else image
-        vm_size = vm_type.id if \
-            isinstance(vm_type, VMType) else vm_type
-        subnet = (self.provider.networking.subnets.get(subnet)
-                  if isinstance(subnet, str) else subnet)
-        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
-        key_pair_name = key_pair.name if isinstance(
-            key_pair,
-            KeyPair) else key_pair
-        if launch_config:
-            bdm = self._process_block_device_mappings(launch_config)
-        else:
-            bdm = None
-
-        subnet_id, zone_id, vm_firewall_ids = \
-            self._resolve_launch_options(subnet, zone_id, vm_firewalls)
-
-        placement = {'AvailabilityZone': zone_id} if zone_id else None
-        inst = self.svc.create('create_instances',
-                               ImageId=image_id,
-                               MinCount=1,
-                               MaxCount=1,
-                               KeyName=key_pair_name,
-                               SecurityGroupIds=vm_firewall_ids or None,
-                               UserData=str(user_data) or None,
-                               InstanceType=vm_size,
-                               Placement=placement,
-                               BlockDeviceMappings=bdm,
-                               SubnetId=subnet_id
-                               )
-        if inst and len(inst) == 1:
-            # Wait until the resource exists
-            # pylint:disable=protected-access
-            inst[0]._wait_till_exists()
-            # Tag the instance w/ the name
-            inst[0].label = label
-            return inst[0]
-        raise ValueError(
-            'Expected a single object response, got a list: %s' % inst)
-
     def _resolve_launch_options(self, subnet=None, zone_id=None,
                                 vm_firewalls=None):
         """
@@ -686,9 +633,59 @@ class AWSInstanceService(BaseInstanceService):
     def create_launch_config(self):
         return AWSLaunchConfig(self.provider)
 
+    @dispatch(event="provider.compute.instances.create",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, image, vm_type, subnet, zone,
+               key_pair=None, vm_firewalls=None, user_data=None,
+               launch_config=None, **kwargs):
+
+        image_id = image.id if isinstance(image, MachineImage) else image
+        vm_size = vm_type.id if \
+            isinstance(vm_type, VMType) else vm_type
+        subnet = (self.provider.networking.subnets.get(subnet)
+                  if isinstance(subnet, str) else subnet)
+        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
+        key_pair_name = key_pair.name if isinstance(
+            key_pair,
+            KeyPair) else key_pair
+        if launch_config:
+            bdm = self._process_block_device_mappings(launch_config)
+        else:
+            bdm = None
+
+        subnet_id, zone_id, vm_firewall_ids = \
+            self._resolve_launch_options(subnet, zone_id, vm_firewalls)
+
+        placement = {'AvailabilityZone': zone_id} if zone_id else None
+        inst = self.svc.create('create_instances',
+                               ImageId=image_id,
+                               MinCount=1,
+                               MaxCount=1,
+                               KeyName=key_pair_name,
+                               SecurityGroupIds=vm_firewall_ids or None,
+                               UserData=str(user_data) or None,
+                               InstanceType=vm_size,
+                               Placement=placement,
+                               BlockDeviceMappings=bdm,
+                               SubnetId=subnet_id
+                               )
+        if inst and len(inst) == 1:
+            # Wait until the resource exists
+            # pylint:disable=protected-access
+            inst[0]._wait_till_exists()
+            # Tag the instance w/ the name
+            inst[0].label = label
+            return inst[0]
+        raise ValueError(
+            'Expected a single object response, got a list: %s' % inst)
+
+    @dispatch(event="provider.compute.instances.get",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def get(self, instance_id):
         return self.svc.get(instance_id)
 
+    @dispatch(event="provider.compute.instances.find",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         label = kwargs.pop('label', None)
 
@@ -700,9 +697,19 @@ class AWSInstanceService(BaseInstanceService):
 
         return self.svc.find(filter_name='tag:Name', filter_value=label)
 
+    @dispatch(event="provider.compute.instances.list",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
+    @dispatch(event="provider.compute.instances.delete",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
+    def delete(self, inst):
+        aws_inst = inst if isinstance(inst, AWSInstance) else self.get(inst)
+        if aws_inst:
+            # pylint:disable=protected-access
+            aws_inst._ec2_instance.terminate()
+
 
 class AWSVMTypeService(BaseVMTypeService):
 
@@ -732,6 +739,8 @@ class AWSVMTypeService(BaseVMTypeService):
         return [vm_type for vm_type in vm_types_list
                 if vm_type.get('pricing', {}).get(self.provider.region_name)]
 
+    @dispatch(event="provider.compute.vm_types.list",
+              priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         vm_types = [AWSVMType(self.provider, vm_type)
                     for vm_type in self.instance_data]
@@ -744,6 +753,8 @@ class AWSRegionService(BaseRegionService):
     def __init__(self, provider):
         super(AWSRegionService, self).__init__(provider)
 
+    @dispatch(event="provider.compute.regions.get",
+              priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
     def get(self, region_id):
         log.debug("Getting AWS Region Service with the id: %s",
                   region_id)
@@ -753,6 +764,8 @@ class AWSRegionService(BaseRegionService):
         else:
             return None
 
+    @dispatch(event="provider.compute.regions.list",
+              priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         regions = [
             AWSRegion(self.provider, region) for region in
@@ -795,14 +808,18 @@ class AWSNetworkService(BaseNetworkService):
                                   cb_resource=AWSNetwork,
                                   boto_collection_name='vpcs')
 
+    @dispatch(event="provider.networking.networks.get",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def get(self, network_id):
-        log.debug("Getting AWS Network Service with the id: %s",
-                  network_id)
         return self.svc.get(network_id)
 
+    @dispatch(event="provider.networking.networks.list",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
+    @dispatch(event="provider.networking.networks.find",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         label = kwargs.pop('label', None)
 
@@ -815,9 +832,9 @@ class AWSNetworkService(BaseNetworkService):
         log.debug("Searching for AWS Network Service %s", label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
 
+    @dispatch(event="provider.networking.networks.create",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def create(self, label, cidr_block):
-        log.debug("Creating AWS Network Service with the params "
-                  "[label: %s block: %s]", label, cidr_block)
         AWSNetwork.assert_valid_resource_label(label)
 
         cb_net = self.svc.create('create_vpc', CidrBlock=cidr_block)
@@ -827,6 +844,14 @@ class AWSNetworkService(BaseNetworkService):
             cb_net.label = label
         return cb_net
 
+    @dispatch(event="provider.networking.networks.delete",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
+    def delete(self, net):
+        network = net if isinstance(net, AWSNetwork) else self.get(net)
+        if network:
+            # pylint:disable=protected-access
+            network._vpc.delete()
+
     def get_or_create_default(self):
         # # Look for provided default network
         # for net in self.provider.networking.networks:
@@ -855,10 +880,13 @@ class AWSSubnetService(BaseSubnetService):
                                   cb_resource=AWSSubnet,
                                   boto_collection_name='subnets')
 
+    @dispatch(event="provider.networking.subnets.get",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def get(self, subnet_id):
-        log.debug("Getting AWS Subnet Service with the id: %s", subnet_id)
         return self.svc.get(subnet_id)
 
+    @dispatch(event="provider.networking.subnets.list",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def list(self, network=None, limit=None, marker=None):
         network_id = network.id if isinstance(network, AWSNetwork) else network
         if network_id:
@@ -868,7 +896,9 @@ class AWSSubnetService(BaseSubnetService):
         else:
             return self.svc.list(limit=limit, marker=marker)
 
-    def find(self, **kwargs):
+    @dispatch(event="provider.networking.subnets.find",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
+    def find(self, network=None, **kwargs):
         label = kwargs.pop('label', None)
 
         # All kwargs should have been popped at this time.
@@ -880,12 +910,10 @@ class AWSSubnetService(BaseSubnetService):
         log.debug("Searching for AWS Subnet Service %s", label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
 
+    @dispatch(event="provider.networking.subnets.create",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network, cidr_block, zone):
-        log.debug("Creating AWS Subnet Service with the params "
-                  "[label: %s network: %s block: %s zone: %s]",
-                  label, network, cidr_block, zone)
         AWSSubnet.assert_valid_resource_label(label)
-
         zone_name = zone.name if isinstance(
             zone, AWSPlacementZone) else zone
 
@@ -899,6 +927,14 @@ class AWSSubnetService(BaseSubnetService):
             subnet.label = label
         return subnet
 
+    @dispatch(event="provider.networking.subnets.delete",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
+    def delete(self, subnet):
+        sn = subnet if isinstance(subnet, AWSSubnet) else self.get(subnet)
+        if sn:
+            # pylint:disable=protected-access
+            sn._subnet.delete()
+
     def get_or_create_default(self, zone):
         zone_name = zone.name if isinstance(zone, AWSPlacementZone) else zone
 
@@ -1001,11 +1037,6 @@ class AWSSubnetService(BaseSubnetService):
             default_sn = sn
         return default_sn
 
-    def delete(self, subnet):
-        log.debug("Deleting AWS Subnet Service: %s", subnet)
-        subnet_id = subnet.id if isinstance(subnet, AWSSubnet) else subnet
-        self.svc.delete(subnet_id)
-
 
 class AWSRouterService(BaseRouterService):
     """For AWS, a CloudBridge router corresponds to an AWS Route Table."""
@@ -1016,10 +1047,13 @@ class AWSRouterService(BaseRouterService):
                                   cb_resource=AWSRouter,
                                   boto_collection_name='route_tables')
 
+    @dispatch(event="provider.networking.routers.get",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def get(self, router_id):
-        log.debug("Getting AWS Router Service with the id: %s", router_id)
         return self.svc.get(router_id)
 
+    @dispatch(event="provider.networking.routers.find",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         label = kwargs.pop('label', None)
 
@@ -1032,17 +1066,25 @@ class AWSRouterService(BaseRouterService):
         log.debug("Searching for AWS Router Service %s", label)
         return self.svc.find(filter_name='tag:Name', filter_value=label)
 
+    @dispatch(event="provider.networking.routers.list",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
+    @dispatch(event="provider.networking.routers.create",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network):
-        log.debug("Creating AWS Router Service with the params "
-                  "[label: %s network: %s]", label, network)
-        AWSRouter.assert_valid_resource_label(label)
-
         network_id = network.id if isinstance(network, AWSNetwork) else network
 
         cb_router = self.svc.create('create_route_table', VpcId=network_id)
         if label:
             cb_router.label = label
         return cb_router
+
+    @dispatch(event="provider.networking.routers.delete",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
+    def delete(self, router):
+        r = router if isinstance(router, AWSRouter) else self.get(router)
+        if r:
+            # pylint:disable=protected-access
+            r._route_table.delete()

+ 0 - 37
cloudbridge/cloud/providers/azure/resources.py

@@ -101,9 +101,6 @@ class AzureVMFirewall(BaseVMFirewall):
     def rules(self):
         return self._rule_container
 
-    def delete(self):
-        self._provider.azure_client.delete_vm_firewall(self.id)
-
     def refresh(self):
         """
         Refreshes the security group with tags if required.
@@ -1131,9 +1128,6 @@ class AzureSubnet(BaseSubnet):
     def network_id(self):
         return self._provider.azure_client.get_network_id_for_subnet(self.id)
 
-    def delete(self):
-        self._provider.azure_client.delete_subnet(self.id)
-
     @property
     def state(self):
         return self._SUBNET_STATE_MAP.get(self._state, NetworkState.UNKNOWN)
@@ -1274,31 +1268,6 @@ class AzureInstance(BaseInstance):
         """
         self._provider.azure_client.restart_vm(self.id)
 
-    def delete(self):
-        """
-        Permanently terminate this instance.
-        After deleting the VM. we are deleting the network interface
-        associated to the instance, public ip addresses associated to
-        the instance and also removing OS disk and data disks where
-        tag with name 'delete_on_terminate' has value True.
-        """
-        # Remove IPs first to avoid a network interface conflict
-        for public_ip_id in self._public_ip_ids:
-            self.remove_floating_ip(public_ip_id)
-        self._provider.azure_client.deallocate_vm(self.id)
-        self._provider.azure_client.delete_vm(self.id)
-        for nic_id in self._nic_ids:
-            self._provider.azure_client.delete_nic(nic_id)
-        for data_disk in self._vm.storage_profile.data_disks:
-            if data_disk.managed_disk:
-                if self._vm.tags.get('delete_on_terminate',
-                                     'False') == 'True':
-                    self._provider.azure_client.\
-                        delete_disk(data_disk.managed_disk.id)
-        if self._vm.storage_profile.os_disk.managed_disk:
-            self._provider.azure_client. \
-                delete_disk(self._vm.storage_profile.os_disk.managed_disk.id)
-
     @property
     def image_id(self):
         """
@@ -1593,9 +1562,6 @@ class AzureKeyPair(BaseKeyPair):
     def name(self):
         return self._key_pair.Name
 
-    def delete(self):
-        self._provider.azure_client.delete_public_key(self._key_pair)
-
 
 class AzureRouter(BaseRouter):
     def __init__(self, provider, route_table):
@@ -1652,9 +1618,6 @@ class AzureRouter(BaseRouter):
     def network_id(self):
         return None
 
-    def delete(self):
-        self._provider.azure_client.delete_route_table(self.name)
-
     def attach_subnet(self, subnet):
         self._provider.azure_client. \
             attach_subnet_to_route_table(subnet.id,

+ 330 - 283
cloudbridge/cloud/providers/azure/services.py

@@ -8,7 +8,7 @@ from azure.mgmt.compute.models import DiskCreateOption
 from msrestazure.azure_exceptions import CloudError
 
 import cloudbridge.cloud.base.helpers as cb_helpers
-from cloudbridge.cloud.base.middleware import implement
+from cloudbridge.cloud.base.middleware import dispatch
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.resources import ServerPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketObjectService
@@ -35,7 +35,6 @@ from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import Snapshot
-from cloudbridge.cloud.interfaces.resources import Subnet
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import Volume
@@ -79,21 +78,27 @@ class AzureVMFirewallService(BaseVMFirewallService):
     def __init__(self, provider):
         super(AzureVMFirewallService, self).__init__(provider)
 
-    def get(self, fw_id):
+    @dispatch(event="provider.security.vm_firewalls.get",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
+    def get(self, vm_firewall_id):
         try:
-            fws = self.provider.azure_client.get_vm_firewall(fw_id)
+            fws = self.provider.azure_client.get_vm_firewall(vm_firewall_id)
             return AzureVMFirewall(self.provider, fws)
         except (CloudError, InvalidValueException) as cloud_error:
             # Azure raises the cloud error if the resource not available
             log.exception(cloud_error)
             return None
 
+    @dispatch(event="provider.security.vm_firewalls.list",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         fws = [AzureVMFirewall(self.provider, fw)
                for fw in self.provider.azure_client.list_vm_firewall()]
         return ClientPagedResultList(self.provider, fws, limit, marker)
 
     @cb_helpers.deprecated_alias(network_id='network')
+    @dispatch(event="provider.security.vm_firewalls.create",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network, description=None):
         AzureVMFirewall.assert_valid_resource_label(label)
         name = AzureVMFirewall._generate_name_from_label(label, "cb-fw")
@@ -136,8 +141,11 @@ class AzureVMFirewallService(BaseVMFirewallService):
         cb_fw = AzureVMFirewall(self.provider, fw)
         return cb_fw
 
-    def delete(self, group_id):
-        self.provider.azure_client.delete_vm_firewall(group_id)
+    @dispatch(event="provider.security.vm_firewalls.delete",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
+    def delete(self, vmf):
+        fw_id = vmf.id if isinstance(vmf, AzureVMFirewall) else vmf
+        self.provider.azure_client.delete_vm_firewall(fw_id)
 
 
 class AzureKeyPairService(BaseKeyPairService):
@@ -146,9 +154,9 @@ class AzureKeyPairService(BaseKeyPairService):
     def __init__(self, provider):
         super(AzureKeyPairService, self).__init__(provider)
 
-    @implement(event_pattern="provider.security.key_pairs.get",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _get(self, key_pair_id):
+    @dispatch(event="provider.security.key_pairs.get",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def get(self, key_pair_id):
         try:
             key_pair = self.provider.azure_client.\
                 get_public_key(key_pair_id)
@@ -161,9 +169,9 @@ class AzureKeyPairService(BaseKeyPairService):
             log.debug(error)
             return None
 
-    @implement(event_pattern="provider.security.key_pairs.list",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
+    @dispatch(event="provider.security.key_pairs.list",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         key_pairs, resume_marker = self.provider.azure_client.list_public_keys(
             AzureKeyPairService.PARTITION_KEY, marker=marker,
             limit=limit or self.provider.config.default_result_limit)
@@ -174,9 +182,9 @@ class AzureKeyPairService(BaseKeyPairService):
                                      supports_total=False,
                                      data=results)
 
-    @implement(event_pattern="provider.security.key_pairs.find",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.security.key_pairs.find",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         obj_list = self
         filters = ['name']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
@@ -190,11 +198,10 @@ class AzureKeyPairService(BaseKeyPairService):
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
-    @implement(event_pattern="provider.security.key_pairs.create",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _create(self, name, public_key_material=None):
+    @dispatch(event="provider.security.key_pairs.create",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def create(self, name, public_key_material=None):
         AzureKeyPair.assert_valid_resource_name(name)
-
         key_pair = self.get(name)
 
         if key_pair:
@@ -217,6 +224,14 @@ class AzureKeyPairService(BaseKeyPairService):
         key_pair.material = private_key
         return key_pair
 
+    @dispatch(event="provider.security.key_pairs.delete",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def delete(self, kp):
+        key_pair = kp if isinstance(kp, AzureKeyPair) else self.get(kp)
+        if key_pair:
+            # pylint:disable=protected-access
+            self.provider.azure_client.delete_public_key(key_pair._key_pair)
+
 
 class AzureStorageService(BaseStorageService):
     def __init__(self, provider):
@@ -249,12 +264,9 @@ class AzureVolumeService(BaseVolumeService):
     def __init__(self, provider):
         super(AzureVolumeService, self).__init__(provider)
 
-    @implement(event_pattern="provider.storage.volumes.get",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _get(self, volume_id):
-        """
-        Returns a volume given its id.
-        """
+    @dispatch(event="provider.storage.volumes.get",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def get(self, volume_id):
         try:
             volume = self.provider.azure_client.get_disk(volume_id)
             return AzureVolume(self.provider, volume)
@@ -263,9 +275,9 @@ class AzureVolumeService(BaseVolumeService):
             log.exception(cloud_error)
             return None
 
-    @implement(event_pattern="provider.storage.volumes.find",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.storage.volumes.find",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         obj_list = self
         filters = ['label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
@@ -279,23 +291,17 @@ class AzureVolumeService(BaseVolumeService):
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
-    @implement(event_pattern="provider.storage.volumes.list",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
-        """
-        List all volumes.
-        """
+    @dispatch(event="provider.storage.volumes.list",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         azure_vols = self.provider.azure_client.list_disks()
         cb_vols = [AzureVolume(self.provider, vol) for vol in azure_vols]
         return ClientPagedResultList(self.provider, cb_vols,
                                      limit=limit, marker=marker)
 
-    @implement(event_pattern="provider.storage.volumes.create",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _create(self, label, size, zone, description=None, snapshot=None):
-        """
-        Creates a new volume.
-        """
+    @dispatch(event="provider.storage.volumes.create",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, size, zone, snapshot=None, description=None):
         AzureVolume.assert_valid_resource_label(label)
         disk_name = AzureVolume._generate_name_from_label(label, "cb-vol")
         tags = {'Label': label}
@@ -340,10 +346,11 @@ class AzureVolumeService(BaseVolumeService):
 
         return cb_vol
 
-    @implement(event_pattern="provider.storage.volumes.delete",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, volume):
-        vol_id = volume.id if isinstance(volume, AzureVolume) else volume
+    @dispatch(event="provider.storage.volumes.delete",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def delete(self, volume_id):
+        vol_id = (volume_id.id if isinstance(volume_id, AzureVolume)
+                  else volume_id)
         self.provider.azure_client.delete_disk(vol_id)
 
 
@@ -351,23 +358,20 @@ class AzureSnapshotService(BaseSnapshotService):
     def __init__(self, provider):
         super(AzureSnapshotService, self).__init__(provider)
 
-    @implement(event_pattern="provider.storage.snapshots.get",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _get(self, ss_id):
-        """
-        Returns a snapshot given its id.
-        """
+    @dispatch(event="provider.storage.snapshots.get",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def get(self, snapshot_id):
         try:
-            snapshot = self.provider.azure_client.get_snapshot(ss_id)
+            snapshot = self.provider.azure_client.get_snapshot(snapshot_id)
             return AzureSnapshot(self.provider, snapshot)
         except (CloudError, InvalidValueException) as cloud_error:
             # Azure raises the cloud error if the resource not available
             log.exception(cloud_error)
             return None
 
-    @implement(event_pattern="provider.storage.snapshots.find",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.storage.snapshots.find",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         obj_list = self
         filters = ['label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
@@ -381,23 +385,17 @@ class AzureSnapshotService(BaseSnapshotService):
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
-    @implement(event_pattern="provider.storage.snapshots.list",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
-        """
-               List all snapshots.
-        """
+    @dispatch(event="provider.storage.snapshots.list",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         snaps = [AzureSnapshot(self.provider, obj)
                  for obj in
                  self.provider.azure_client.list_snapshots()]
         return ClientPagedResultList(self.provider, snaps, limit, marker)
 
-    @implement(event_pattern="provider.storage.snapshots.create",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _create(self, label, volume, description=None):
-        """
-        Creates a new snapshot of a given volume.
-        """
+    @dispatch(event="provider.storage.snapshots.create",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, volume, description=None):
         AzureSnapshot.assert_valid_resource_label(label)
         snapshot_name = AzureSnapshot._generate_name_from_label(label,
                                                                 "cb-snap")
@@ -422,11 +420,11 @@ class AzureSnapshotService(BaseSnapshotService):
                                                                 params)
         return AzureSnapshot(self.provider, azure_snap)
 
-    @implement(event_pattern="provider.storage.snapshots.delete",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, snapshot):
-        snap_id = (snapshot.id if isinstance(snapshot, AzureSnapshot)
-                   else snapshot)
+    @dispatch(event="provider.storage.snapshots.delete",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def delete(self, snapshot_id):
+        snap_id = (snapshot_id.id if isinstance(snapshot_id, AzureSnapshot)
+                   else snapshot_id)
         self.provider.azure_client.delete_snapshot(snap_id)
 
 
@@ -434,9 +432,9 @@ class AzureBucketService(BaseBucketService):
     def __init__(self, provider):
         super(AzureBucketService, self).__init__(provider)
 
-    @implement(event_pattern="provider.storage.buckets.get",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _get(self, bucket_id):
+    @dispatch(event="provider.storage.buckets.get",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def get(self, bucket_id):
         """
         Returns a bucket given its ID. Returns ``None`` if the bucket
         does not exist.
@@ -448,18 +446,18 @@ class AzureBucketService(BaseBucketService):
             log.exception(error)
             return None
 
-    @implement(event_pattern="provider.storage.buckets.list",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit, marker):
+    @dispatch(event="provider.storage.buckets.list",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         buckets = [AzureBucket(self.provider, bucket)
                    for bucket
                    in self.provider.azure_client.list_containers()[0]]
         return ClientPagedResultList(self.provider, buckets,
                                      limit=limit, marker=marker)
 
-    @implement(event_pattern="provider.storage.buckets.create",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _create(self, name, location=None):
+    @dispatch(event="provider.storage.buckets.create",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def create(self, name, location=None):
         """
         Create a new bucket.
         """
@@ -467,13 +465,14 @@ class AzureBucketService(BaseBucketService):
         bucket = self.provider.azure_client.create_container(name)
         return AzureBucket(self.provider, bucket)
 
-    @implement(event_pattern="provider.storage.buckets.delete",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, bucket_id):
+    @dispatch(event="provider.storage.buckets.delete",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def delete(self, bucket):
         """
         Delete this bucket.
         """
-        self.provider.azure_client.delete_container(bucket_id)
+        b_id = bucket.id if isinstance(bucket, AzureBucket) else bucket
+        self.provider.azure_client.delete_container(b_id)
 
 
 class AzureBucketObjectService(BaseBucketObjectService):
@@ -592,141 +591,6 @@ class AzureInstanceService(BaseInstanceService):
     def __init__(self, provider):
         super(AzureInstanceService, self).__init__(provider)
 
-    def create(self, label, image, vm_type, subnet, zone,
-               key_pair=None, vm_firewalls=None, user_data=None,
-               launch_config=None, **kwargs):
-
-        AzureInstance.assert_valid_resource_label(label)
-
-        instance_name = AzureInstance._generate_name_from_label(label,
-                                                                "cb-ins")
-
-        image = (image if isinstance(image, AzureMachineImage) else
-                 self.provider.compute.images.get(image))
-        if not isinstance(image, AzureMachineImage):
-            raise Exception("Provided image %s is not a valid azure image"
-                            % image)
-
-        instance_size = vm_type.id if \
-            isinstance(vm_type, VMType) else vm_type
-
-        if not subnet:
-            # Azure has only a single zone per region; use the current one
-            zone = self.provider.compute.regions.get(
-                self.provider.region_name).zones[0]
-            subnet = self.provider.networking.subnets.get_or_create_default(
-                zone)
-        else:
-            subnet = (self.provider.networking.subnets.get(subnet)
-                      if isinstance(subnet, str) else subnet)
-
-        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
-
-        subnet_id, zone_id, vm_firewall_id = \
-            self._resolve_launch_options(instance_name,
-                                         subnet, zone_id, vm_firewalls)
-
-        storage_profile = self._create_storage_profile(image, launch_config,
-                                                       instance_name, zone_id)
-
-        nic_params = {
-            'location': self.provider.region_name,
-            'ip_configurations': [{
-                'name': instance_name + '_ip_config',
-                'private_ip_allocation_method': 'Dynamic',
-                'subnet': {
-                    'id': subnet_id
-                }
-            }]
-        }
-
-        if vm_firewall_id:
-            nic_params['network_security_group'] = {
-                'id': vm_firewall_id
-            }
-        nic_info = self.provider.azure_client.create_nic(
-            instance_name + '_nic',
-            nic_params
-        )
-        # #! indicates shell script
-        ud = '#cloud-config\n' + user_data \
-            if user_data and not user_data.startswith('#!')\
-            and not user_data.startswith('#cloud-config') else user_data
-
-        # Key_pair is mandatory in azure and it should not be None.
-        temp_key_pair = None
-        if key_pair:
-            key_pair = (key_pair if isinstance(key_pair, AzureKeyPair)
-                        else self.provider.security.key_pairs.get(key_pair))
-        else:
-            # Create a temporary keypair if none is provided to keep Azure
-            # happy, but the private key will be discarded, so it'll be all
-            # but useless. However, this will allow an instance to be launched
-            # without specifying a keypair, so users may still be able to login
-            # if they have a preinstalled keypair/password baked into the image
-            temp_kp_name = "".join(["cb-default-kp-",
-                                   str(uuid.uuid5(uuid.NAMESPACE_OID,
-                                                  instance_name))[-6:]])
-            key_pair = self.provider.security.key_pairs.create(
-                name=temp_kp_name)
-            temp_key_pair = key_pair
-
-        params = {
-            'location': zone_id or self.provider.region_name,
-            'os_profile': {
-                'admin_username': self.provider.vm_default_user_name,
-                'computer_name': instance_name,
-                'linux_configuration': {
-                    "disable_password_authentication": True,
-                    "ssh": {
-                        "public_keys": [{
-                            "path":
-                                "/home/{}/.ssh/authorized_keys".format(
-                                        self.provider.vm_default_user_name),
-                                "key_data": key_pair._key_pair.Key
-                        }]
-                    }
-                }
-            },
-            'hardware_profile': {
-                'vm_size': instance_size
-            },
-            'network_profile': {
-                'network_interfaces': [{
-                    'id': nic_info.id
-                }]
-            },
-            'storage_profile': storage_profile,
-            'tags': {'Label': label}
-        }
-
-        for disk_def in storage_profile.get('data_disks', []):
-            params['tags'] = dict(disk_def.get('tags', {}), **params['tags'])
-
-        if user_data:
-            custom_data = base64.b64encode(bytes(ud, 'utf-8'))
-            params['os_profile']['custom_data'] = str(custom_data, 'utf-8')
-
-        if not temp_key_pair:
-            params['tags'].update(Key_Pair=key_pair.id)
-
-        try:
-            vm = self.provider.azure_client.create_vm(instance_name, params)
-        except Exception as e:
-            # If VM creation fails, attempt to clean up intermediary resources
-            self.provider.azure_client.delete_nic(nic_info.id)
-            for disk_def in storage_profile.get('data_disks', []):
-                if disk_def.get('tags', {}).get('delete_on_terminate'):
-                    disk_id = disk_def.get('managed_disk', {}).get('id')
-                    if disk_id:
-                        vol = self.provider.storage.volumes.get(disk_id)
-                        vol.delete()
-            raise e
-        finally:
-            if temp_key_pair:
-                temp_key_pair.delete()
-        return AzureInstance(self.provider, vm)
-
     def _resolve_launch_options(self, inst_name, subnet=None, zone_id=None,
                                 vm_firewalls=None):
         if subnet:
@@ -865,6 +729,143 @@ class AzureInstanceService(BaseInstanceService):
     def create_launch_config(self):
         return AzureLaunchConfig(self.provider)
 
+    @dispatch(event="provider.compute.instances.create",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, image, vm_type, subnet, zone,
+               key_pair=None, vm_firewalls=None, user_data=None,
+               launch_config=None, **kwargs):
+        AzureInstance.assert_valid_resource_label(label)
+        instance_name = AzureInstance._generate_name_from_label(label,
+                                                                "cb-ins")
+
+        image = (image if isinstance(image, AzureMachineImage) else
+                 self.provider.compute.images.get(image))
+        if not isinstance(image, AzureMachineImage):
+            raise Exception("Provided image %s is not a valid azure image"
+                            % image)
+
+        instance_size = vm_type.id if \
+            isinstance(vm_type, VMType) else vm_type
+
+        if not subnet:
+            # Azure has only a single zone per region; use the current one
+            zone = self.provider.compute.regions.get(
+                self.provider.region_name).zones[0]
+            subnet = self.provider.networking.subnets.get_or_create_default(
+                zone)
+        else:
+            subnet = (self.provider.networking.subnets.get(subnet)
+                      if isinstance(subnet, str) else subnet)
+
+        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
+
+        subnet_id, zone_id, vm_firewall_id = \
+            self._resolve_launch_options(instance_name,
+                                         subnet, zone_id, vm_firewalls)
+
+        storage_profile = self._create_storage_profile(image, launch_config,
+                                                       instance_name, zone_id)
+
+        nic_params = {
+            'location': self.provider.region_name,
+            'ip_configurations': [{
+                'name': instance_name + '_ip_config',
+                'private_ip_allocation_method': 'Dynamic',
+                'subnet': {
+                    'id': subnet_id
+                }
+            }]
+        }
+
+        if vm_firewall_id:
+            nic_params['network_security_group'] = {
+                'id': vm_firewall_id
+            }
+        nic_info = self.provider.azure_client.create_nic(
+            instance_name + '_nic',
+            nic_params
+        )
+        # #! indicates shell script
+        ud = '#cloud-config\n' + user_data \
+            if user_data and not user_data.startswith('#!')\
+            and not user_data.startswith('#cloud-config') else user_data
+
+        # Key_pair is mandatory in azure and it should not be None.
+        temp_key_pair = None
+        if key_pair:
+            key_pair = (key_pair if isinstance(key_pair, AzureKeyPair)
+                        else self.provider.security.key_pairs.get(key_pair))
+        else:
+            # Create a temporary keypair if none is provided to keep Azure
+            # happy, but the private key will be discarded, so it'll be all
+            # but useless. However, this will allow an instance to be launched
+            # without specifying a keypair, so users may still be able to login
+            # if they have a preinstalled keypair/password baked into the image
+            temp_kp_name = "".join(["cb-default-kp-",
+                                   str(uuid.uuid5(uuid.NAMESPACE_OID,
+                                                  instance_name))[-6:]])
+            key_pair = self.provider.security.key_pairs.create(
+                name=temp_kp_name)
+            temp_key_pair = key_pair
+
+        params = {
+            'location': zone_id or self.provider.region_name,
+            'os_profile': {
+                'admin_username': self.provider.vm_default_user_name,
+                'computer_name': instance_name,
+                'linux_configuration': {
+                    "disable_password_authentication": True,
+                    "ssh": {
+                        "public_keys": [{
+                            "path":
+                                "/home/{}/.ssh/authorized_keys".format(
+                                        self.provider.vm_default_user_name),
+                                "key_data": key_pair._key_pair.Key
+                        }]
+                    }
+                }
+            },
+            'hardware_profile': {
+                'vm_size': instance_size
+            },
+            'network_profile': {
+                'network_interfaces': [{
+                    'id': nic_info.id
+                }]
+            },
+            'storage_profile': storage_profile,
+            'tags': {'Label': label}
+        }
+
+        for disk_def in storage_profile.get('data_disks', []):
+            params['tags'] = dict(disk_def.get('tags', {}), **params['tags'])
+
+        if user_data:
+            custom_data = base64.b64encode(bytes(ud, 'utf-8'))
+            params['os_profile']['custom_data'] = str(custom_data, 'utf-8')
+
+        if not temp_key_pair:
+            params['tags'].update(Key_Pair=key_pair.id)
+
+        try:
+            vm = self.provider.azure_client.create_vm(instance_name, params)
+        except Exception as e:
+            # If VM creation fails, attempt to clean up intermediary resources
+            self.provider.azure_client.delete_nic(nic_info.id)
+            for disk_def in storage_profile.get('data_disks', []):
+                if disk_def.get('tags', {}).get('delete_on_terminate'):
+                    disk_id = disk_def.get('managed_disk', {}).get('id')
+                    if disk_id:
+                        vol = self.provider.storage.volumes.get(disk_id)
+                        vol.delete()
+            raise e
+        finally:
+            if temp_key_pair:
+                temp_key_pair.delete()
+        return AzureInstance(self.provider, vm)
+
+    @dispatch(event="provider.compute.instances.list",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         """
         List all instances.
@@ -874,6 +875,8 @@ class AzureInstanceService(BaseInstanceService):
         return ClientPagedResultList(self.provider, instances,
                                      limit=limit, marker=marker)
 
+    @dispatch(event="provider.compute.instances.get",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def get(self, instance_id):
         """
         Returns an instance given its id. Returns None
@@ -887,6 +890,8 @@ class AzureInstanceService(BaseInstanceService):
             log.exception(cloud_error)
             return None
 
+    @dispatch(event="provider.compute.instances.find",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         obj_list = self
         filters = ['label']
@@ -901,6 +906,36 @@ class AzureInstanceService(BaseInstanceService):
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
+    @dispatch(event="provider.compute.instances.delete",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
+    def delete(self, inst):
+        """
+        Permanently terminate this instance.
+        After deleting the VM. we are deleting the network interface
+        associated to the instance, and also removing OS disk and data disks
+        where tag with name 'delete_on_terminate' has value True.
+        """
+        ins = inst if isinstance(inst, AzureInstance) else self.get(inst)
+        if not inst:
+            return
+
+        # Remove IPs first to avoid a network interface conflict
+        for public_ip_id in ins._public_ip_ids:
+            ins.remove_floating_ip(public_ip_id)
+        self.provider.azure_client.deallocate_vm(ins.id)
+        self.provider.azure_client.delete_vm(ins.id)
+        for nic_id in ins._nic_ids:
+            self.provider.azure_client.delete_nic(nic_id)
+        for data_disk in ins._vm.storage_profile.data_disks:
+            if data_disk.managed_disk:
+                if ins._vm.tags.get('delete_on_terminate',
+                                    'False') == 'True':
+                    self.provider.azure_client. \
+                        delete_disk(data_disk.managed_disk.id)
+        if ins._vm.storage_profile.os_disk.managed_disk:
+            self.provider.azure_client. \
+                delete_disk(ins._vm.storage_profile.os_disk.managed_disk.id)
+
 
 class AzureVMTypeService(BaseVMTypeService):
 
@@ -915,6 +950,8 @@ class AzureVMTypeService(BaseVMTypeService):
         r = self.provider.azure_client.list_vm_types()
         return r
 
+    @dispatch(event="provider.compute.vm_types.list",
+              priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         vm_types = [AzureVMType(self.provider, vm_type)
                     for vm_type in self.instance_data]
@@ -926,6 +963,8 @@ class AzureRegionService(BaseRegionService):
     def __init__(self, provider):
         super(AzureRegionService, self).__init__(provider)
 
+    @dispatch(event="provider.compute.regions.get",
+              priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
     def get(self, region_id):
         region = None
         for azureRegion in self.provider.azure_client.list_locations():
@@ -934,6 +973,8 @@ class AzureRegionService(BaseRegionService):
                 break
         return region
 
+    @dispatch(event="provider.compute.regions.list",
+              priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         regions = [AzureRegion(self.provider, region)
                    for region in self.provider.azure_client.list_locations()]
@@ -969,6 +1010,8 @@ class AzureNetworkService(BaseNetworkService):
     def __init__(self, provider):
         super(AzureNetworkService, self).__init__(provider)
 
+    @dispatch(event="provider.networking.networks.get",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def get(self, network_id):
         try:
             network = self.provider.azure_client.get_network(network_id)
@@ -978,29 +1021,16 @@ class AzureNetworkService(BaseNetworkService):
             log.exception(cloud_error)
             return None
 
+    @dispatch(event="provider.networking.networks.list",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
-        """
-        List all networks.
-        """
         networks = [AzureNetwork(self.provider, network)
                     for network in self.provider.azure_client.list_networks()]
         return ClientPagedResultList(self.provider, networks,
                                      limit=limit, marker=marker)
 
-    def find(self, **kwargs):
-        obj_list = self
-        filters = ['label']
-        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
-
-        # All kwargs should have been popped at this time.
-        if len(kwargs) > 0:
-            raise InvalidParamException(
-                "Unrecognised parameters for search: %s. Supported "
-                "attributes: %s" % (kwargs, ", ".join(filters)))
-
-        return ClientPagedResultList(self.provider,
-                                     matches if matches else [])
-
+    @dispatch(event="provider.networking.networks.create",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def create(self, label, cidr_block):
         AzureNetwork.assert_valid_resource_label(label)
         params = {
@@ -1018,11 +1048,12 @@ class AzureNetworkService(BaseNetworkService):
         cb_network = AzureNetwork(self.provider, az_network)
         return cb_network
 
-    def delete(self, network_id):
-        """
-        Delete an existing network.
-        """
-        self.provider.azure_client.delete_network(network_id)
+    @dispatch(event="provider.networking.networks.delete",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
+    def delete(self, net):
+        net_id = net.id if isinstance(net, AzureNetwork) else net
+        if net_id:
+            self.provider.azure_client.delete_network(net_id)
 
 
 class AzureSubnetService(BaseSubnetService):
@@ -1030,33 +1061,6 @@ class AzureSubnetService(BaseSubnetService):
     def __init__(self, provider):
         super(AzureSubnetService, self).__init__(provider)
 
-    def get(self, subnet_id):
-        """
-         Azure does not provide an api to get the subnet directly by id.
-         It also requires the network id.
-         To make it consistent across the providers the following code
-         gets the specific code from the subnet list.
-
-        :param subnet_id:
-        :return:
-        """
-        try:
-            azure_subnet = self.provider.azure_client.get_subnet(subnet_id)
-            return AzureSubnet(self.provider,
-                               azure_subnet) if azure_subnet else None
-        except (CloudError, InvalidValueException) as cloud_error:
-            # Azure raises the cloud error if the resource not available
-            log.exception(cloud_error)
-            return None
-
-    def list(self, network=None, limit=None, marker=None):
-        """
-        List subnets
-        """
-        return ClientPagedResultList(self.provider,
-                                     self._list_subnets(network),
-                                     limit=limit, marker=marker)
-
     def _list_subnets(self, network=None):
         result_list = []
         if network:
@@ -1079,6 +1083,33 @@ class AzureSubnetService(BaseSubnetService):
 
         return subnets
 
+    @dispatch(event="provider.networking.subnets.get",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
+    def get(self, subnet_id):
+        """
+         Azure does not provide an api to get the subnet directly by id.
+         It also requires the network id.
+         To make it consistent across the providers the following code
+         gets the specific code from the subnet list.
+        """
+        try:
+            azure_subnet = self.provider.azure_client.get_subnet(subnet_id)
+            return AzureSubnet(self.provider,
+                               azure_subnet) if azure_subnet else None
+        except (CloudError, InvalidValueException) as cloud_error:
+            # Azure raises the cloud error if the resource not available
+            log.exception(cloud_error)
+            return None
+
+    @dispatch(event="provider.networking.subnets.list",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
+    def list(self, network=None, limit=None, marker=None):
+        return ClientPagedResultList(self.provider,
+                                     self._list_subnets(network),
+                                     limit=limit, marker=marker)
+
+    @dispatch(event="provider.networking.subnets.find",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def find(self, network=None, **kwargs):
         obj_list = self._list_subnets(network)
         filters = ['label']
@@ -1087,13 +1118,12 @@ class AzureSubnetService(BaseSubnetService):
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
+    @dispatch(event="provider.networking.subnets.create",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network, cidr_block, zone):
-        """
-        Create subnet
-        """
+        AzureSubnet.assert_valid_resource_label(label)
         # Although Subnet doesn't support tags in Azure, we use the parent
         # Network's tags to track its subnets' labels
-        AzureSubnet.assert_valid_resource_label(label)
         subnet_name = AzureSubnet._generate_name_from_label(label, "cb-sn")
 
         network_id = network.id \
@@ -1112,23 +1142,28 @@ class AzureSubnetService(BaseSubnetService):
         subnet.label = label
         return subnet
 
+    @dispatch(event="provider.networking.subnets.delete",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def delete(self, subnet):
-        subnet_id = subnet.id if isinstance(subnet, Subnet) else subnet
-        self.provider.azure_client.delete_subnet(subnet_id)
-        # Although Subnet doesn't support labels, we use the parent Network's
-        # tags to track the subnet's labels, thus that network-level tag must
-        # be deleted with the subnet
-        network = subnet.network
-        az_network = network._network
-        az_network.tags.pop(subnet.tag_name)
-        self._provider.azure_client.update_network_tags(
-            az_network.id, az_network)
+        sn = subnet if isinstance(subnet, AzureSubnet) else self.get(subnet)
+        if sn:
+            self.provider.azure_client.delete_subnet(sn.id)
+            # Although Subnet doesn't support labels, we use the parent
+            # Network's tags to track the subnet's labels, thus that
+            # network-level tag must be deleted with the subnet
+            net_id = sn.network_id
+            az_network = self.provider.azure_client.get_network(net_id)
+            az_network.tags.pop(sn.tag_name)
+            self._provider.azure_client.update_network_tags(
+                az_network.id, az_network)
 
 
 class AzureRouterService(BaseRouterService):
     def __init__(self, provider):
         super(AzureRouterService, self).__init__(provider)
 
+    @dispatch(event="provider.networking.routers.get",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def get(self, router_id):
         try:
             route = self.provider.azure_client.get_route_table(router_id)
@@ -1138,6 +1173,8 @@ class AzureRouterService(BaseRouterService):
             log.exception(cloud_error)
             return None
 
+    @dispatch(event="provider.networking.routers.find",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         obj_list = self
         filters = ['label']
@@ -1152,6 +1189,8 @@ class AzureRouterService(BaseRouterService):
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
+    @dispatch(event="provider.networking.routers.list",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         routes = [AzureRouter(self.provider, route)
                   for route in
@@ -1160,8 +1199,9 @@ class AzureRouterService(BaseRouterService):
                                      routes,
                                      limit=limit, marker=marker)
 
+    @dispatch(event="provider.networking.routers.create",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network):
-        AzureRouter.assert_valid_resource_label(label)
         router_name = AzureRouter._generate_name_from_label(label, "cb-router")
 
         parameters = {"location": self.provider.region_name,
@@ -1170,3 +1210,10 @@ class AzureRouterService(BaseRouterService):
         route = self.provider.azure_client. \
             create_route_table(router_name, parameters)
         return AzureRouter(self.provider, route)
+
+    @dispatch(event="provider.networking.routers.delete",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
+    def delete(self, router):
+        r = router if isinstance(router, AzureRouter) else self.get(router)
+        if r:
+            self.provider.azure_client.delete_route_table(r.name)

+ 6 - 45
cloudbridge/cloud/providers/gce/resources.py

@@ -408,14 +408,19 @@ class GCEFirewallsDelegate(object):
         Delete a given firewall.
         """
         project_name = self._provider.project_name
+        name = firewall['name']
         response = (self._provider
                         .gce_compute
                         .firewalls()
                         .delete(project=project_name,
-                                firewall=firewall['name'])
+                                firewall=name)
                         .execute())
         self._provider.wait_for_operation(response)
         # TODO: process the response and handle errors.
+        tag_name = "_".join(["firewall", name, "label"])
+        if not helpers.remove_metadata_item(self._provider, tag_name):
+            log.warning('No label was found associated with this firewall '
+                        '"{}" when deleted.'.format(name))
         return True
 
     def _update_list_response(self):
@@ -506,16 +511,6 @@ class GCEVMFirewall(BaseVMFirewall):
     def rules(self):
         return self._rule_container
 
-    def delete(self):
-        for rule in self._rule_container:
-            rule.delete()
-        self._rule_container.dummy_rule.force_delete()
-        # Remove label
-        tag_name = "_".join(["firewall", self.name, "label"])
-        if not helpers.remove_metadata_item(self._provider, tag_name):
-            log.warning('No label was found associated with this firewall '
-                        '"{}" when deleted.'.format(self.name))
-
     def to_json(self):
         attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
         js = {k: v for(k, v) in attr if not k.startswith('_')}
@@ -963,19 +958,6 @@ class GCEInstance(BaseInstance):
                     instance=self.name)
              .execute())
 
-    def delete(self):
-        """
-        Permanently terminate this instance.
-        """
-        name = self.name
-        (self._provider
-         .gce_compute
-         .instances()
-         .delete(project=self._provider.project_name,
-                 zone=self.zone_name,
-                 instance=name)
-         .execute())
-
     def stop(self):
         """
         Stop this instance.
@@ -1394,9 +1376,6 @@ class GCENetwork(BaseNetwork):
     def subnets(self):
         return list(self._provider.networking.subnets.iter(network=self))
 
-    def delete(self):
-        self._provider.networking.networks.delete(self)
-
     def create_subnet(self, label, cidr_block, zone):
         return self._provider.networking.subnets.create(
             label, self, cidr_block, zone)
@@ -1611,21 +1590,6 @@ class GCERouter(BaseRouter):
         network = self._provider.networking.networks.get(self.network_id)
         return network.subnets
 
-    def delete(self):
-        operation = (self._provider
-                     .gce_compute
-                     .routers()
-                     .delete(project=self._provider.project_name,
-                             region=self.region_name,
-                             router=self.name)
-                     .execute())
-        self._provider.wait_for_operation(operation, region=self.region_name)
-        # Remove label
-        tag_name = "_".join(["router", self.name, "label"])
-        if not helpers.remove_metadata_item(self._provider, tag_name):
-            log.warning('No label was found associated with this router '
-                        '"{}" when deleted.'.format(self.name))
-
     def attach_subnet(self, subnet):
         if not isinstance(subnet, GCESubnet):
             subnet = self._provider.networking.subnets.get(subnet)
@@ -1758,9 +1722,6 @@ class GCESubnet(BaseSubnet):
     def zone(self):
         return None
 
-    def delete(self):
-        return self._provider.networking.subnets.delete(self)
-
     @property
     def state(self):
         if self._subnet.get('status') == SubnetState.UNKNOWN:

+ 196 - 151
cloudbridge/cloud/providers/gce/services.py

@@ -9,7 +9,7 @@ import googleapiclient
 
 import cloudbridge as cb
 from cloudbridge.cloud.base import helpers as cb_helpers
-from cloudbridge.cloud.base.middleware import implement
+from cloudbridge.cloud.base.middleware import dispatch
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.resources import ServerPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketObjectService
@@ -78,9 +78,9 @@ class GCEKeyPairService(BaseKeyPairService):
     def __init__(self, provider):
         super(GCEKeyPairService, self).__init__(provider)
 
-    @implement(event_pattern="provider.security.key_pairs.get",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _get(self, key_pair_id):
+    @dispatch(event="provider.security.key_pairs.get",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def get(self, key_pair_id):
         """
         Returns a KeyPair given its ID.
         """
@@ -90,9 +90,9 @@ class GCEKeyPairService(BaseKeyPairService):
         else:
             return None
 
-    @implement(event_pattern="provider.security.key_pairs.list",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
+    @dispatch(event="provider.security.key_pairs.list",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         key_pairs = []
         for item in helpers.find_matching_metadata_items(
                 self.provider, GCEKeyPair.KP_TAG_REGEX):
@@ -102,9 +102,9 @@ class GCEKeyPairService(BaseKeyPairService):
         return ClientPagedResultList(self.provider, key_pairs,
                                      limit=limit, marker=marker)
 
-    @implement(event_pattern="provider.security.key_pairs.find",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.security.key_pairs.find",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         """
         Searches for a key pair by a given list of attributes.
         """
@@ -121,11 +121,10 @@ class GCEKeyPairService(BaseKeyPairService):
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
-    @implement(event_pattern="provider.security.key_pairs.create",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _create(self, name, public_key_material=None):
+    @dispatch(event="provider.security.key_pairs.create",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def create(self, name, public_key_material=None):
         GCEKeyPair.assert_valid_resource_name(name)
-
         private_key = None
         if not public_key_material:
             public_key_material, private_key = cb_helpers.generate_key_pair()
@@ -148,10 +147,10 @@ class GCEKeyPairService(BaseKeyPairService):
                         'A KeyPair with name {0} already exists'.format(name))
             raise
 
-    @implement(event_pattern="provider.security.key_pairs.delete",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, key_pair_id):
-        kp = self.get(key_pair_id)
+    @dispatch(event="provider.security.key_pairs.delete",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def delete(self, kp):
+        kp = kp if isinstance(kp, GCEKeyPair) else self.get(kp)
         if kp:
             helpers.remove_metadata_item(
                 self.provider, GCEKeyPair.KP_TAG_PREFIX + kp.name)
@@ -163,13 +162,18 @@ class GCEVMFirewallService(BaseVMFirewallService):
         super(GCEVMFirewallService, self).__init__(provider)
         self._delegate = GCEFirewallsDelegate(provider)
 
-    def get(self, group_id):
-        tag, network_name = self._delegate.get_tag_network_from_id(group_id)
+    @dispatch(event="provider.security.vm_firewalls.get",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
+    def get(self, vm_firewall_id):
+        tag, network_name = \
+            self._delegate.get_tag_network_from_id(vm_firewall_id)
         if tag is None:
             return None
         network = self.provider.networking.networks.get(network_name)
         return GCEVMFirewall(self._delegate, tag, network)
 
+    @dispatch(event="provider.security.vm_firewalls.list",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         vm_firewalls = []
         for tag, network_name in self._delegate.tag_networks:
@@ -180,7 +184,9 @@ class GCEVMFirewallService(BaseVMFirewallService):
         return ClientPagedResultList(self.provider, vm_firewalls,
                                      limit=limit, marker=marker)
 
-    def create(self, label, description, network=None):
+    @dispatch(event="provider.security.vm_firewalls.create",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, network, description=None):
         GCEVMFirewall.assert_valid_resource_label(label)
         network = (network if isinstance(network, GCENetwork)
                    else self.provider.networking.networks.get(network))
@@ -193,8 +199,11 @@ class GCEVMFirewallService(BaseVMFirewallService):
         fw.label = label
         return fw
 
-    def delete(self, group_id):
-        return self._delegate.delete_tag_network_with_id(group_id)
+    @dispatch(event="provider.security.vm_firewalls.delete",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
+    def delete(self, vmf):
+        fw_id = vmf.id if isinstance(vmf, GCEVMFirewall) else vmf
+        return self._delegate.delete_tag_network_with_id(fw_id)
 
     def find_by_network_and_tags(self, network_name, tags):
         """
@@ -228,10 +237,14 @@ class GCEVMTypeService(BaseVMTypeService):
                         .execute())
         return response['items']
 
+    @dispatch(event="provider.compute.vm_types.get",
+              priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
     def get(self, vm_type_id):
         vm_type = self.provider.get_resource('machineTypes', vm_type_id)
         return GCEVMType(self.provider, vm_type) if vm_type else None
 
+    @dispatch(event="provider.compute.vm_types.find",
+              priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         matched_inst_types = []
         for inst_type in self.instance_data:
@@ -248,6 +261,8 @@ class GCEVMTypeService(BaseVMTypeService):
                     GCEVMType(self.provider, inst_type))
         return matched_inst_types
 
+    @dispatch(event="provider.compute.vm_types.list",
+              priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         inst_types = [GCEVMType(self.provider, inst_type)
                       for inst_type in self.instance_data]
@@ -260,11 +275,15 @@ class GCERegionService(BaseRegionService):
     def __init__(self, provider):
         super(GCERegionService, self).__init__(provider)
 
+    @dispatch(event="provider.compute.regions.get",
+              priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
     def get(self, region_id):
         region = self.provider.get_resource('regions', region_id,
                                             region=region_id)
         return GCERegion(self.provider, region) if region else None
 
+    @dispatch(event="provider.compute.regions.list",
+              priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         max_result = limit if limit is not None and limit < 500 else 500
         regions_response = (self.provider
@@ -359,6 +378,8 @@ class GCEInstanceService(BaseInstanceService):
     def __init__(self, provider):
         super(GCEInstanceService, self).__init__(provider)
 
+    @dispatch(event="provider.compute.instances.create",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def create(self, label, image, vm_type, subnet, zone=None,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
@@ -508,6 +529,8 @@ class GCEInstanceService(BaseInstanceService):
         cb_inst = self.get(instance_id)
         return cb_inst
 
+    @dispatch(event="provider.compute.instances.get",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def get(self, instance_id):
         """
         Returns an instance given its name. Returns None
@@ -519,6 +542,8 @@ class GCEInstanceService(BaseInstanceService):
         instance = self.provider.get_resource('instances', instance_id)
         return GCEInstance(self.provider, instance) if instance else None
 
+    @dispatch(event="provider.compute.instances.find",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def find(self, limit=None, marker=None, **kwargs):
         """
         Searches for instances by instance label.
@@ -537,6 +562,8 @@ class GCEInstanceService(BaseInstanceService):
         return ClientPagedResultList(self.provider, instances,
                                      limit=limit, marker=marker)
 
+    @dispatch(event="provider.compute.instances.list",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         """
         List all instances.
@@ -561,6 +588,19 @@ class GCEInstanceService(BaseInstanceService):
                                      response.get('nextPageToken'),
                                      False, data=instances)
 
+    @dispatch(event="provider.compute.instances.delete",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
+    def delete(self, inst):
+        instance = inst if isinstance(inst, GCEInstance) else self.get(inst)
+        if instance:
+            (self._provider
+             .gce_compute
+             .instances()
+             .delete(project=self.provider.project_name,
+                     zone=instance.zone_name,
+                     instance=instance.name)
+             .execute())
+
     def create_launch_config(self):
         return GCELaunchConfig(self.provider)
 
@@ -617,10 +657,14 @@ class GCENetworkService(BaseNetworkService):
     def __init__(self, provider):
         super(GCENetworkService, self).__init__(provider)
 
+    @dispatch(event="provider.networking.networks.get",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def get(self, network_id):
         network = self.provider.get_resource('networks', network_id)
         return GCENetwork(self.provider, network) if network else None
 
+    @dispatch(event="provider.networking.networks.find",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def find(self, limit=None, marker=None, **kwargs):
         """
         GCE networks are global. There is at most one network with a given
@@ -632,6 +676,8 @@ class GCENetworkService(BaseNetworkService):
         return ClientPagedResultList(self._provider, list(matches),
                                      limit=limit, marker=marker)
 
+    @dispatch(event="provider.networking.networks.list",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None, filter=None):
         # TODO: Decide whether we keep filter in 'list'
         networks = []
@@ -646,6 +692,8 @@ class GCENetworkService(BaseNetworkService):
         return ClientPagedResultList(self.provider, networks,
                                      limit=limit, marker=marker)
 
+    @dispatch(event="provider.networking.networks.create",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def create(self, label, cidr_block):
         """
         Creates an auto mode VPC network with default subnets. It is possible
@@ -679,6 +727,8 @@ class GCENetworkService(BaseNetworkService):
                 label=GCENetwork.CB_DEFAULT_NETWORK_LABEL,
                 cidr_block=GCENetwork.CB_DEFAULT_IPV4RANGE)
 
+    @dispatch(event="provider.networking.networks.delete",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def delete(self, network):
         # Accepts network object
         if isinstance(network, GCENetwork):
@@ -699,7 +749,7 @@ class GCENetworkService(BaseNetworkService):
         tag_name = "_".join(["network", name, "label"])
         if not helpers.remove_metadata_item(self.provider, tag_name):
             log.warning('No label was found associated with this network '
-                        '"{}" when deleted.'.format(network.name))
+                        '"{}" when deleted.'.format(network))
         return True
 
 
@@ -708,11 +758,15 @@ class GCERouterService(BaseRouterService):
     def __init__(self, provider):
         super(GCERouterService, self).__init__(provider)
 
+    @dispatch(event="provider.networking.routers.get",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def get(self, router_id):
         router = self.provider.get_resource(
             'routers', router_id, region=self.provider.region_name)
         return GCERouter(self.provider, router) if router else None
 
+    @dispatch(event="provider.networking.routers.find",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def find(self, limit=None, marker=None, **kwargs):
         obj_list = self
         filters = ['name', 'label']
@@ -720,6 +774,8 @@ class GCERouterService(BaseRouterService):
         return ClientPagedResultList(self._provider, list(matches),
                                      limit=limit, marker=marker)
 
+    @dispatch(event="provider.networking.routers.list",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         region = self.provider.region_name
         max_result = limit if limit is not None and limit < 500 else 500
@@ -741,6 +797,8 @@ class GCERouterService(BaseRouterService):
                                      response.get('nextPageToken'),
                                      False, data=routers)
 
+    @dispatch(event="provider.networking.routers.create",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network):
         log.debug("Creating GCE Router Service with params "
                   "[label: %s network: %s]", label, network)
@@ -764,16 +822,23 @@ class GCERouterService(BaseRouterService):
         cb_router.label = label
         return cb_router
 
+    @dispatch(event="provider.networking.routers.delete",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def delete(self, router):
-        region_name = self.provider.region_name
-        name = router.name if isinstance(router, GCERouter) else router
-        (self.provider
-         .gce_compute
-         .routers()
-         .delete(project=self.provider.project_name,
-                 region=region_name,
-                 router=name)
-         .execute())
+        r = router if isinstance(router, GCERouter) else self.get(router)
+        if r:
+            (self.provider
+             .gce_compute
+             .routers()
+             .delete(project=self.provider.project_name,
+                     region=r.region_name,
+                     router=r.name)
+             .execute())
+            # Remove label
+            tag_name = "_".join(["router", r.name, "label"])
+            if not helpers.remove_metadata_item(self.provider, tag_name):
+                log.warning('No label was found associated with this router '
+                            '"{}" when deleted.'.format(r.name))
 
     def _get_in_region(self, router_id, region=None):
         region_name = self.provider.region_name
@@ -791,10 +856,14 @@ class GCESubnetService(BaseSubnetService):
     def __init__(self, provider):
         super(GCESubnetService, self).__init__(provider)
 
+    @dispatch(event="provider.networking.subnets.get",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def get(self, subnet_id):
         subnet = self.provider.get_resource('subnetworks', subnet_id)
         return GCESubnet(self.provider, subnet) if subnet else None
 
+    @dispatch(event="provider.networking.subnets.list",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def list(self, network=None, zone=None, limit=None, marker=None):
         """
         If the zone is not given, we list all subnets in the default region.
@@ -821,6 +890,8 @@ class GCESubnetService(BaseSubnetService):
         return ClientPagedResultList(self.provider, subnets,
                                      limit=limit, marker=marker)
 
+    @dispatch(event="provider.networking.subnets.create",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network, cidr_block, zone):
         """
         GCE subnets are regional. The region is inferred from the zone;
@@ -863,6 +934,26 @@ class GCESubnetService(BaseSubnetService):
         cb_subnet.label = label
         return cb_subnet
 
+    @dispatch(event="provider.networking.subnets.delete",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
+    def delete(self, subnet):
+        sn = subnet if isinstance(subnet, GCESubnet) else self.get(subnet)
+        if not sn:
+            return
+        response = (self.provider
+                    .gce_compute
+                    .subnetworks()
+                    .delete(project=self.provider.project_name,
+                            region=sn.region_name,
+                            subnetwork=sn.name)
+                    .execute())
+        self.provider.wait_for_operation(response, region=sn.region_name)
+        # Remove label
+        tag_name = "_".join(["subnet", sn.name, "label"])
+        if not helpers.remove_metadata_item(self._provider, tag_name):
+            log.warning('No label was found associated with this subnet '
+                        '"{}" when deleted.'.format(sn.name))
+
     def get_or_create_default(self, zone):
         """
         Return an existing or create a new subnet for the supplied zone.
@@ -907,21 +998,6 @@ class GCESubnetService(BaseSubnetService):
         router.attach_gateway(gateway)
         return sn
 
-    def delete(self, subnet):
-        response = (self.provider
-                    .gce_compute
-                    .subnetworks()
-                    .delete(project=self.provider.project_name,
-                            region=subnet.region_name,
-                            subnetwork=subnet.name)
-                    .execute())
-        self.provider.wait_for_operation(response, region=subnet.region_name)
-        # Remove label
-        tag_name = "_".join(["subnet", subnet.name, "label"])
-        if not helpers.remove_metadata_item(self._provider, tag_name):
-            log.warning('No label was found associated with this subnet '
-                        '"{}" when deleted.'.format(subnet.name))
-
     def _zone_to_region(self, zone, return_name_only=True):
         """
         Given a GCE zone, return parent region.
@@ -975,18 +1051,15 @@ class GCEVolumeService(BaseVolumeService):
     def __init__(self, provider):
         super(GCEVolumeService, self).__init__(provider)
 
-    @implement(event_pattern="provider.storage.volumes.get",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _get(self, volume_id):
-        """
-        Returns a volume given its id.
-        """
+    @dispatch(event="provider.storage.volumes.get",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def get(self, volume_id):
         vol = self.provider.get_resource('disks', volume_id)
         return GCEVolume(self.provider, vol) if vol else None
 
-    @implement(event_pattern="provider.storage.volumes.find",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _find(self, limit=None, marker=None, **kwargs):
+    @dispatch(event="provider.storage.volumes.find",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def find(self, limit=None, marker=None, **kwargs):
         """
         Searches for a volume by a given list of attributes.
         """
@@ -1018,9 +1091,9 @@ class GCEVolumeService(BaseVolumeService):
                                      response.get('nextPageToken'),
                                      False, data=gce_vols)
 
-    @implement(event_pattern="provider.storage.volumes.list",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
+    @dispatch(event="provider.storage.volumes.list",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         """
         List all volumes.
 
@@ -1048,23 +1121,9 @@ class GCEVolumeService(BaseVolumeService):
                                      response.get('nextPageToken'),
                                      False, data=gce_vols)
 
-    @implement(event_pattern="provider.storage.volumes.create",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _create(self, label, size, zone, snapshot=None, description=None):
-        """
-        Creates a new volume.
-
-        Argument `name` must be 1-63 characters long, and comply with RFC1035.
-        Specifically, the name must be 1-63 characters long and match the
-        regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first
-        character must be a lowercase letter, and all following characters must
-        be a dash, lowercase letter, or digit, except the last character, which
-        cannot be a dash.
-        """
-        log.debug("Creating GCE Volume with parameters "
-                  "[label: %s size: %s zone: %s snapshot: %s "
-                  "description: %s]", label, size, zone, snapshot,
-                  description)
+    @dispatch(event="provider.storage.volumes.create",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, size, zone, snapshot=None, description=None):
         GCEVolume.assert_valid_resource_label(label)
         name = GCEVolume._generate_name_from_label(label, 'cb-vol')
         if not isinstance(zone, GCEPlacementZone):
@@ -1093,17 +1152,17 @@ class GCEVolumeService(BaseVolumeService):
         cb_vol = self.get(operation.get('targetLink'))
         return cb_vol
 
-    @implement(event_pattern="provider.storage.volumes.delete",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, volume):
-        cb_vol = volume if isinstance(volume, GCEVolume) else self.get(volume)
-        (self._provider
-         .gce_compute
-         .disks()
-         .delete(project=self.provider.project_name,
-                 zone=cb_vol.zone_name,
-                 disk=cb_vol.name)
-         .execute())
+    @dispatch(event="provider.storage.volumes.delete",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def delete(self, vol):
+        volume = vol if isinstance(vol, GCEVolume) else self.get(vol)
+        if volume:
+            (self._provider.gce_compute
+                           .disks()
+                           .delete(project=self.provider.project_name,
+                                   zone=volume.zone_name,
+                                   disk=volume.name)
+                           .execute())
 
 
 class GCESnapshotService(BaseSnapshotService):
@@ -1111,21 +1170,15 @@ class GCESnapshotService(BaseSnapshotService):
     def __init__(self, provider):
         super(GCESnapshotService, self).__init__(provider)
 
-    @implement(event_pattern="provider.storage.snapshots.get",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _get(self, snapshot_id):
-        """
-        Returns a snapshot given its id.
-        """
+    @dispatch(event="provider.storage.snapshots.get",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def get(self, snapshot_id):
         snapshot = self.provider.get_resource('snapshots', snapshot_id)
         return GCESnapshot(self.provider, snapshot) if snapshot else None
 
-    @implement(event_pattern="provider.storage.snapshots.find",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _find(self, limit=None, marker=None, **kwargs):
-        """
-        Searches for a snapshot by a given list of attributes.
-        """
+    @dispatch(event="provider.storage.snapshots.find",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def find(self, limit=None, marker=None, **kwargs):
         label = kwargs.pop('label', None)
 
         # All kwargs should have been popped at this time.
@@ -1153,12 +1206,9 @@ class GCESnapshotService(BaseSnapshotService):
                                      response.get('nextPageToken'),
                                      False, data=snapshots)
 
-    @implement(event_pattern="provider.storage.snapshots.list",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
-        """
-        List all snapshots.
-        """
+    @dispatch(event="provider.storage.snapshots.list",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         max_result = limit if limit is not None and limit < 500 else 500
         response = (self.provider
                         .gce_compute
@@ -1176,12 +1226,9 @@ class GCESnapshotService(BaseSnapshotService):
                                      response.get('nextPageToken'),
                                      False, data=snapshots)
 
-    @implement(event_pattern="provider.storage.snapshots.create",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _create(self, label, volume, description=None):
-        """
-        Creates a new snapshot of a given volume.
-        """
+    @dispatch(event="provider.storage.snapshots.create",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, volume, description=None):
         GCESnapshot.assert_valid_resource_label(label)
         name = GCESnapshot._generate_name_from_label(label, 'cbsnap')
         volume_name = volume.name if isinstance(volume, GCEVolume) else volume
@@ -1205,17 +1252,17 @@ class GCESnapshotService(BaseSnapshotService):
         cb_snap = self.get(name)
         return cb_snap
 
-    @implement(event_pattern="provider.storage.snapshots.delete",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, snapshot):
-        cb_snap = (snapshot if isinstance(snapshot, GCESnapshot)
-                   else self.get(snapshot))
-        (self._provider
-         .gce_compute
-         .snapshots()
-         .delete(project=self.provider.project_name,
-                 snapshot=cb_snap.name)
-         .execute())
+    @dispatch(event="provider.storage.snapshots.delete",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def delete(self, snap):
+        snapshot = snap if isinstance(snap, GCESnapshot) else self.get(snap)
+        if snapshot:
+            (self.provider
+                 .gce_compute
+                 .snapshots()
+                 .delete(project=self.provider.project_name,
+                         snapshot=snapshot.name)
+                 .execute())
 
 
 class GCSBucketService(BaseBucketService):
@@ -1223,9 +1270,9 @@ class GCSBucketService(BaseBucketService):
     def __init__(self, provider):
         super(GCSBucketService, self).__init__(provider)
 
-    @implement(event_pattern="provider.storage.buckets.get",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _get(self, bucket_id):
+    @dispatch(event="provider.storage.buckets.get",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def get(self, bucket_id):
         """
         Returns a bucket given its ID. Returns ``None`` if the bucket
         does not exist or if the user does not have permission to access the
@@ -1234,9 +1281,9 @@ class GCSBucketService(BaseBucketService):
         bucket = self.provider.get_resource('buckets', bucket_id)
         return GCSBucket(self.provider, bucket) if bucket else None
 
-    @implement(event_pattern="provider.storage.buckets.find",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _find(self, limit=None, marker=None, **kwargs):
+    @dispatch(event="provider.storage.buckets.find",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def find(self, limit=None, marker=None, **kwargs):
         name = kwargs.pop('name', None)
 
         # All kwargs should have been popped at this time.
@@ -1249,9 +1296,9 @@ class GCSBucketService(BaseBucketService):
         return ClientPagedResultList(self.provider, buckets, limit=limit,
                                      marker=marker)
 
-    @implement(event_pattern="provider.storage.buckets.list",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
+    @dispatch(event="provider.storage.buckets.list",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         """
         List all containers.
         """
@@ -1273,9 +1320,9 @@ class GCSBucketService(BaseBucketService):
                                      response.get('nextPageToken'),
                                      False, data=buckets)
 
-    @implement(event_pattern="provider.storage.buckets.create",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _create(self, name, location=None):
+    @dispatch(event="provider.storage.buckets.create",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def create(self, name, location=None):
         GCSBucket.assert_valid_resource_name(name)
         body = {'name': name}
         if location:
@@ -1300,25 +1347,23 @@ class GCSBucketService(BaseBucketService):
             else:
                 raise
 
-    @implement(event_pattern="provider.storage.buckets.delete",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, bucket_id):
+    @dispatch(event="provider.storage.buckets.delete",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def delete(self, bucket):
         """
         Delete this bucket.
         """
-        # GCE uses name rather than URL to identify resources
-        name = (self._provider._storage_resources
-                    .parse_url(bucket_id)
-                    .parameters.get("bucket"))
-        (self._provider
-             .gcs_storage
-             .buckets()
-             .delete(bucket=name)
-             .execute())
-        # GCS has a rate limit of 1 operation per 2 seconds for bucket
-        # creation/deletion: https://cloud.google.com/storage/quotas. Throttle
-        # here to avoid future failures.
-        time.sleep(2)
+        b = bucket if isinstance(bucket, GCSBucket) else self.get(bucket)
+        if b:
+            (self.provider
+                 .gcs_storage
+                 .buckets()
+                 .delete(bucket=b.name)
+                 .execute())
+            # GCS has a rate limit of 1 operation per 2 seconds for bucket
+            # creation/deletion: https://cloud.google.com/storage/quotas.
+            # Throttle here to avoid future failures.
+            time.sleep(2)
 
 
 class GCSBucketObjectService(BaseBucketObjectService):

+ 0 - 38
cloudbridge/cloud/providers/openstack/resources.py

@@ -14,8 +14,6 @@ except ImportError:  # python 2
 
 from keystoneclient.v3.regions import Region
 
-from neutronclient.common.exceptions import PortNotFoundClient
-
 import novaclient.exceptions as novaex
 
 from openstack.exceptions import HttpException
@@ -368,17 +366,6 @@ class OpenStackInstance(BaseInstance):
         """
         self._os_instance.reboot()
 
-    def delete(self):
-        """
-        Permanently delete this instance.
-        """
-        # delete the port we created when launching
-        # Assumption: it's the first interface in the list
-        iface_list = self._os_instance.interface_list()
-        if iface_list:
-            self._provider.neutron.delete_port(iface_list[0].port_id)
-        self._os_instance.delete()
-
     @property
     def image_id(self):
         """
@@ -882,21 +869,6 @@ class OpenStackNetwork(BaseNetwork):
         # OpenStack does not define a CIDR block for networks
         return ''
 
-    def delete(self):
-        if not self.external and self.id in str(
-                self._provider.neutron.list_networks()):
-            # If there are ports associated with the network, it won't delete
-            ports = self._provider.neutron.list_ports(
-                network_id=self.id).get('ports', [])
-            for port in ports:
-                try:
-                    self._provider.neutron.delete_port(port.get('id'))
-                except PortNotFoundClient:
-                    # Ports could have already been deleted if instances
-                    # are terminated etc. so exceptions can be safely ignored
-                    pass
-            self._provider.neutron.delete_network(self.id)
-
     @property
     def subnets(self):
         subnets = (self._provider.neutron.list_subnets(network_id=self.id)
@@ -964,10 +936,6 @@ class OpenStackSubnet(BaseSubnet):
         """
         return None
 
-    def delete(self):
-        if self.id in str(self._provider.neutron.list_subnets()):
-            self._provider.neutron.delete_subnet(self.id)
-
     @property
     def state(self):
         return SubnetState.UNKNOWN if self._state == SubnetState.UNKNOWN \
@@ -1088,9 +1056,6 @@ class OpenStackRouter(BaseRouter):
             return ports[0].network_id
         return None
 
-    def delete(self):
-        self._provider.os_conn.delete_router(self.id)
-
     def attach_subnet(self, subnet):
         ret = self._provider.os_conn.add_router_interface(
             self._router.toDict(), subnet.id)
@@ -1254,9 +1219,6 @@ class OpenStackVMFirewall(BaseVMFirewall):
     def rules(self):
         return self._rule_svc
 
-    def delete(self):
-        return self._vm_firewall.delete(self._provider.os_conn.session)
-
     def refresh(self):
         self._vm_firewall = self._provider.os_conn.network.get_security_group(
             self.id)

+ 229 - 182
cloudbridge/cloud/providers/openstack/services.py

@@ -6,6 +6,7 @@ import logging
 from cinderclient.exceptions import NotFound as CinderNotFound
 
 from neutronclient.common.exceptions import NeutronClientException
+from neutronclient.common.exceptions import PortNotFoundClient
 
 from novaclient.exceptions import NotFound as NovaNotFound
 
@@ -15,7 +16,7 @@ from openstack.exceptions import ResourceNotFound
 from swiftclient import ClientException as SwiftClientException
 
 import cloudbridge.cloud.base.helpers as cb_helpers
-from cloudbridge.cloud.base.middleware import implement
+from cloudbridge.cloud.base.middleware import dispatch
 from cloudbridge.cloud.base.resources import BaseLaunchConfig
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketObjectService
@@ -131,9 +132,9 @@ class OpenStackKeyPairService(BaseKeyPairService):
     def __init__(self, provider):
         super(OpenStackKeyPairService, self).__init__(provider)
 
-    @implement(event_pattern="provider.security.key_pairs.get",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _get(self, key_pair_id):
+    @dispatch(event="provider.security.key_pairs.get",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def get(self, key_pair_id):
         """
         Returns a KeyPair given its id.
         """
@@ -145,9 +146,9 @@ class OpenStackKeyPairService(BaseKeyPairService):
             log.debug("KeyPair %s was not found.", key_pair_id)
             return None
 
-    @implement(event_pattern="provider.security.key_pairs.list",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
+    @dispatch(event="provider.security.key_pairs.list",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         """
         List all key pairs associated with this account.
 
@@ -162,9 +163,9 @@ class OpenStackKeyPairService(BaseKeyPairService):
         return ClientPagedResultList(self.provider, results,
                                      limit=limit, marker=marker)
 
-    @implement(event_pattern="provider.security.key_pairs.find",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.security.key_pairs.find",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         name = kwargs.pop('name', None)
 
         # All kwargs should have been popped at this time.
@@ -179,12 +180,10 @@ class OpenStackKeyPairService(BaseKeyPairService):
         log.debug("Searching for %s in: %s", name, keypairs)
         return ClientPagedResultList(self.provider, results)
 
-    @implement(event_pattern="provider.security.key_pairs.create",
-               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
-    def _create(self, name, public_key_material=None):
-        log.debug("Creating a new key pair with the name: %s", name)
+    @dispatch(event="provider.security.key_pairs.create",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def create(self, name, public_key_material=None):
         OpenStackKeyPair.assert_valid_resource_name(name)
-
         existing_kp = self.find(name=name)
         if existing_kp:
             raise DuplicateResourceException(
@@ -200,22 +199,34 @@ class OpenStackKeyPairService(BaseKeyPairService):
         cb_kp.material = private_key
         return cb_kp
 
+    @dispatch(event="provider.security.key_pairs.delete",
+              priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
+    def delete(self, kp):
+        keypair = kp if isinstance(kp, OpenStackKeyPair) else self.get(kp)
+        if keypair:
+            # pylint:disable=protected-access
+            keypair._key_pair.delete()
+
 
 class OpenStackVMFirewallService(BaseVMFirewallService):
 
     def __init__(self, provider):
         super(OpenStackVMFirewallService, self).__init__(provider)
 
-    def get(self, firewall_id):
-        log.debug("Getting OpenStack VM Firewall with the id: %s", firewall_id)
+    @dispatch(event="provider.security.vm_firewalls.get",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
+    def get(self, vm_firewall_id):
         try:
             return OpenStackVMFirewall(
                 self.provider,
-                self.provider.os_conn.network.get_security_group(firewall_id))
+                self.provider.os_conn.network
+                    .get_security_group(vm_firewall_id))
         except (ResourceNotFound, NotFoundException):
-            log.debug("Firewall %s not found.", firewall_id)
+            log.debug("Firewall %s not found.", vm_firewall_id)
             return None
 
+    @dispatch(event="provider.security.vm_firewalls.list",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         firewalls = [
             OpenStackVMFirewall(self.provider, fw)
@@ -225,11 +236,10 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
                                      limit=limit, marker=marker)
 
     @cb_helpers.deprecated_alias(network_id='network')
+    @dispatch(event="provider.security.vm_firewalls.create",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network, description=None):
         OpenStackVMFirewall.assert_valid_resource_label(label)
-        log.debug("Creating OpenStack VM Firewall with the params: "
-                  "[label: %s network id: %s description: %s]", label,
-                  network, description)
         net = network.id if isinstance(network, Network) else network
         # We generally simulate a network being associated with a firewall
         # by storing the supplied value in the firewall description field that
@@ -247,12 +257,13 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
             return OpenStackVMFirewall(self.provider, sg)
         return None
 
-    def delete(self, group_id):
-        log.debug("Deleting OpenStack Firewall with the id: %s", group_id)
-        firewall = self.get(group_id)
-        if firewall:
-            firewall.delete()
-        return True
+    @dispatch(event="provider.security.vm_firewalls.delete",
+              priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
+    def delete(self, vmf):
+        fw = vmf if isinstance(vmf, OpenStackVMFirewall) else self.get(vmf)
+        if fw:
+            # pylint:disable=protected-access
+            fw._vm_firewall.delete(self.provider.os_conn.session)
 
 
 class OpenStackStorageService(BaseStorageService):
@@ -288,13 +299,9 @@ class OpenStackVolumeService(BaseVolumeService):
     def __init__(self, provider):
         super(OpenStackVolumeService, self).__init__(provider)
 
-    @implement(event_pattern="provider.storage.volumes.get",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _get(self, volume_id):
-        """
-        Returns a volume given its id.
-        """
-        log.debug("Getting OpenStack Volume with the id: %s", volume_id)
+    @dispatch(event="provider.storage.volumes.get",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def get(self, volume_id):
         try:
             return OpenStackVolume(
                 self.provider, self.provider.cinder.volumes.get(volume_id))
@@ -302,9 +309,9 @@ class OpenStackVolumeService(BaseVolumeService):
             log.debug("Volume %s was not found.", volume_id)
             return None
 
-    @implement(event_pattern="provider.storage.volumes.find",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.storage.volumes.find",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         label = kwargs.pop('label', None)
 
         # All kwargs should have been popped at this time.
@@ -324,12 +331,9 @@ class OpenStackVolumeService(BaseVolumeService):
 
         return oshelpers.to_server_paged_list(self.provider, cb_vols)
 
-    @implement(event_pattern="provider.storage.volumes.list",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
-        """
-        List all volumes.
-        """
+    @dispatch(event="provider.storage.volumes.list",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         cb_vols = [
             OpenStackVolume(self.provider, vol)
             for vol in self.provider.cinder.volumes.list(
@@ -338,17 +342,10 @@ class OpenStackVolumeService(BaseVolumeService):
 
         return oshelpers.to_server_paged_list(self.provider, cb_vols, limit)
 
-    @implement(event_pattern="provider.storage.volumes.create",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _create(self, label, size, zone, snapshot=None, description=None):
-        """
-        Creates a new volume.
-        """
-        log.debug("Creating a new volume with the params: "
-                  "[label: %s size: %s zone: %s snapshot: %s description: %s]",
-                  label, size, zone, snapshot, description)
+    @dispatch(event="provider.storage.volumes.create",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, size, zone, snapshot=None, description=None):
         OpenStackVolume.assert_valid_resource_label(label)
-
         zone_id = zone.id if isinstance(zone, PlacementZone) else zone
         snapshot_id = snapshot.id if isinstance(
             snapshot, OpenStackSnapshot) and snapshot else snapshot
@@ -358,13 +355,13 @@ class OpenStackVolumeService(BaseVolumeService):
             availability_zone=zone_id, snapshot_id=snapshot_id)
         return OpenStackVolume(self.provider, os_vol)
 
-    @implement(event_pattern="provider.storage.volumes.delete",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, volume):
-        cb_vol = (volume if isinstance(volume, OpenStackVolume)
-                  else self.get(volume))
-        # pylint:disable=protected-access
-        cb_vol._volume.delete()
+    @dispatch(event="provider.storage.volumes.delete",
+              priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
+    def delete(self, vol):
+        volume = vol if isinstance(vol, OpenStackVolume) else self.get(vol)
+        if volume:
+            # pylint:disable=protected-access
+            volume._volume.delete()
 
 
 class OpenStackSnapshotService(BaseSnapshotService):
@@ -372,13 +369,9 @@ class OpenStackSnapshotService(BaseSnapshotService):
     def __init__(self, provider):
         super(OpenStackSnapshotService, self).__init__(provider)
 
-    @implement(event_pattern="provider.storage.snapshots.get",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _get(self, snapshot_id):
-        """
-        Returns a snapshot given its id.
-        """
-        log.debug("Getting OpenStack snapshot with the id: %s", snapshot_id)
+    @dispatch(event="provider.storage.snapshots.get",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def get(self, snapshot_id):
         try:
             return OpenStackSnapshot(
                 self.provider,
@@ -387,9 +380,9 @@ class OpenStackSnapshotService(BaseSnapshotService):
             log.debug("Snapshot %s was not found.", snapshot_id)
             return None
 
-    @implement(event_pattern="provider.storage.snapshots.find",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.storage.snapshots.find",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         label = kwargs.pop('label', None)
 
         # All kwargs should have been popped at this time.
@@ -410,12 +403,9 @@ class OpenStackSnapshotService(BaseSnapshotService):
 
         return oshelpers.to_server_paged_list(self.provider, cb_snaps)
 
-    @implement(event_pattern="provider.storage.snapshots.list",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit=None, marker=None):
-        """
-        List all snapshot.
-        """
+    @dispatch(event="provider.storage.snapshots.list",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         cb_snaps = [
             OpenStackSnapshot(self.provider, snap) for
             snap in self.provider.cinder.volume_snapshots.list(
@@ -424,15 +414,10 @@ class OpenStackSnapshotService(BaseSnapshotService):
                              'marker': marker})]
         return oshelpers.to_server_paged_list(self.provider, cb_snaps, limit)
 
-    @implement(event_pattern="provider.storage.snapshots.create",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _create(self, label, volume, description=None):
-        """
-        Creates a new snapshot of a given volume.
-        """
-        log.debug("Creating a new snapshot of the %s volume.", label)
+    @dispatch(event="provider.storage.snapshots.create",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def create(self, label, volume, description=None):
         OpenStackSnapshot.assert_valid_resource_label(label)
-
         volume_id = (volume.id if isinstance(volume, OpenStackVolume)
                      else volume)
 
@@ -441,13 +426,13 @@ class OpenStackSnapshotService(BaseSnapshotService):
             description=description)
         return OpenStackSnapshot(self.provider, os_snap)
 
-    @implement(event_pattern="provider.storage.snapshots.delete",
-               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, snapshot):
-        cb_snap = (snapshot if isinstance(snapshot, OpenStackSnapshot)
-                   else self.get(snapshot))
-        # pylint:disable=protected-access
-        cb_snap._snapshot.delete()
+    @dispatch(event="provider.storage.snapshots.delete",
+              priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
+    def delete(self, snap):
+        s = snap if isinstance(snap, OpenStackSnapshot) else self.get(snap)
+        if s:
+            # pylint:disable=protected-access
+            s._snapshot.delete()
 
 
 class OpenStackBucketService(BaseBucketService):
@@ -455,9 +440,9 @@ class OpenStackBucketService(BaseBucketService):
     def __init__(self, provider):
         super(OpenStackBucketService, self).__init__(provider)
 
-    @implement(event_pattern="provider.storage.buckets.get",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _get(self, bucket_id):
+    @dispatch(event="provider.storage.buckets.get",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def get(self, bucket_id):
         """
         Returns a bucket given its ID. Returns ``None`` if the bucket
         does not exist.
@@ -472,9 +457,9 @@ class OpenStackBucketService(BaseBucketService):
             log.debug("Bucket %s was not found.", bucket_id)
             return None
 
-    @implement(event_pattern="provider.storage.buckets.find",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _find(self, **kwargs):
+    @dispatch(event="provider.storage.buckets.find",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def find(self, **kwargs):
         name = kwargs.pop('name', None)
 
         # All kwargs should have been popped at this time.
@@ -488,9 +473,9 @@ class OpenStackBucketService(BaseBucketService):
                       if name in c.get("name")]
         return oshelpers.to_server_paged_list(self.provider, cb_buckets)
 
-    @implement(event_pattern="provider.storage.buckets.list",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _list(self, limit, marker):
+    @dispatch(event="provider.storage.buckets.list",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def list(self, limit=None, marker=None):
         _, container_list = self.provider.swift.get_account(
             limit=oshelpers.os_result_limit(self.provider, limit),
             marker=marker)
@@ -498,9 +483,9 @@ class OpenStackBucketService(BaseBucketService):
                       for c in container_list]
         return oshelpers.to_server_paged_list(self.provider, cb_buckets, limit)
 
-    @implement(event_pattern="provider.storage.buckets.create",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _create(self, name, location):
+    @dispatch(event="provider.storage.buckets.create",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def create(self, name, location=None):
         OpenStackBucket.assert_valid_resource_name(name)
         location = location or self.provider.region_name
         try:
@@ -511,10 +496,11 @@ class OpenStackBucketService(BaseBucketService):
             self.provider.swift.put_container(name)
             return self.get(name)
 
-    @implement(event_pattern="provider.storage.buckets.delete",
-               priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
-    def _delete(self, bucket_id):
-        self.provider.swift.delete_container(bucket_id)
+    @dispatch(event="provider.storage.buckets.delete",
+              priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
+    def delete(self, bucket):
+        b_id = bucket.id if isinstance(bucket, OpenStackBucket) else bucket
+        self.provider.swift.delete_container(b_id)
 
 
 class OpenStackBucketObjectService(BaseBucketObjectService):
@@ -641,12 +627,65 @@ class OpenStackInstanceService(BaseInstanceService):
     def __init__(self, provider):
         super(OpenStackInstanceService, self).__init__(provider)
 
+    def _to_block_device_mapping(self, launch_config):
+        """
+        Extracts block device mapping information
+        from a launch config and constructs a BlockDeviceMappingV2
+        object.
+        """
+        bdm = []
+        for device in launch_config.block_devices:
+            bdm_dict = dict()
+
+            if device.is_volume:
+                bdm_dict['destination_type'] = 'volume'
+
+                if device.is_root:
+                    bdm_dict['device_name'] = '/dev/sda'
+                    bdm_dict['boot_index'] = 0
+
+                if isinstance(device.source, Snapshot):
+                    bdm_dict['source_type'] = 'snapshot'
+                    bdm_dict['uuid'] = device.source.id
+                elif isinstance(device.source, Volume):
+                    bdm_dict['source_type'] = 'volume'
+                    bdm_dict['uuid'] = device.source.id
+                elif isinstance(device.source, MachineImage):
+                    bdm_dict['source_type'] = 'image'
+                    bdm_dict['uuid'] = device.source.id
+                else:
+                    bdm_dict['source_type'] = 'blank'
+
+                if device.delete_on_terminate is not None:
+                    bdm_dict[
+                        'delete_on_termination'] = device.delete_on_terminate
+
+                if device.size:
+                    bdm_dict['volume_size'] = device.size
+            else:
+                bdm_dict['destination_type'] = 'local'
+                bdm_dict['source_type'] = 'blank'
+                bdm_dict['delete_on_termination'] = True
+            bdm.append(bdm_dict)
+        return bdm
+
+    def _has_root_device(self, launch_config):
+        if not launch_config:
+            return False
+        for device in launch_config.block_devices:
+            if device.is_root:
+                return True
+        return False
+
+    def create_launch_config(self):
+        return BaseLaunchConfig(self.provider)
+
+    @dispatch(event="provider.compute.instances.create",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def create(self, label, image, vm_type, subnet, zone,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
-        """Create a new virtual machine instance."""
         OpenStackInstance.assert_valid_resource_label(label)
-
         image_id = image.id if isinstance(image, MachineImage) else image
         vm_size = vm_type.id if \
             isinstance(vm_type, VMType) else \
@@ -724,59 +763,8 @@ class OpenStackInstanceService(BaseInstanceService):
             nics=nics)
         return OpenStackInstance(self.provider, os_instance)
 
-    def _to_block_device_mapping(self, launch_config):
-        """
-        Extracts block device mapping information
-        from a launch config and constructs a BlockDeviceMappingV2
-        object.
-        """
-        bdm = []
-        for device in launch_config.block_devices:
-            bdm_dict = dict()
-
-            if device.is_volume:
-                bdm_dict['destination_type'] = 'volume'
-
-                if device.is_root:
-                    bdm_dict['device_name'] = '/dev/sda'
-                    bdm_dict['boot_index'] = 0
-
-                if isinstance(device.source, Snapshot):
-                    bdm_dict['source_type'] = 'snapshot'
-                    bdm_dict['uuid'] = device.source.id
-                elif isinstance(device.source, Volume):
-                    bdm_dict['source_type'] = 'volume'
-                    bdm_dict['uuid'] = device.source.id
-                elif isinstance(device.source, MachineImage):
-                    bdm_dict['source_type'] = 'image'
-                    bdm_dict['uuid'] = device.source.id
-                else:
-                    bdm_dict['source_type'] = 'blank'
-
-                if device.delete_on_terminate is not None:
-                    bdm_dict[
-                        'delete_on_termination'] = device.delete_on_terminate
-
-                if device.size:
-                    bdm_dict['volume_size'] = device.size
-            else:
-                bdm_dict['destination_type'] = 'local'
-                bdm_dict['source_type'] = 'blank'
-                bdm_dict['delete_on_termination'] = True
-            bdm.append(bdm_dict)
-        return bdm
-
-    def _has_root_device(self, launch_config):
-        if not launch_config:
-            return False
-        for device in launch_config.block_devices:
-            if device.is_root:
-                return True
-        return False
-
-    def create_launch_config(self):
-        return BaseLaunchConfig(self.provider)
-
+    @dispatch(event="provider.compute.instances.find",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         label = kwargs.pop('label', None)
 
@@ -795,6 +783,8 @@ class OpenStackInstanceService(BaseInstanceService):
                 marker=None)]
         return oshelpers.to_server_paged_list(self.provider, cb_insts)
 
+    @dispatch(event="provider.compute.instances.list",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         """
         List all instances.
@@ -806,6 +796,8 @@ class OpenStackInstanceService(BaseInstanceService):
                 marker=marker)]
         return oshelpers.to_server_paged_list(self.provider, cb_insts, limit)
 
+    @dispatch(event="provider.compute.instances.get",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def get(self, instance_id):
         """
         Returns an instance given its id.
@@ -817,12 +809,28 @@ class OpenStackInstanceService(BaseInstanceService):
             log.debug("Instance %s was not found.", instance_id)
             return None
 
+    @dispatch(event="provider.compute.instances.delete",
+              priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
+    def delete(self, inst):
+        ins = inst if isinstance(inst, OpenStackInstance) else self.get(inst)
+        if ins:
+            # pylint:disable=protected-access
+            os_instance = ins._os_instance
+            # delete the port we created when launching
+            # Assumption: it's the first interface in the list
+            iface_list = os_instance.interface_list()
+            if iface_list:
+                self.provider.neutron.delete_port(iface_list[0].port_id)
+            os_instance.delete()
+
 
 class OpenStackVMTypeService(BaseVMTypeService):
 
     def __init__(self, provider):
         super(OpenStackVMTypeService, self).__init__(provider)
 
+    @dispatch(event="provider.compute.vm_types.list",
+              priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         cb_itypes = [
             OpenStackVMType(self.provider, obj)
@@ -838,11 +846,15 @@ class OpenStackRegionService(BaseRegionService):
     def __init__(self, provider):
         super(OpenStackRegionService, self).__init__(provider)
 
+    @dispatch(event="provider.compute.regions.get",
+              priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
     def get(self, region_id):
         log.debug("Getting OpenStack Region with the id: %s", region_id)
         region = (r for r in self if r.id == region_id)
         return next(region, None)
 
+    @dispatch(event="provider.compute.regions.list",
+              priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         # pylint:disable=protected-access
         if self.provider._keystone_version == 3:
@@ -897,11 +909,14 @@ class OpenStackNetworkService(BaseNetworkService):
     def __init__(self, provider):
         super(OpenStackNetworkService, self).__init__(provider)
 
+    @dispatch(event="provider.networking.networks.get",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def get(self, network_id):
-        log.debug("Getting OpenStack Network with the id: %s", network_id)
         network = (n for n in self if n.id == network_id)
         return next(network, None)
 
+    @dispatch(event="provider.networking.networks.list",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         networks = [OpenStackNetwork(self.provider, network)
                     for network in self.provider.neutron.list_networks()
@@ -909,6 +924,8 @@ class OpenStackNetworkService(BaseNetworkService):
         return ClientPagedResultList(self.provider, networks,
                                      limit=limit, marker=marker)
 
+    @dispatch(event="provider.networking.networks.find",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         label = kwargs.pop('label', None)
 
@@ -925,9 +942,9 @@ class OpenStackNetworkService(BaseNetworkService):
                     .get('networks') if network]
         return ClientPagedResultList(self.provider, networks)
 
+    @dispatch(event="provider.networking.networks.create",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def create(self, label, cidr_block):
-        log.debug("Creating OpenStack Network with the params: "
-                  "[label: %s Cinder Block: %s]", label, cidr_block)
         OpenStackNetwork.assert_valid_resource_label(label)
         net_info = {'name': label or ""}
         network = self.provider.neutron.create_network({'network': net_info})
@@ -936,17 +953,40 @@ class OpenStackNetworkService(BaseNetworkService):
             cb_net.label = label
         return cb_net
 
+    @dispatch(event="provider.networking.networks.delete",
+              priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
+    def delete(self, net):
+        network = net if isinstance(net, OpenStackNetwork) else self.get(net)
+        if not network:
+            return
+        if not network.external and network.id in str(
+                self.provider.neutron.list_networks()):
+            # If there are ports associated with the network, it won't delete
+            ports = self.provider.neutron.list_ports(
+                network_id=network.id).get('ports', [])
+            for port in ports:
+                try:
+                    self.provider.neutron.delete_port(port.get('id'))
+                except PortNotFoundClient:
+                    # Ports could have already been deleted if instances
+                    # are terminated etc. so exceptions can be safely ignored
+                    pass
+            self.provider.neutron.delete_network(network.id)
+
 
 class OpenStackSubnetService(BaseSubnetService):
 
     def __init__(self, provider):
         super(OpenStackSubnetService, self).__init__(provider)
 
+    @dispatch(event="provider.networking.subnets.get",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def get(self, subnet_id):
-        log.debug("Getting OpenStack Subnet with the id: %s", subnet_id)
         subnet = (s for s in self if s.id == subnet_id)
         return next(subnet, None)
 
+    @dispatch(event="provider.networking.subnets.list",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def list(self, network=None, limit=None, marker=None):
         if network:
             network_id = (network.id if isinstance(network, OpenStackNetwork)
@@ -959,11 +999,10 @@ class OpenStackSubnetService(BaseSubnetService):
         return ClientPagedResultList(self.provider, subnets,
                                      limit=limit, marker=marker)
 
+    @dispatch(event="provider.networking.subnets.create",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network, cidr_block, zone):
         """zone param is ignored."""
-        log.debug("Creating OpenStack Subnet with the params: "
-                  "[Label: %s Network: %s Cinder Block: %s Zone: -ignored-]",
-                  label, network, cidr_block)
         OpenStackSubnet.assert_valid_resource_label(label)
         network_id = (network.id if isinstance(network, OpenStackNetwork)
                       else network)
@@ -974,6 +1013,12 @@ class OpenStackSubnetService(BaseSubnetService):
         cb_subnet = OpenStackSubnet(self.provider, subnet)
         return cb_subnet
 
+    @dispatch(event="provider.networking.subnets.delete",
+              priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
+    def delete(self, subnet):
+        sn_id = subnet.id if isinstance(subnet, OpenStackSubnet) else subnet
+        self.provider.neutron.delete_subnet(sn_id)
+
     def get_or_create_default(self, zone):
         """
         Subnet zone is not supported by OpenStack and is thus ignored.
@@ -997,42 +1042,44 @@ class OpenStackSubnetService(BaseSubnetService):
         except NeutronClientException:
             return None
 
-    def delete(self, subnet):
-        log.debug("Deleting subnet: %s", subnet)
-        subnet_id = (subnet.id if isinstance(subnet, OpenStackSubnet)
-                     else subnet)
-        self.provider.neutron.delete_subnet(subnet_id)
-        # Adhere to the interface docs
-        if subnet_id not in self:
-            return True
-        return False
-
 
 class OpenStackRouterService(BaseRouterService):
 
     def __init__(self, provider):
         super(OpenStackRouterService, self).__init__(provider)
 
+    @dispatch(event="provider.networking.routers.get",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def get(self, router_id):
         log.debug("Getting OpenStack Router with the id: %s", router_id)
         router = self.provider.os_conn.get_router(router_id)
         return OpenStackRouter(self.provider, router) if router else None
 
+    @dispatch(event="provider.networking.routers.list",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         routers = self.provider.os_conn.list_routers()
         os_routers = [OpenStackRouter(self.provider, r) for r in routers]
         return ClientPagedResultList(self.provider, os_routers, limit=limit,
                                      marker=marker)
 
+    @dispatch(event="provider.networking.routers.find",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
         obj_list = self
         filters = ['label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         return ClientPagedResultList(self._provider, list(matches))
 
+    @dispatch(event="provider.networking.routers.create",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def create(self, label, network):
         """Parameter ``network`` is not used by OpenStack."""
-        log.debug("Creating OpenStack Router with the label: %s", label)
-        OpenStackRouter.assert_valid_resource_label(label)
         router = self.provider.os_conn.create_router(name=label)
         return OpenStackRouter(self.provider, router)
+
+    @dispatch(event="provider.networking.routers.delete",
+              priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
+    def delete(self, router):
+        r_id = router.id if isinstance(router, OpenStackRouter) else router
+        self.provider.os_conn.delete_router(r_id)

+ 2 - 2
docs/topics/event_system.rst

@@ -116,7 +116,7 @@ getter, from the CloudBridge developer perspective.
         def __init__(self, provider):
             super(MyFirstProviderService, self).__init__(provider)
 
-        def _get(self, obj_id):
+        def get(self, obj_id):
             # do the getting
             resource = ...
             return MyFirstProviderResource(resource)
@@ -126,7 +126,7 @@ getter, from the CloudBridge developer perspective.
         def __init__(self, provider):
             super(MySecondProviderService, self).__init__(provider)
 
-        def _get(self, obj_id):
+        def get(self, obj_id):
             # do the getting
             resource = ...
             return MySecondProviderResource(resource)

+ 3 - 3
test/test_middleware_system.py

@@ -5,7 +5,7 @@ from cloudbridge.cloud.base.middleware import BaseMiddleware
 from cloudbridge.cloud.base.middleware import EventDebugLoggingMiddleware
 from cloudbridge.cloud.base.middleware import ExceptionWrappingMiddleware
 from cloudbridge.cloud.base.middleware import SimpleMiddlewareManager
-from cloudbridge.cloud.base.middleware import dispatch_event
+from cloudbridge.cloud.base.middleware import dispatch
 from cloudbridge.cloud.base.middleware import implement
 from cloudbridge.cloud.base.middleware import intercept
 from cloudbridge.cloud.base.middleware import observe
@@ -233,7 +233,7 @@ class MiddlewareSystemTestCase(unittest.TestCase):
                 assert event_args['result'] == "hello"
                 assert kwargs.get('a_keyword_arg') == "something"
 
-            @dispatch_event(EVENT_NAME)
+            @dispatch(event=EVENT_NAME, priority=2500)
             def my_callback_impl(self, *args, **kwargs):
                 self.invocation_order += "impl_"
                 assert 'first_pos_arg' in args
@@ -279,7 +279,7 @@ class MiddlewareSystemTestCase(unittest.TestCase):
 
         class SomeDummyClass(object):
 
-            @dispatch_event(EVENT_NAME)
+            @dispatch(event=EVENT_NAME, priority=2500)
             def my_callback_impl(self, *args, **kwargs):
                 assert 'first_pos_arg' in args
                 assert kwargs.get('a_keyword_arg') == "something"

+ 1 - 1
test/test_security_service.py

@@ -44,7 +44,7 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
 
         def cleanup_kp(kp):
             if kp:
-                self.provider.security.key_pairs.delete(key_pair_id=kp.id)
+                self.provider.security.key_pairs.delete(kp.id)
 
         def extra_tests(kp):
             # Recreating existing keypair should raise an exception