瀏覽代碼

Merge pull request #186 from CloudVE/subservices

[WIP] Subservices
Nuwan Goonasekera 7 年之前
父節點
當前提交
5631c5cb98
共有 36 個文件被更改,包括 2031 次插入1294 次删除
  1. 2 2
      cloudbridge/__init__.py
  2. 17 92
      cloudbridge/cloud/base/resources.py
  3. 71 37
      cloudbridge/cloud/base/services.py
  4. 130 0
      cloudbridge/cloud/base/subservices.py
  5. 7 323
      cloudbridge/cloud/interfaces/resources.py
  6. 331 8
      cloudbridge/cloud/interfaces/services.py
  7. 320 0
      cloudbridge/cloud/interfaces/subservices.py
  8. 5 15
      cloudbridge/cloud/providers/aws/helpers.py
  9. 12 170
      cloudbridge/cloud/providers/aws/resources.py
  10. 212 12
      cloudbridge/cloud/providers/aws/services.py
  11. 33 0
      cloudbridge/cloud/providers/aws/subservices.py
  12. 16 156
      cloudbridge/cloud/providers/azure/resources.py
  13. 202 11
      cloudbridge/cloud/providers/azure/services.py
  14. 32 0
      cloudbridge/cloud/providers/azure/subservices.py
  15. 1 0
      cloudbridge/cloud/providers/gce/helpers.py
  16. 4 2
      cloudbridge/cloud/providers/gce/provider.py
  17. 58 220
      cloudbridge/cloud/providers/gce/resources.py
  18. 254 45
      cloudbridge/cloud/providers/gce/services.py
  19. 33 0
      cloudbridge/cloud/providers/gce/subservices.py
  20. 20 156
      cloudbridge/cloud/providers/openstack/resources.py
  21. 203 27
      cloudbridge/cloud/providers/openstack/services.py
  22. 35 0
      cloudbridge/cloud/providers/openstack/subservices.py
  23. 2 2
      docs/api_docs/cloud/services.rst
  24. 2 2
      docs/getting_started.rst
  25. 1 1
      docs/topics/aws_mapping.rst
  26. 1 1
      docs/topics/launch.rst
  27. 2 2
      docs/topics/networking.rst
  28. 1 1
      docs/topics/object_storage.rst
  29. 2 1
      setup.py
  30. 1 1
      test/helpers/__init__.py
  31. 2 0
      test/test_block_store_service.py
  32. 5 0
      test/test_cloud_helpers.py
  33. 2 1
      test/test_compute_service.py
  34. 4 1
      test/test_network_service.py
  35. 7 5
      test/test_object_store_service.py
  36. 1 0
      test/test_region_service.py

+ 2 - 2
cloudbridge/__init__.py

@@ -53,7 +53,7 @@ class CBLogger(logging.Logger):
 #   import cloudbridge
 #   cloudbridge.set_stream_logger(__name__)
 #   OR
-#   cloudbridge.set_file_logger(__name__, '/tmp/cb.log')
+#   cloudbridge.set_file_logger(__name__, '/tmp/log')
 default_format_string = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
 logging.setLoggerClass(CBLogger)
 logging.addLevelName(TRACE, "TRACE")
@@ -66,7 +66,7 @@ log.addHandler(NullHandler())
 #   import cloudbridge
 #   cloudbridge.set_stream_logger(__name__)
 #   OR
-#   cloudbridge.set_file_logger(__name__, '/tmp/cb.log')
+#   cloudbridge.set_file_logger(__name__, '/tmp/log')
 
 
 def set_stream_logger(name, level=TRACE, format_string=None):

+ 17 - 92
cloudbridge/cloud/base/resources.py

@@ -12,21 +12,17 @@ import uuid
 
 import six
 
-import cloudbridge.cloud.base.helpers as cb_helpers
-from cloudbridge.cloud.interfaces.exceptions \
-    import InvalidConfigurationException
+from cloudbridge.cloud.interfaces.exceptions import \
+    InvalidConfigurationException
 from cloudbridge.cloud.interfaces.exceptions import InvalidLabelException
 from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
 from cloudbridge.cloud.interfaces.exceptions import WaitStateException
 from cloudbridge.cloud.interfaces.resources import AttachmentInfo
 from cloudbridge.cloud.interfaces.resources import Bucket
-from cloudbridge.cloud.interfaces.resources import BucketContainer
 from cloudbridge.cloud.interfaces.resources import BucketObject
 from cloudbridge.cloud.interfaces.resources import CloudResource
 from cloudbridge.cloud.interfaces.resources import FloatingIP
-from cloudbridge.cloud.interfaces.resources import FloatingIPContainer
 from cloudbridge.cloud.interfaces.resources import FloatingIpState
-from cloudbridge.cloud.interfaces.resources import GatewayContainer
 from cloudbridge.cloud.interfaces.resources import GatewayState
 from cloudbridge.cloud.interfaces.resources import Instance
 from cloudbridge.cloud.interfaces.resources import InstanceState
@@ -49,11 +45,12 @@ from cloudbridge.cloud.interfaces.resources import Subnet
 from cloudbridge.cloud.interfaces.resources import SubnetState
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMFirewallRule
-from cloudbridge.cloud.interfaces.resources import VMFirewallRuleContainer
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import Volume
 from cloudbridge.cloud.interfaces.resources import VolumeState
 
+from . import helpers as cb_helpers
+
 log = logging.getLogger(__name__)
 
 
@@ -589,37 +586,6 @@ class BaseVMFirewall(BaseCloudResource, VMFirewall):
         return self._provider.security.vm_firewalls.delete(self)
 
 
-class BaseVMFirewallRuleContainer(BasePageableObjectMixin,
-                                  VMFirewallRuleContainer):
-
-    def __init__(self, provider, firewall):
-        self.__provider = provider
-        self.firewall = firewall
-
-    @property
-    def _provider(self):
-        return self.__provider
-
-    def get(self, rule_id):
-        matches = [rule for rule in self if rule.id == rule_id]
-        if matches:
-            return matches[0]
-        else:
-            return None
-
-    def find(self, **kwargs):
-        obj_list = self
-        filters = ['name', 'direction', 'protocol', 'from_port', 'to_port',
-                   'cidr', 'src_dest_fw', 'src_dest_fw_id']
-        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
-        return ClientPagedResultList(self._provider, list(matches))
-
-    def delete(self, rule_id):
-        rule = self.get(rule_id)
-        if rule:
-            rule.delete()
-
-
 class BaseVMFirewallRule(BaseCloudResource, VMFirewallRule):
 
     def __init__(self, parent_fw, rule):
@@ -675,6 +641,9 @@ class BaseVMFirewallRule(BaseCloudResource, VMFirewallRule):
         js['firewall'] = self.firewall.id
         return js
 
+    def delete(self):
+        self._provider.security._vm_firewall_rules.delete(self.firewall, self)
+
 
 class BasePlacementZone(BaseCloudResource, PlacementZone):
 
@@ -768,38 +737,6 @@ class BaseBucket(BaseCloudResource, Bucket):
     # TODO: Discuss creating `create_object` method, or change docs
 
 
-class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
-
-    def __init__(self, provider, bucket):
-        self.__provider = provider
-        self.bucket = bucket
-
-    @property
-    def _provider(self):
-        return self.__provider
-
-    def get(self, name):
-        return self._provider.storage.bucket_objects.get(self.bucket, name)
-
-    def list(self, limit=None, marker=None, prefix=None):
-        return self._provider.storage.bucket_objects.list(self.bucket, limit,
-                                                          marker, prefix)
-
-    def find(self, **kwargs):
-        return self._provider.storage.bucket_objects.find(self.bucket,
-                                                          **kwargs)
-
-    def create(self, name):
-        return self._provider.storage.bucket_objects.create(self.bucket, name)
-
-
-class BaseGatewayContainer(GatewayContainer, BasePageableObjectMixin):
-
-    def __init__(self, provider, network):
-        self._network = network
-        self._provider = provider
-
-
 class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
 
     CB_DEFAULT_NETWORK_LABEL = os.environ.get('CB_DEFAULT_NETWORK_LABEL',
@@ -875,28 +812,6 @@ class BaseSubnet(BaseCloudResource, BaseObjectLifeCycleMixin, Subnet):
         self._provider.networking.subnets.delete(self)
 
 
-class BaseFloatingIPContainer(FloatingIPContainer, BasePageableObjectMixin):
-
-    def __init__(self, provider, gateway):
-        self.__provider = provider
-        self.gateway = gateway
-
-    @property
-    def _provider(self):
-        return self.__provider
-
-    def find(self, **kwargs):
-        obj_list = self
-        filters = ['name', 'public_ip']
-        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
-        return ClientPagedResultList(self._provider, list(matches))
-
-    def delete(self, fip_id):
-        floating_ip = self.get(fip_id)
-        if floating_ip:
-            floating_ip.delete()
-
-
 class BaseFloatingIP(BaseCloudResource, BaseObjectLifeCycleMixin, FloatingIP):
 
     def __init__(self, provider):
@@ -924,6 +839,12 @@ class BaseFloatingIP(BaseCloudResource, BaseObjectLifeCycleMixin, FloatingIP):
                 self._provider == other._provider and
                 self.id == other.id)
 
+    def delete(self):
+        # For OS where the gateway is necessary, we pass the gateway when
+        # deleting, for all others we pass None and it will be ignored
+        gw = getattr(self, '_gateway_id', None)
+        self._provider.networking._floating_ips.delete(gw, self.id)
+
 
 class BaseRouter(BaseCloudResource, Router):
 
@@ -965,3 +886,7 @@ class BaseInternetGateway(BaseCloudResource, BaseObjectLifeCycleMixin,
             terminal_states=[GatewayState.ERROR, GatewayState.UNKNOWN],
             timeout=timeout,
             interval=interval)
+
+    def delete(self):
+        return self._provider.networking._gateways.delete(self.network_id,
+                                                          self)

+ 71 - 37
cloudbridge/cloud/base/services.py

@@ -3,20 +3,14 @@ Base implementation for services available through a provider
 """
 import logging
 
-import cloudbridge.cloud.base.helpers as cb_helpers
-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
-from cloudbridge.cloud.base.resources import BaseSubnet
-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.services import BucketObjectService
 from cloudbridge.cloud.interfaces.services import BucketService
 from cloudbridge.cloud.interfaces.services import CloudService
 from cloudbridge.cloud.interfaces.services import ComputeService
+from cloudbridge.cloud.interfaces.services import FloatingIPService
+from cloudbridge.cloud.interfaces.services import GatewayService
 from cloudbridge.cloud.interfaces.services import ImageService
 from cloudbridge.cloud.interfaces.services import InstanceService
 from cloudbridge.cloud.interfaces.services import KeyPairService
@@ -28,11 +22,17 @@ from cloudbridge.cloud.interfaces.services import SecurityService
 from cloudbridge.cloud.interfaces.services import SnapshotService
 from cloudbridge.cloud.interfaces.services import StorageService
 from cloudbridge.cloud.interfaces.services import SubnetService
+from cloudbridge.cloud.interfaces.services import VMFirewallRuleService
 from cloudbridge.cloud.interfaces.services import VMFirewallService
 from cloudbridge.cloud.interfaces.services import VMTypeService
 from cloudbridge.cloud.interfaces.services import VolumeService
 
+from . import helpers as cb_helpers
+from .middleware import dispatch
+from .resources import BaseNetwork
 from .resources import BasePageableObjectMixin
+from .resources import BaseRouter
+from .resources import BaseSubnet
 from .resources import ClientPagedResultList
 
 log = logging.getLogger(__name__)
@@ -95,6 +95,37 @@ class BaseVMFirewallService(
                                      matches if matches else [])
 
 
+class BaseVMFirewallRuleService(BasePageableObjectMixin,
+                                VMFirewallRuleService,
+                                BaseCloudService):
+
+    def __init__(self, provider):
+        super(BaseVMFirewallRuleService, self).__init__(provider)
+        self._provider = provider
+
+    @property
+    def provider(self):
+        return self._provider
+
+    @dispatch(event="provider.security.vm_firewall_rules.get",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
+    def get(self, firewall, rule_id):
+        matches = [rule for rule in firewall.rules if rule.id == rule_id]
+        if matches:
+            return matches[0]
+        else:
+            return None
+
+    @dispatch(event="provider.security.vm_firewall_rules.find",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
+    def find(self, firewall, **kwargs):
+        obj_list = firewall.rules
+        filters = ['name', 'direction', 'protocol', 'from_port', 'to_port',
+                   'cidr', 'src_dest_fw', 'src_dest_fw_id']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
+
+
 class BaseStorageService(StorageService, BaseCloudService):
 
     def __init__(self, provider):
@@ -143,40 +174,13 @@ class BaseBucketService(
                                      matches if matches else [])
 
 
-class BaseBucketObjectService(
-        BasePageableObjectMixin, BucketObjectService, BaseCloudService):
+class BaseBucketObjectService(BucketObjectService, BaseCloudService):
 
     def __init__(self, provider):
         super(BaseBucketObjectService, self).__init__(provider)
-        self._service_event_pattern += ".storage.bucket_objects"
+        self._service_event_pattern += ".storage._bucket_objects"
         self._bucket = None
 
-    # Default bucket needs to be set in order for the service to be iterable
-    def set_bucket(self, bucket):
-        bucket = bucket if isinstance(bucket, BaseBucket) \
-                 else self.provider.storage.buckets.get(bucket)
-        self._bucket = bucket
-
-    def __iter__(self):
-        if not self._bucket:
-            message = "You must set a bucket before iterating through its " \
-                      "objects. We do not allow iterating through all " \
-                      "buckets at this time. In order to set a bucket, use: " \
-                      "`provider.storage.bucket_objects.set_bucket(my_bucket)`"
-            raise InvalidConfigurationException(message)
-        result_list = self.list(bucket=self._bucket)
-        if result_list.supports_server_paging:
-            for result in result_list:
-                yield result
-            while result_list.is_truncated:
-                result_list = self.list(bucket=self._bucket,
-                                        marker=result_list.marker)
-                for result in result_list:
-                    yield result
-        else:
-            for result in result_list.data:
-                yield result
-
 
 class BaseComputeService(ComputeService, BaseCloudService):
 
@@ -333,3 +337,33 @@ class BaseRouterService(
         else:
             return self.provider.networking.routers.create(
                 network=net_id, label=BaseRouter.CB_DEFAULT_ROUTER_LABEL)
+
+
+class BaseGatewayService(GatewayService, BaseCloudService):
+
+    def __init__(self, provider):
+        super(BaseGatewayService, self).__init__(provider)
+        self._provider = provider
+
+    @property
+    def provider(self):
+        return self._provider
+
+
+class BaseFloatingIPService(FloatingIPService, BaseCloudService):
+
+    def __init__(self, provider):
+        super(BaseFloatingIPService, self).__init__(provider)
+        self._provider = provider
+
+    @property
+    def provider(self):
+        return self._provider
+
+    @dispatch(event="provider.networking.floating_ips.find",
+              priority=BaseCloudService.STANDARD_EVENT_PRIORITY)
+    def find(self, gateway, **kwargs):
+        obj_list = gateway.floating_ips
+        filters = ['name', 'public_ip']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))

+ 130 - 0
cloudbridge/cloud/base/subservices.py

@@ -0,0 +1,130 @@
+import logging
+
+from cloudbridge.cloud.interfaces.subservices import BucketObjectSubService
+from cloudbridge.cloud.interfaces.subservices import FloatingIPSubService
+from cloudbridge.cloud.interfaces.subservices import GatewaySubService
+from cloudbridge.cloud.interfaces.subservices import VMFirewallRuleSubService
+
+from .resources import BasePageableObjectMixin
+
+log = logging.getLogger(__name__)
+
+
+class BaseBucketObjectSubService(BasePageableObjectMixin,
+                                 BucketObjectSubService):
+
+    def __init__(self, provider, bucket):
+        self.__provider = provider
+        self.bucket = bucket
+
+    @property
+    def _provider(self):
+        return self.__provider
+
+    def get(self, name):
+        return self._provider.storage._bucket_objects.get(self.bucket, name)
+
+    def list(self, limit=None, marker=None, prefix=None):
+        return self._provider.storage._bucket_objects.list(self.bucket, limit,
+                                                           marker, prefix)
+
+    def find(self, **kwargs):
+        return self._provider.storage._bucket_objects.find(self.bucket,
+                                                           **kwargs)
+
+    def create(self, name):
+        return self._provider.storage._bucket_objects.create(self.bucket, name)
+
+
+class BaseGatewaySubService(GatewaySubService, BasePageableObjectMixin):
+
+    def __init__(self, provider, network):
+        self._network = network
+        self.__provider = provider
+
+    @property
+    def _provider(self):
+        return self.__provider
+
+    def get_or_create(self):
+        return (self._provider.networking
+                              ._gateways
+                              .get_or_create(self._network))
+
+    def delete(self, gateway):
+        return (self._provider.networking
+                              ._gateways
+                              .delete(self._network, gateway))
+
+    def list(self, limit=None, marker=None):
+        return (self._provider.networking
+                              ._gateways
+                              .list(self._network, limit, marker))
+
+
+class BaseVMFirewallRuleSubService(BasePageableObjectMixin,
+                                   VMFirewallRuleSubService):
+
+    def __init__(self, provider, firewall):
+        self.__provider = provider
+        self._firewall = firewall
+
+    @property
+    def _provider(self):
+        return self.__provider
+
+    def get(self, rule_id):
+        return self._provider.security._vm_firewall_rules.get(self._firewall,
+                                                              rule_id)
+
+    def list(self, limit=None, marker=None):
+        return self._provider.security._vm_firewall_rules.list(self._firewall,
+                                                               limit, marker)
+
+    def create(self, direction, protocol=None, from_port=None,
+               to_port=None, cidr=None, src_dest_fw=None):
+        return (self._provider
+                    .security
+                    ._vm_firewall_rules
+                    .create(self._firewall, direction, protocol, from_port,
+                            to_port, cidr, src_dest_fw))
+
+    def find(self, **kwargs):
+        return self._provider.security._vm_firewall_rules.find(self._firewall,
+                                                               **kwargs)
+
+    def delete(self, rule_id):
+        return (self._provider
+                    .security
+                    ._vm_firewall_rules
+                    .delete(self._firewall, rule_id))
+
+
+class BaseFloatingIPSubService(FloatingIPSubService, BasePageableObjectMixin):
+
+    def __init__(self, provider, gateway):
+        self.__provider = provider
+        self.gateway = gateway
+
+    @property
+    def _provider(self):
+        return self.__provider
+
+    def get(self, fip_id):
+        return self._provider.networking._floating_ips.get(self.gateway,
+                                                           fip_id)
+
+    def list(self, limit=None, marker=None):
+        return self._provider.networking._floating_ips.list(self.gateway,
+                                                            limit, marker)
+
+    def find(self, **kwargs):
+        return self._provider.networking._floating_ips.find(self.gateway,
+                                                            **kwargs)
+
+    def create(self):
+        return self._provider.networking._floating_ips.create(self.gateway)
+
+    def delete(self, fip):
+        return self._provider.networking._floating_ips.delete(self.gateway,
+                                                              fip)

+ 7 - 323
cloudbridge/cloud/interfaces/resources.py

@@ -989,8 +989,8 @@ class Network(ObjectLifeCycleMixin, LabeledCloudResource):
         """
         Provides access to the internet gateways attached to this network.
 
-        :rtype: :class:`.GatewayContainer`
-        :return: A GatewayContainer object
+        :rtype: :class:`.GatewaySubService`
+        :return: A GatewaySubService object
         """
         pass
 
@@ -1082,76 +1082,6 @@ class Subnet(ObjectLifeCycleMixin, LabeledCloudResource):
         pass
 
 
-class FloatingIPContainer(PageableObjectMixin):
-    """
-    Base interface for a FloatingIP Service.
-    """
-    __metaclass__ = ABCMeta
-
-    @abstractmethod
-    def get(self, fip_id):
-        """
-        Returns a FloatingIP given its ID or ``None`` if not found.
-
-        :type fip_id: ``str``
-        :param fip_id: The ID of the FloatingIP to retrieve.
-
-        :rtype: ``object`` of :class:`.FloatingIP`
-        :return: a FloatingIP object
-        """
-        pass
-
-    @abstractmethod
-    def list(self, limit=None, marker=None):
-        """
-        List floating (i.e., static) IP addresses.
-
-        :rtype: ``list`` of :class:`.FloatingIP`
-        :return: list of FloatingIP objects
-        """
-        pass
-
-    @abstractmethod
-    def find(self, **kwargs):
-        """
-        Searches for a FloatingIP by a given list of attributes.
-
-        Supported attributes: name, public_ip
-
-        Example:
-
-        .. code-block:: python
-
-            fip = provider.networking.gateways.get('id').floating_ips.find(
-                        public_ip='public_ip')
-
-
-        :rtype: List of ``object`` of :class:`.FloatingIP`
-        :return: A list of FloatingIP objects matching the supplied attributes.
-        """
-        pass
-
-    @abstractmethod
-    def create(self):
-        """
-        Allocate a new floating (i.e., static) IP address.
-
-        :rtype: ``object`` of :class:`.FloatingIP`
-        :return:  A FloatingIP object
-        """
-        pass
-
-    @abstractmethod
-    def delete(self, fip_id):
-        """
-        Delete an existing FloatingIP.
-
-        :type fip_id: ``str``
-        :param fip_id: The ID of the FloatingIP to be deleted.
-        """
-        pass
-
-
 class FloatingIpState(object):
 
     """
@@ -1357,49 +1287,6 @@ class GatewayState(object):
     ERROR = "error"
 
 
-class GatewayContainer(PageableObjectMixin):
-    """
-    Manage internet gateway resources.
-    """
-    __metaclass__ = ABCMeta
-
-    @abstractmethod
-    def get_or_create_inet_gateway(self):
-        """
-        Creates new or returns an existing internet gateway for a network.
-
-        The returned gateway object can subsequently be attached to a router to
-        provide internet routing to a network.
-
-        :type  name: ``str``
-        :param name: The gateway label.
-
-        :rtype: ``object``  of :class:`.InternetGateway` or ``None``
-        :return: an InternetGateway object of ``None`` if not found.
-        """
-        pass
-
-    @abstractmethod
-    def delete(self, gateway):
-        """
-        Delete a gateway.
-
-        :type gateway: :class:`.Gateway` object
-        :param gateway: Gateway object to delete.
-        """
-        pass
-
-    @abstractmethod
-    def list(self, limit=None, marker=None):
-        """
-        List all available internet gateways.
-
-        :rtype: ``list`` of :class:`.InternetGateway` or ``None``
-        :return: Current list of internet gateways.
-        """
-        pass
-
-
 class Gateway(CloudResource):
     """
     Represents a gateway resource.
@@ -1429,8 +1316,8 @@ class Gateway(CloudResource):
         """
         Provides access to floating IPs connected to this internet gateway.
 
-        :rtype: :class:`.FloatingIPContainer`
-        :return: A FloatingIPContainer object
+        :rtype: :class:`.FloatingIPSubService`
+        :return: A FloatingIPSubService object
         """
         pass
 
@@ -2022,148 +1909,13 @@ class VMFirewall(LabeledCloudResource):
     @abstractproperty
     def rules(self):
         """
-        Get a container for the rules belonging to this VM firewall.
+        Get access to the rules belonging to this VM firewall.
 
         This object can be used for further operations on rules, such as get,
         list, create, etc.
 
-        :rtype: An object of :class:`.VMFirewallRuleContainer`
-        :return: A VMFirewallRuleContainer for further operations
-        """
-        pass
-
-
-class VMFirewallRuleContainer(PageableObjectMixin):
-    """
-    Base interface for Firewall rules.
-    """
-    __metaclass__ = ABCMeta
-
-    @abstractmethod
-    def get(self, rule_id):
-        """
-        Return a firewall rule given its ID.
-
-        Returns ``None`` if the rule does not exist.
-
-        Example:
-
-        .. code-block:: python
-
-            fw = provider.security.vm_firewalls.get('my_fw_id')
-            rule = fw.rules.get('rule_id')
-            print(rule.id, rule.label)
-
-        :rtype: :class:`.FirewallRule`
-        :return:  a FirewallRule instance
-        """
-        pass
-
-    @abstractmethod
-    def list(self, limit=None, marker=None):
-        """
-        List all firewall rules associated with this firewall.
-
-        :rtype: ``list`` of :class:`.FirewallRule`
-        :return:  list of Firewall rule objects
-        """
-        pass
-
-    @abstractmethod
-    def create(self,  direction, protocol=None, from_port=None,
-               to_port=None, cidr=None, src_dest_fw=None):
-        """
-        Create a VM firewall rule.
-
-        If a matching rule already exists, return it.
-
-        Example:
-
-        .. code-block:: python
-            from cloudbridge.cloud.interfaces.resources import TrafficDirection
-            from cloudbridge.cloud.interfaces.resources import BaseNetwork
-
-            fw = provider.security.vm_firewalls.get('my_fw_id')
-            fw.rules.create(TrafficDirection.INBOUND, protocol='tcp',
-                            from_port=80, to_port=80,
-                            cidr=BaseNetwork.CB_DEFAULT_IPV4RANGE)
-            fw.rules.create(TrafficDirection.INBOUND, src_dest_fw=fw)
-            fw.rules.create(TrafficDirection.OUTBOUND, src_dest_fw=fw)
-
-        You need to pass in either ``src_dest_fw`` OR ``protocol`` AND
-        ``from_port``, ``to_port``, ``cidr``. In other words, either
-        you are authorizing another group or you are authorizing some
-        IP-based rule.
-
-        :type direction: :class:``.TrafficDirection``
-        :param direction: Either ``TrafficDirection.INBOUND`` |
-                          ``TrafficDirection.OUTBOUND``
-
-        :type protocol: ``str``
-        :param protocol: Either ``tcp`` | ``udp`` | ``icmp``.
-
-        :type from_port: ``int``
-        :param from_port: The beginning port number you are enabling.
-
-        :type to_port: ``int``
-        :param to_port: The ending port number you are enabling.
-
-        :type cidr: ``str`` or list of ``str``
-        :param cidr: The CIDR block you are providing access to.
-
-        :type src_dest_fw: :class:`.VMFirewall`
-        :param src_dest_fw: The VM firewall object which is the
-                            source/destination of the traffic, depending on
-                            whether it's ingress/egress traffic.
-
-        :rtype: :class:`.VMFirewallRule`
-        :return: Rule object if successful or ``None``.
-        """
-        pass
-
-    @abstractmethod
-    def find(self, **kwargs):
-        """
-        Find a firewall rule filtered by the given parameters.
-
-        :type label: str
-        :param label: The label of the VM firewall to retrieve.
-
-        :type protocol: ``str``
-        :param protocol: Either ``tcp`` | ``udp`` | ``icmp``.
-
-        :type from_port: ``int``
-        :param from_port: The beginning port number you are enabling.
-
-        :type to_port: ``int``
-        :param to_port: The ending port number you are enabling.
-
-        :type cidr: ``str`` or list of ``str``
-        :param cidr: The CIDR block you are providing access to.
-
-        :type src_dest_fw: :class:`.VMFirewall`
-        :param src_dest_fw: The VM firewall object which is the
-                            source/destination of the traffic, depending on
-                            whether it's ingress/egress traffic.
-
-        :type src_dest_fw_id: :class:`.str`
-        :param src_dest_fw_id: The VM firewall id which is the
-                               source/destination of the traffic, depending on
-                               whether it's ingress/egress traffic.
-
-        :rtype: list of :class:`VMFirewallRule`
-        :return: A list of VMFirewall objects or an empty list if none
-                 found.
-        """
-        pass
-
-    @abstractmethod
-    def delete(self, rule_id):
-        """
-        Delete an existing VMFirewall rule.
-
-        :type rule_id: str
-        :param rule_id: The VM firewall rule to be deleted.
+        :rtype: An object of :class:`.VMFirewallRuleSubService`
+        :return: A VMFirewallRuleSubService for further operations
         """
         pass
 
@@ -2435,71 +2187,3 @@ class Bucket(CloudResource):
         :return: ``True`` if successful.
         """
         pass
-
-
-class BucketContainer(PageableObjectMixin):
-    """
-    A container service for objects within a bucket.
-    """
-    __metaclass__ = ABCMeta
-
-    @abstractmethod
-    def get(self, name):
-        """
-        Retrieve a given object from this bucket.
-
-        :type name: ``str``
-        :param name: The identifier of the object to retrieve
-
-        :rtype: :class:``.BucketObject``
-        :return: The BucketObject or ``None`` if it cannot be found.
-        """
-        pass
-
-    @abstractmethod
-    # pylint:disable=arguments-differ
-    def list(self, limit=None, marker=None, prefix=None):
-        """
-        List objects in this bucket.
-
-        :type limit: ``int``
-        :param limit: Maximum number of elements to return.
-
-        :type marker: ``int``
-        :param marker: Fetch results after this offset.
-
-        :type prefix: ``str``
-        :param prefix: Prefix criteria by which to filter listed objects.
-
-        :rtype: List of ``objects`` of :class:``.BucketObject``
-        :return: List of all available BucketObjects within this bucket.
-        """
-        pass
-
-    @abstractmethod
-    def find(self, **kwargs):
-        """
-        Search for an object by a given list of attributes.
-
-        Supported attributes: ``name``
-
-        :rtype: List of ``objects`` of :class:`.BucketObject`
-        :return: A list of BucketObjects matching the supplied attributes.
-
-        :type limit: ``int``
-        :param limit: Maximum number of elements to return.
-
-        :type marker: ``int``
-        :param marker: Fetch results after this offset.
-        """
-        pass
-
-    @abstractmethod
-    def create(self, name):
-        """
-        Create a new object within this bucket.
-
-        :rtype: :class:``.BucketObject``
-        :return: The newly created bucket object
-        """
-        pass

+ 331 - 8
cloudbridge/cloud/interfaces/services.py

@@ -604,6 +604,28 @@ class NetworkingService(CloudService):
         """
         pass
 
+    @abstractproperty
+    def _floating_ips(self):
+        """
+        Provides access to floating ips for this provider.
+        This service is not iterable.
+
+        :rtype: :class:`.FloatingIPService`
+        :return: a FloatingIPService object
+        """
+        pass
+
+    @abstractproperty
+    def _gateways(self):
+        """
+        Provides access to internet gateways for this provider.
+        This service is not iterable.
+
+        :rtype: :class:`.GatewayService`
+        :return: a GatewayService object
+        """
+        pass
+
 
 class NetworkService(PageableObjectMixin, CloudService):
 
@@ -965,7 +987,7 @@ class BucketService(PageableObjectMixin, CloudService):
         pass
 
 
-class BucketObjectService(PageableObjectMixin, CloudService):
+class BucketObjectService(CloudService):
 
     """
     The Bucket Object Service interface provides access to the underlying
@@ -988,7 +1010,8 @@ class BucketObjectService(PageableObjectMixin, CloudService):
         .. code-block:: python
 
             bucket = provider.storage.buckets.get('my_bucket_id')
-            buck_obj = provider.storage.bucket_objects.get('my_object_id',
+            # pylint:disable=protected-access
+            buck_obj = provider.storage._bucket_objects.get('my_object_id',
                                                            bucket)
             print(buck_obj.id, buck_obj.name)
 
@@ -1009,7 +1032,8 @@ class BucketObjectService(PageableObjectMixin, CloudService):
         .. code-block:: python
 
             bucket = provider.storage.buckets.get('my_bucket_id')
-            objs = provider.storage.bucket_objects.find(bucket,
+            # pylint:disable=protected-access
+            objs = provider.storage._bucket_objects.find(bucket,
                                                         name='my_obj_name')
             for buck_obj in objs:
                 print(buck_obj.id, buck_obj.name)
@@ -1029,7 +1053,8 @@ class BucketObjectService(PageableObjectMixin, CloudService):
         .. code-block:: python
 
             bucket = provider.storage.buckets.get('my_bucket_id')
-            objs = provider.storage.bucket_objects.list(bucket)
+            # pylint:disable=protected-access
+            objs = provider.storage._bucket_objects.list(bucket)
             for buck_obj in objs:
                 print(buck_obj.id, buck_obj.name)
 
@@ -1048,7 +1073,8 @@ class BucketObjectService(PageableObjectMixin, CloudService):
         .. code-block:: python
 
             bucket = provider.storage.buckets.get('my_bucket_id')
-            buck_obj = provider.storage.bucket_objects.create('my_name',
+            # pylint:disable=protected-access
+            buck_obj = provider.storage._bucket_objects.create('my_name',
                                                               bucket)
             print(buck_obj.name)
 
@@ -1117,6 +1143,17 @@ class SecurityService(CloudService):
         """
         pass
 
+    @abstractproperty
+    def _vm_firewall_rules(self):
+        """
+        Provides access to firewall (security group) rules for this provider.
+        This service is not iterable.
+
+        :rtype: :class:`.VMFirewallRuleService`
+        :return: a VMFirewallRuleService object
+        """
+        pass
+
 
 class KeyPairService(PageableObjectMixin, CloudService):
 
@@ -1274,12 +1311,165 @@ class VMFirewallService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def delete(self, firewall):
+    def delete(self, vm_firewall):
         """
         Delete an existing VMFirewall.
 
-        :type firewall: ``str`` or :class:`.VMFirewall`
-        :param firewall: The object or VM firewall ID to be deleted.
+        :type vm_firewall: ``str`` or :class:`.VMFirewall`
+        :param vm_firewall: The object or VM firewall ID to be deleted.
+        """
+        pass
+
+
+class VMFirewallRuleService(CloudService):
+    """
+    Base interface for Firewall rules.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def get(self, firewall, rule_id):
+        """
+        Return a firewall rule given its ID.
+
+        Returns ``None`` if the rule does not exist.
+
+        Example:
+
+        .. code-block:: python
+
+            fw = provider.security.vm_firewalls.get('my_fw_id')
+            rule = fw.rules.get('rule_id')
+            print(rule.id, rule.label)
+
+        :type firewall: ``VMFirewall``
+        :param firewall: The firewall to which the rule is attached
+
+        :type rule_id: str
+        :param rule_id: The ID of the desired firewall rule
+
+        :rtype: :class:`.FirewallRule`
+        :return:  a FirewallRule instance
+        """
+        pass
+
+    @abstractmethod
+    def list(self, firewall, limit=None, marker=None):
+        """
+        List all firewall rules associated with this firewall.
+
+        :type firewall: ``VMFirewall``
+        :param firewall: The firewall to which the rules are attached
+
+        :rtype: ``list`` of :class:`.FirewallRule`
+        :return:  list of Firewall rule objects
+        """
+        pass
+
+    @abstractmethod
+    def create(self, firewall,  direction, protocol=None, from_port=None,
+               to_port=None, cidr=None, src_dest_fw=None):
+        """
+        Create a VM firewall rule.
+
+        If a matching rule already exists, return it.
+
+        Example:
+
+        .. code-block:: python
+            from cloudbridge.cloud.interfaces.resources import TrafficDirection
+            from cloudbridge.cloud.interfaces.resources import BaseNetwork
+
+            fw = provider.security.vm_firewalls.get('my_fw_id')
+            fw.rules.create(TrafficDirection.INBOUND, protocol='tcp',
+                            from_port=80, to_port=80,
+                            cidr=BaseNetwork.CB_DEFAULT_IPV4RANGE)
+            fw.rules.create(TrafficDirection.INBOUND, src_dest_fw=fw)
+            fw.rules.create(TrafficDirection.OUTBOUND, src_dest_fw=fw)
+
+        You need to pass in either ``src_dest_fw`` OR ``protocol`` AND
+        ``from_port``, ``to_port``, ``cidr``. In other words, either
+        you are authorizing another group or you are authorizing some
+        IP-based rule.
+
+        :type firewall: ``VMFirewall``
+        :param firewall: The firewall to which the rule should be attached
+
+        :type direction: :class:``.TrafficDirection``
+        :param direction: Either ``TrafficDirection.INBOUND`` |
+                          ``TrafficDirection.OUTBOUND``
+
+        :type protocol: ``str``
+        :param protocol: Either ``tcp`` | ``udp`` | ``icmp``.
+
+        :type from_port: ``int``
+        :param from_port: The beginning port number you are enabling.
+
+        :type to_port: ``int``
+        :param to_port: The ending port number you are enabling.
+
+        :type cidr: ``str`` or list of ``str``
+        :param cidr: The CIDR block you are providing access to.
+
+        :type src_dest_fw: :class:`.VMFirewall`
+        :param src_dest_fw: The VM firewall object which is the
+                            source/destination of the traffic, depending on
+                            whether it's ingress/egress traffic.
+
+        :rtype: :class:`.VMFirewallRule`
+        :return: Rule object if successful or ``None``.
+        """
+        pass
+
+    @abstractmethod
+    def find(self, firewall, **kwargs):
+        """
+        Find a firewall rule filtered by the given parameters.
+
+        :type firewall: ``VMFirewall``
+        :param firewall: The firewall in which to look for rules
+
+        :type label: str
+        :param label: The label of the VM firewall to retrieve.
+
+        :type protocol: ``str``
+        :param protocol: Either ``tcp`` | ``udp`` | ``icmp``.
+
+        :type from_port: ``int``
+        :param from_port: The beginning port number you are enabling.
+
+        :type to_port: ``int``
+        :param to_port: The ending port number you are enabling.
+
+        :type cidr: ``str`` or list of ``str``
+        :param cidr: The CIDR block you are providing access to.
+
+        :type src_dest_fw: :class:`.VMFirewall`
+        :param src_dest_fw: The VM firewall object which is the
+                            source/destination of the traffic, depending on
+                            whether it's ingress/egress traffic.
+
+        :type src_dest_fw_id: :class:`.str`
+        :param src_dest_fw_id: The VM firewall id which is the
+                               source/destination of the traffic, depending on
+                               whether it's ingress/egress traffic.
+
+        :rtype: list of :class:`VMFirewallRule`
+        :return: A list of VMFirewall objects or an empty list if none
+                 found.
+        """
+        pass
+
+    @abstractmethod
+    def delete(self, firewall, rule_id):
+        """
+        Delete an existing VMFirewall rule.
+
+        :type firewall: ``VMFirewall``
+        :param firewall: The firewall to which the rule is attached
+
+        :type rule_id: str
+        :param rule_id: The VM firewall rule to be deleted.
         """
         pass
 
@@ -1379,3 +1569,136 @@ class RegionService(PageableObjectMixin, CloudService):
         :return: a Region object
         """
         pass
+
+
+class GatewayService(CloudService):
+    """
+    Manage internet gateway resources.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def get_or_create(self, network):
+        """
+        Creates new or returns an existing internet gateway for a network.
+
+        The returned gateway object can subsequently be attached to a router to
+        provide internet routing to a network.
+
+        :type  network: ``Network``
+        :param network: The network to which the gateway should be attached.
+
+        :rtype: ``object``  of :class:`.InternetGateway` or ``None``
+        :return: an InternetGateway object of ``None`` if not found.
+        """
+        pass
+
+    @abstractmethod
+    def delete(self, network, gateway):
+        """
+        Delete a gateway.
+
+        :type  network: ``Network``
+        :param network: The network to which the gateway is attached.
+
+        :type gateway: :class:`.Gateway` object
+        :param gateway: Gateway object to delete.
+        """
+        pass
+
+    @abstractmethod
+    def list(self, network, limit=None, marker=None):
+        """
+        List all available internet gateways.
+
+        :type  network: ``Network``
+        :param network: The network to which the gateway is attached.
+
+        :rtype: ``list`` of :class:`.InternetGateway` or ``None``
+        :return: Current list of internet gateways.
+        """
+        pass
+
+
+class FloatingIPService(CloudService):
+    """
+    Base interface for a FloatingIP Service.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def get(self, gateway, fip_id):
+        """
+        Returns a FloatingIP given its ID or ``None`` if not found.
+
+        :type gateway: ``Gateway``
+        :param gateway: The gateway to which the Floating IP is attached
+
+        :type fip_id: ``str``
+        :param fip_id: The ID of the FloatingIP to retrieve.
+
+        :rtype: ``object`` of :class:`.FloatingIP`
+        :return: a FloatingIP object
+        """
+        pass
+
+    @abstractmethod
+    def list(self, gateway, limit=None, marker=None):
+        """
+        List floating (i.e., static) IP addresses.
+
+        :type gateway: ``Gateway``
+        :param gateway: The gateway to which the Floating IPs are attached
+
+        :rtype: ``list`` of :class:`.FloatingIP`
+        :return: list of FloatingIP objects
+        """
+        pass
+
+    @abstractmethod
+    def find(self, gateway, **kwargs):
+        """
+        Searches for a FloatingIP by a given list of attributes.
+
+        Supported attributes: name, public_ip
+
+        Example:
+
+        .. code-block:: python
+
+            fip = provider.networking.gateways.get('id').floating_ips.find(
+                        public_ip='public_ip')
+
+        :type gateway: ``Gateway``
+        :param gateway: The gateway to which the Floating IPs are attached
+
+        :rtype: List of ``object`` of :class:`.FloatingIP`
+        :return: A list of FloatingIP objects matching the supplied attributes.
+        """
+        pass
+
+    @abstractmethod
+    def create(self, gateway):
+        """
+        Allocate a new floating (i.e., static) IP address.
+
+        :type gateway: ``Gateway``
+        :param gateway: The gateway to which the Floating IP should be attached
+
+        :rtype: ``object`` of :class:`.FloatingIP`
+        :return:  A FloatingIP object
+        """
+        pass
+
+    @abstractmethod
+    def delete(self, gateway, fip):
+        """
+        Delete an existing FloatingIP.
+
+        :type gateway: ``Gateway``
+        :param gateway: The gateway to which the Floating IP is attached
+
+        :type fip: ``str``
+        :param fip: The FloatingIP to be deleted.
+        """
+        pass

+ 320 - 0
cloudbridge/cloud/interfaces/subservices.py

@@ -0,0 +1,320 @@
+from abc import ABCMeta
+from abc import abstractmethod
+
+from cloudbridge.cloud.interfaces.resources import PageableObjectMixin
+
+
+class BucketObjectSubService(PageableObjectMixin):
+    """
+    A container service for objects within a bucket.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def get(self, name):
+        """
+        Retrieve a given object from this bucket.
+
+        :type name: ``str``
+        :param name: The identifier of the object to retrieve
+
+        :rtype: :class:``.BucketObject``
+        :return: The BucketObject or ``None`` if it cannot be found.
+        """
+        pass
+
+    @abstractmethod
+    # pylint:disable=arguments-differ
+    def list(self, limit=None, marker=None, prefix=None):
+        """
+        List objects in this bucket.
+
+        :type limit: ``int``
+        :param limit: Maximum number of elements to return.
+
+        :type marker: ``int``
+        :param marker: Fetch results after this offset.
+
+        :type prefix: ``str``
+        :param prefix: Prefix criteria by which to filter listed objects.
+
+        :rtype: List of ``objects`` of :class:``.BucketObject``
+        :return: List of all available BucketObjects within this bucket.
+        """
+        pass
+
+    @abstractmethod
+    def find(self, **kwargs):
+        """
+        Search for an object by a given list of attributes.
+
+        Supported attributes: ``name``
+
+        :rtype: List of ``objects`` of :class:`.BucketObject`
+        :return: A list of BucketObjects matching the supplied attributes.
+
+        :type limit: ``int``
+        :param limit: Maximum number of elements to return.
+
+        :type marker: ``int``
+        :param marker: Fetch results after this offset.
+        """
+        pass
+
+    @abstractmethod
+    def create(self, name):
+        """
+        Create a new object within this bucket.
+
+        :rtype: :class:``.BucketObject``
+        :return: The newly created bucket object
+        """
+        pass
+
+
+class GatewaySubService(PageableObjectMixin):
+    """
+    Manage internet gateway resources.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def get_or_create(self):
+        """
+        Creates new or returns an existing internet gateway for a network.
+
+        The returned gateway object can subsequently be attached to a router to
+        provide internet routing to a network.
+
+        :rtype: ``object``  of :class:`.InternetGateway` or ``None``
+        :return: an InternetGateway object of ``None`` if not found.
+        """
+        pass
+
+    @abstractmethod
+    def delete(self, gateway):
+        """
+        Delete a gateway.
+
+        :type gateway: :class:`.Gateway` object
+        :param gateway: Gateway object to delete.
+        """
+        pass
+
+    @abstractmethod
+    def list(self, limit=None, marker=None):
+        """
+        List all available internet gateways.
+
+        :rtype: ``list`` of :class:`.InternetGateway` or ``None``
+        :return: Current list of internet gateways.
+        """
+        pass
+
+
+class FloatingIPSubService(PageableObjectMixin):
+    """
+    Base interface for a FloatingIP Service.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def get(self, fip_id):
+        """
+        Returns a FloatingIP given its ID or ``None`` if not found.
+
+        :type fip_id: ``str``
+        :param fip_id: The ID of the FloatingIP to retrieve.
+
+        :rtype: ``object`` of :class:`.FloatingIP`
+        :return: a FloatingIP object
+        """
+        pass
+
+    @abstractmethod
+    def list(self, limit=None, marker=None):
+        """
+        List floating (i.e., static) IP addresses.
+
+        :rtype: ``list`` of :class:`.FloatingIP`
+        :return: list of FloatingIP objects
+        """
+        pass
+
+    @abstractmethod
+    def find(self, **kwargs):
+        """
+        Searches for a FloatingIP by a given list of attributes.
+
+        Supported attributes: name, public_ip
+
+        Example:
+
+        .. code-block:: python
+
+            fip = provider.networking.gateways.get('id').floating_ips.find(
+                        public_ip='public_ip')
+
+
+        :rtype: List of ``object`` of :class:`.FloatingIP`
+        :return: A list of FloatingIP objects matching the supplied attributes.
+        """
+        pass
+
+    @abstractmethod
+    def create(self):
+        """
+        Allocate a new floating (i.e., static) IP address.
+
+        :rtype: ``object`` of :class:`.FloatingIP`
+        :return:  A FloatingIP object
+        """
+        pass
+
+    @abstractmethod
+    def delete(self, fip_id):
+        """
+        Delete an existing FloatingIP.
+
+        :type fip_id: ``str``
+        :param fip_id: The ID of the FloatingIP to be deleted.
+        """
+        pass
+
+
+class VMFirewallRuleSubService(PageableObjectMixin):
+    """
+    Base interface for Firewall rules.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def get(self, rule_id):
+        """
+        Return a firewall rule given its ID.
+
+        Returns ``None`` if the rule does not exist.
+
+        Example:
+
+        .. code-block:: python
+
+            fw = provider.security.vm_firewalls.get('my_fw_id')
+            rule = fw.rules.get('rule_id')
+            print(rule.id, rule.label)
+
+        :type rule_id: str
+        :param rule_id: The ID of the desired firewall rule
+
+        :rtype: :class:`.FirewallRule`
+        :return:  a FirewallRule instance
+        """
+        pass
+
+    @abstractmethod
+    def list(self, limit=None, marker=None):
+        """
+        List all firewall rules associated with this firewall.
+
+        :rtype: ``list`` of :class:`.FirewallRule`
+        :return:  list of Firewall rule objects
+        """
+        pass
+
+    @abstractmethod
+    def create(self,  direction, protocol=None, from_port=None,
+               to_port=None, cidr=None, src_dest_fw=None):
+        """
+        Create a VM firewall rule.
+
+        If a matching rule already exists, return it.
+
+        Example:
+
+        .. code-block:: python
+            from cloudbridge.cloud.interfaces.resources import TrafficDirection
+            from cloudbridge.cloud.interfaces.resources import BaseNetwork
+
+            fw = provider.security.vm_firewalls.get('my_fw_id')
+            fw.rules.create(TrafficDirection.INBOUND, protocol='tcp',
+                            from_port=80, to_port=80,
+                            cidr=BaseNetwork.CB_DEFAULT_IPV4RANGE)
+            fw.rules.create(TrafficDirection.INBOUND, src_dest_fw=fw)
+            fw.rules.create(TrafficDirection.OUTBOUND, src_dest_fw=fw)
+
+        You need to pass in either ``src_dest_fw`` OR ``protocol`` AND
+        ``from_port``, ``to_port``, ``cidr``. In other words, either
+        you are authorizing another group or you are authorizing some
+        IP-based rule.
+
+        :type direction: :class:``.TrafficDirection``
+        :param direction: Either ``TrafficDirection.INBOUND`` |
+                          ``TrafficDirection.OUTBOUND``
+
+        :type protocol: ``str``
+        :param protocol: Either ``tcp`` | ``udp`` | ``icmp``.
+
+        :type from_port: ``int``
+        :param from_port: The beginning port number you are enabling.
+
+        :type to_port: ``int``
+        :param to_port: The ending port number you are enabling.
+
+        :type cidr: ``str`` or list of ``str``
+        :param cidr: The CIDR block you are providing access to.
+
+        :type src_dest_fw: :class:`.VMFirewall`
+        :param src_dest_fw: The VM firewall object which is the
+                            source/destination of the traffic, depending on
+                            whether it's ingress/egress traffic.
+
+        :rtype: :class:`.VMFirewallRule`
+        :return: Rule object if successful or ``None``.
+        """
+        pass
+
+    @abstractmethod
+    def find(self, **kwargs):
+        """
+        Find a firewall rule filtered by the given parameters.
+
+        :type label: str
+        :param label: The label of the VM firewall to retrieve.
+
+        :type protocol: ``str``
+        :param protocol: Either ``tcp`` | ``udp`` | ``icmp``.
+
+        :type from_port: ``int``
+        :param from_port: The beginning port number you are enabling.
+
+        :type to_port: ``int``
+        :param to_port: The ending port number you are enabling.
+
+        :type cidr: ``str`` or list of ``str``
+        :param cidr: The CIDR block you are providing access to.
+
+        :type src_dest_fw: :class:`.VMFirewall`
+        :param src_dest_fw: The VM firewall object which is the
+                            source/destination of the traffic, depending on
+                            whether it's ingress/egress traffic.
+
+        :type src_dest_fw_id: :class:`.str`
+        :param src_dest_fw_id: The VM firewall id which is the
+                               source/destination of the traffic, depending on
+                               whether it's ingress/egress traffic.
+
+        :rtype: list of :class:`VMFirewallRule`
+        :return: A list of VMFirewall objects or an empty list if none
+                 found.
+        """
+        pass
+
+    @abstractmethod
+    def delete(self, rule_id):
+        """
+        Delete an existing VMFirewall rule.
+
+        :type rule_id: str
+        :param rule_id: The VM firewall rule to be deleted.
+        """
+        pass

+ 5 - 15
cloudbridge/cloud/providers/aws/helpers.py

@@ -130,21 +130,11 @@ class BotoGenericService(object):
 
         :returns A CloudBridge wrapped 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 self.cb_resource(self.provider, 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
+        aws_res = self.get_raw(resource_id)
+        if aws_res:
+            return self.cb_resource(self.provider, aws_res)
+        else:
+            return None
 
     def _get_list_operation(self):
         """

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

@@ -9,11 +9,8 @@ from botocore.exceptions import ClientError
 
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
-from cloudbridge.cloud.base.resources import BaseBucketContainer
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
-from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
-from cloudbridge.cloud.base.resources import BaseGatewayContainer
 from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInternetGateway
 from cloudbridge.cloud.base.resources import BaseKeyPair
@@ -27,11 +24,8 @@ from cloudbridge.cloud.base.resources import BaseSnapshot
 from cloudbridge.cloud.base.resources import BaseSubnet
 from cloudbridge.cloud.base.resources import BaseVMFirewall
 from cloudbridge.cloud.base.resources import BaseVMFirewallRule
-from cloudbridge.cloud.base.resources import BaseVMFirewallRuleContainer
 from cloudbridge.cloud.base.resources import BaseVMType
 from cloudbridge.cloud.base.resources import BaseVolume
-from cloudbridge.cloud.base.resources import ClientPagedResultList
-from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.resources import GatewayState
 from cloudbridge.cloud.interfaces.resources import InstanceState
 from cloudbridge.cloud.interfaces.resources import MachineImageState
@@ -39,12 +33,14 @@ from cloudbridge.cloud.interfaces.resources import NetworkState
 from cloudbridge.cloud.interfaces.resources import RouterState
 from cloudbridge.cloud.interfaces.resources import SnapshotState
 from cloudbridge.cloud.interfaces.resources import SubnetState
-from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VolumeState
 
-from .helpers import BotoEC2Service
 from .helpers import find_tag_value
 from .helpers import trim_empty_params
+from .subservices import AWSBucketObjectSubService
+from .subservices import AWSFloatingIPSubService
+from .subservices import AWSGatewaySubService
+from .subservices import AWSVMFirewallRuleSubService
 
 log = logging.getLogger(__name__)
 
@@ -144,6 +140,7 @@ class AWSPlacementZone(BasePlacementZone):
         if isinstance(zone, AWSPlacementZone):
             # pylint:disable=protected-access
             self._aws_zone = zone._aws_zone
+            # pylint:disable=protected-access
             self._aws_region = zone._aws_region
         else:
             self._aws_zone = zone
@@ -327,18 +324,16 @@ class AWSInstance(BaseInstance):
 
     def _get_fip(self, floating_ip):
         """Get a floating IP object based on the supplied allocation ID."""
-        return AWSFloatingIP(
-            self._provider, list(self._provider.ec2_conn.vpc_addresses.filter(
-                AllocationIds=[floating_ip]))[0])
+        return self._provider.networking._floating_ips.get(None, floating_ip)
 
     def add_floating_ip(self, floating_ip):
         fip = (floating_ip if isinstance(floating_ip, AWSFloatingIP)
                else self._get_fip(floating_ip))
+        # pylint:disable=protected-access
         params = trim_empty_params({
             'InstanceId': self.id,
             'PublicIp': None if self._ec2_instance.vpc_id else
             fip.public_ip,
-            # pylint:disable=protected-access
             'AllocationId': fip._ip.allocation_id})
         self._provider.ec2_conn.meta.client.associate_address(**params)
         self.refresh()
@@ -346,10 +341,10 @@ class AWSInstance(BaseInstance):
     def remove_floating_ip(self, floating_ip):
         fip = (floating_ip if isinstance(floating_ip, AWSFloatingIP)
                else self._get_fip(floating_ip))
+        # pylint:disable=protected-access
         params = trim_empty_params({
             'PublicIp': None if self._ec2_instance.vpc_id else
             fip.public_ip,
-            # pylint:disable=protected-access
             'AssociationId': fip._ip.association_id})
         self._provider.ec2_conn.meta.client.disassociate_address(**params)
         self.refresh()
@@ -615,7 +610,7 @@ class AWSVMFirewall(BaseVMFirewall):
 
     def __init__(self, provider, _vm_firewall):
         super(AWSVMFirewall, self).__init__(provider, _vm_firewall)
-        self._rule_container = AWSVMFirewallRuleContainer(provider, self)
+        self._rule_container = AWSVMFirewallRuleSubService(provider, self)
 
     @property
     def name(self):
@@ -659,56 +654,6 @@ class AWSVMFirewall(BaseVMFirewall):
         return js
 
 
-class AWSVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
-
-    def __init__(self, provider, firewall):
-        super(AWSVMFirewallRuleContainer, self).__init__(provider, firewall)
-
-    def list(self, limit=None, marker=None):
-        # pylint:disable=protected-access
-        rules = [AWSVMFirewallRule(self.firewall,
-                                   TrafficDirection.INBOUND, r)
-                 for r in self.firewall._vm_firewall.ip_permissions]
-        rules = rules + [
-            AWSVMFirewallRule(
-                self.firewall, TrafficDirection.OUTBOUND, r)
-            for r in self.firewall._vm_firewall.ip_permissions_egress]
-        return ClientPagedResultList(self._provider, rules,
-                                     limit=limit, marker=marker)
-
-    def create(self,  direction, protocol=None, from_port=None,
-               to_port=None, cidr=None, src_dest_fw=None):
-        src_dest_fw_id = (
-            src_dest_fw.id if isinstance(src_dest_fw, AWSVMFirewall)
-            else src_dest_fw)
-
-        # pylint:disable=protected-access
-        ip_perm_entry = AWSVMFirewallRule._construct_ip_perms(
-            protocol, from_port, to_port, cidr, src_dest_fw_id)
-        # Filter out empty values to please Boto
-        ip_perms = [trim_empty_params(ip_perm_entry)]
-
-        try:
-            if direction == TrafficDirection.INBOUND:
-                # pylint:disable=protected-access
-                self.firewall._vm_firewall.authorize_ingress(
-                    IpPermissions=ip_perms)
-            elif direction == TrafficDirection.OUTBOUND:
-                # pylint:disable=protected-access
-                self.firewall._vm_firewall.authorize_egress(
-                    IpPermissions=ip_perms)
-            else:
-                raise InvalidValueException("direction", direction)
-            self.firewall.refresh()
-            return AWSVMFirewallRule(self.firewall, direction, ip_perm_entry)
-        except ClientError as ec2e:
-            if ec2e.response['Error']['Code'] == "InvalidPermission.Duplicate":
-                return AWSVMFirewallRule(
-                    self.firewall, direction, ip_perm_entry)
-            else:
-                raise ec2e
-
-
 class AWSVMFirewallRule(BaseVMFirewallRule):
 
     def __init__(self, parent_fw, direction, rule):
@@ -775,23 +720,6 @@ class AWSVMFirewallRule(BaseVMFirewallRule):
             ] if src_dest_fw_id else None
         }
 
-    def delete(self):
-        ip_perm_entry = self._construct_ip_perms(
-            self.protocol, self.from_port, self.to_port,
-            self.cidr, self.src_dest_fw_id)
-
-        # Filter out empty values to please Boto
-        ip_perms = [trim_empty_params(ip_perm_entry)]
-
-        # pylint:disable=protected-access
-        if self.direction == TrafficDirection.INBOUND:
-            self.firewall._vm_firewall.revoke_ingress(
-                IpPermissions=ip_perms)
-        else:
-            self.firewall._vm_firewall.revoke_egress(
-                IpPermissions=ip_perms)
-        self.firewall.refresh()
-
 
 class AWSBucketObject(BaseBucketObject):
 
@@ -865,7 +793,7 @@ class AWSBucket(BaseBucket):
     def __init__(self, provider, bucket):
         super(AWSBucket, self).__init__(provider)
         self._bucket = bucket
-        self._object_container = AWSBucketContainer(provider, self)
+        self._object_container = AWSBucketObjectSubService(provider, self)
 
     @property
     def id(self):
@@ -880,12 +808,6 @@ class AWSBucket(BaseBucket):
         return self._object_container
 
 
-class AWSBucketContainer(BaseBucketContainer):
-
-    def __init__(self, provider, bucket):
-        super(AWSBucketContainer, self).__init__(provider, bucket)
-
-
 class AWSRegion(BaseRegion):
 
     def __init__(self, provider, aws_region):
@@ -927,7 +849,7 @@ class AWSNetwork(BaseNetwork):
     def __init__(self, provider, network):
         super(AWSNetwork, self).__init__(provider)
         self._vpc = network
-        self._gtw_container = AWSGatewayContainer(provider, self)
+        self._gtw_container = AWSGatewaySubService(provider, self)
         self._unknown_state = False
 
     @property
@@ -1058,31 +980,6 @@ class AWSSubnet(BaseSubnet):
             self._unknown_state = True
 
 
-class AWSFloatingIPContainer(BaseFloatingIPContainer):
-
-    def __init__(self, provider, gateway):
-        super(AWSFloatingIPContainer, self).__init__(provider, gateway)
-        self.svc = BotoEC2Service(provider=self._provider,
-                                  cb_resource=AWSFloatingIP,
-                                  boto_collection_name='vpc_addresses')
-
-    def get(self, fip_id):
-        log.debug("Getting AWS Floating IP Service with the id: %s", fip_id)
-        return self.svc.get(fip_id)
-
-    def list(self, limit=None, marker=None):
-        log.debug("Listing all floating IPs under gateway %s", self.gateway)
-        return self.svc.list(limit=limit, marker=marker)
-
-    def create(self):
-        log.debug("Creating a floating IP under gateway %s", self.gateway)
-        ip = self._provider.ec2_conn.meta.client.allocate_address(
-            Domain='vpc')
-        return AWSFloatingIP(
-            self._provider,
-            self._provider.ec2_conn.VpcAddress(ip.get('AllocationId')))
-
-
 class AWSFloatingIP(BaseFloatingIP):
 
     def __init__(self, provider, floating_ip):
@@ -1105,9 +1002,6 @@ class AWSFloatingIP(BaseFloatingIP):
     def in_use(self):
         return True if self._ip.association_id else False
 
-    def delete(self):
-        self._ip.release()
-
     def refresh(self):
         self._ip.reload()
 
@@ -1186,57 +1080,13 @@ class AWSRouter(BaseRouter):
             InternetGatewayId=gw_id, VpcId=self._route_table.vpc_id)
 
 
-class AWSGatewayContainer(BaseGatewayContainer):
-
-    def __init__(self, provider, network):
-        super(AWSGatewayContainer, self).__init__(provider, network)
-        self.svc = BotoEC2Service(provider=provider,
-                                  cb_resource=AWSInternetGateway,
-                                  boto_collection_name='internet_gateways')
-
-    def get_or_create_inet_gateway(self):
-        log.debug("Get or create inet gateway on net %s",
-                  self._network)
-        network_id = self._network.id if isinstance(
-            self._network, AWSNetwork) else self._network
-        # Don't filter by label because it may conflict with at least the
-        # default VPC that most accounts have but that network is typically
-        # without a name.
-        gtw = self.svc.find(filter_name='attachment.vpc-id',
-                            filter_value=network_id)
-        if gtw:
-            return gtw[0]  # There can be only one gtw attached to a VPC
-        # Gateway does not exist so create one and attach to the supplied net
-        cb_gateway = self.svc.create('create_internet_gateway')
-        cb_gateway._gateway.create_tags(
-            Tags=[{'Key': 'Name',
-                   'Value': AWSInternetGateway.CB_DEFAULT_INET_GATEWAY_NAME
-                   }])
-        cb_gateway._gateway.attach_to_vpc(VpcId=network_id)
-        return cb_gateway
-
-    def delete(self, gateway):
-        log.debug("Service deleting AWS Gateway %s", gateway)
-        gateway_id = gateway.id if isinstance(
-            gateway, AWSInternetGateway) else gateway
-        gateway = self.svc.get(gateway_id)
-        if gateway:
-            gateway.delete()
-
-    def list(self, limit=None, marker=None):
-        log.debug("Listing current AWS internet gateways for net %s.",
-                  self._network.id)
-        fltr = [{'Name': 'attachment.vpc-id', 'Values': [self._network.id]}]
-        return self.svc.list(limit=None, marker=None, Filters=fltr)
-
-
 class AWSInternetGateway(BaseInternetGateway):
 
     def __init__(self, provider, gateway):
         super(AWSInternetGateway, self).__init__(provider)
         self._gateway = gateway
         self._gateway.state = ''
-        self._fips_container = AWSFloatingIPContainer(provider, self)
+        self._fips_container = AWSFloatingIPSubService(provider, self)
 
     @property
     def id(self):
@@ -1265,14 +1115,6 @@ class AWSInternetGateway(BaseInternetGateway):
             return self._gateway.attachments[0].get('VpcId')
         return None
 
-    def delete(self):
-        try:
-            if self.network_id:
-                self._gateway.detach_from_vpc(VpcId=self.network_id)
-            self._gateway.delete()
-        except ClientError as e:
-            log.warn("Error deleting gateway {0}: {1}".format(self.id, e))
-
     @property
     def floating_ips(self):
         return self._fips_container

+ 212 - 12
cloudbridge/cloud/providers/aws/services.py

@@ -15,6 +15,8 @@ from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketObjectService
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseComputeService
+from cloudbridge.cloud.base.services import BaseFloatingIPService
+from cloudbridge.cloud.base.services import BaseGatewayService
 from cloudbridge.cloud.base.services import BaseImageService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseKeyPairService
@@ -26,6 +28,7 @@ from cloudbridge.cloud.base.services import BaseSecurityService
 from cloudbridge.cloud.base.services import BaseSnapshotService
 from cloudbridge.cloud.base.services import BaseStorageService
 from cloudbridge.cloud.base.services import BaseSubnetService
+from cloudbridge.cloud.base.services import BaseVMFirewallRuleService
 from cloudbridge.cloud.base.services import BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
@@ -33,20 +36,25 @@ from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.cloud.interfaces.exceptions import \
     InvalidConfigurationException
 from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
+from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 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 TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import Volume
 
 from .helpers import BotoEC2Service
 from .helpers import BotoS3Service
+from .helpers import trim_empty_params
 from .resources import AWSBucket
 from .resources import AWSBucketObject
+from .resources import AWSFloatingIP
 from .resources import AWSInstance
+from .resources import AWSInternetGateway
 from .resources import AWSKeyPair
 from .resources import AWSLaunchConfig
 from .resources import AWSMachineImage
@@ -57,6 +65,7 @@ from .resources import AWSRouter
 from .resources import AWSSnapshot
 from .resources import AWSSubnet
 from .resources import AWSVMFirewall
+from .resources import AWSVMFirewallRule
 from .resources import AWSVMType
 from .resources import AWSVolume
 
@@ -71,6 +80,7 @@ class AWSSecurityService(BaseSecurityService):
         # Initialize provider services
         self._key_pairs = AWSKeyPairService(provider)
         self._vm_firewalls = AWSVMFirewallService(provider)
+        self._vm_firewall_rule_svc = AWSVMFirewallRuleService(provider)
 
     @property
     def key_pairs(self):
@@ -80,6 +90,10 @@ class AWSSecurityService(BaseSecurityService):
     def vm_firewalls(self):
         return self._vm_firewalls
 
+    @property
+    def _vm_firewall_rules(self):
+        return self._vm_firewall_rule_svc
+
 
 class AWSKeyPairService(BaseKeyPairService):
 
@@ -135,8 +149,9 @@ class AWSKeyPairService(BaseKeyPairService):
 
     @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)
+    def delete(self, key_pair):
+        key_pair = (key_pair if isinstance(key_pair, AWSKeyPair) else
+                    self.get(key_pair))
         if key_pair:
             # pylint:disable=protected-access
             key_pair._key_pair.delete()
@@ -190,13 +205,90 @@ class AWSVMFirewallService(BaseVMFirewallService):
 
     @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)
+    def delete(self, vm_firewall):
+        firewall = (vm_firewall if isinstance(vm_firewall, AWSVMFirewall)
+                    else self.get(vm_firewall))
         if firewall:
             # pylint:disable=protected-access
             firewall._vm_firewall.delete()
 
 
+class AWSVMFirewallRuleService(BaseVMFirewallRuleService):
+
+    def __init__(self, provider):
+        super(AWSVMFirewallRuleService, self).__init__(provider)
+
+    @dispatch(event="provider.security.vm_firewall_rules.list",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def list(self, firewall, limit=None, marker=None):
+        # pylint:disable=protected-access
+        rules = [AWSVMFirewallRule(firewall,
+                                   TrafficDirection.INBOUND, r)
+                 for r in firewall._vm_firewall.ip_permissions]
+        # pylint:disable=protected-access
+        rules = rules + [
+            AWSVMFirewallRule(
+                firewall, TrafficDirection.OUTBOUND, r)
+            for r in firewall._vm_firewall.ip_permissions_egress]
+        return ClientPagedResultList(self.provider, rules,
+                                     limit=limit, marker=marker)
+
+    @dispatch(event="provider.security.vm_firewall_rules.create",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def create(self, firewall,  direction, protocol=None, from_port=None,
+               to_port=None, cidr=None, src_dest_fw=None):
+        src_dest_fw_id = (
+            src_dest_fw.id if isinstance(src_dest_fw, AWSVMFirewall)
+            else src_dest_fw)
+
+        # pylint:disable=protected-access
+        ip_perm_entry = AWSVMFirewallRule._construct_ip_perms(
+            protocol, from_port, to_port, cidr, src_dest_fw_id)
+        # Filter out empty values to please Boto
+        ip_perms = [trim_empty_params(ip_perm_entry)]
+
+        try:
+            if direction == TrafficDirection.INBOUND:
+                # pylint:disable=protected-access
+                firewall._vm_firewall.authorize_ingress(
+                    IpPermissions=ip_perms)
+            elif direction == TrafficDirection.OUTBOUND:
+                # pylint:disable=protected-access
+                firewall._vm_firewall.authorize_egress(
+                    IpPermissions=ip_perms)
+            else:
+                raise InvalidValueException("direction", direction)
+            firewall.refresh()
+            return AWSVMFirewallRule(firewall, direction, ip_perm_entry)
+        except ClientError as ec2e:
+            if ec2e.response['Error']['Code'] == "InvalidPermission.Duplicate":
+                return AWSVMFirewallRule(
+                    firewall, direction, ip_perm_entry)
+            else:
+                raise ec2e
+
+    @dispatch(event="provider.security.vm_firewall_rules.delete",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def delete(self, firewall, rule):
+        # pylint:disable=protected-access
+        ip_perm_entry = rule._construct_ip_perms(
+            rule.protocol, rule.from_port, rule.to_port,
+            rule.cidr, rule.src_dest_fw_id)
+
+        # Filter out empty values to please Boto
+        ip_perms = [trim_empty_params(ip_perm_entry)]
+
+        # pylint:disable=protected-access
+        if rule.direction == TrafficDirection.INBOUND:
+            firewall._vm_firewall.revoke_ingress(
+                IpPermissions=ip_perms)
+        else:
+            # pylint:disable=protected-access
+            firewall._vm_firewall.revoke_egress(
+                IpPermissions=ip_perms)
+        firewall.refresh()
+
+
 class AWSStorageService(BaseStorageService):
 
     def __init__(self, provider):
@@ -221,7 +313,7 @@ class AWSStorageService(BaseStorageService):
         return self._bucket_svc
 
     @property
-    def bucket_objects(self):
+    def _bucket_objects(self):
         return self._bucket_obj_svc
 
 
@@ -336,8 +428,9 @@ class AWSSnapshotService(BaseSnapshotService):
 
     @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)
+    def delete(self, snapshot):
+        snapshot = (snapshot if isinstance(snapshot, AWSSnapshot) else
+                    self.get(snapshot))
         if snapshot:
             # pylint:disable=protected-access
             snapshot._snapshot.delete()
@@ -457,6 +550,7 @@ class AWSBucketObjectService(BaseBucketObjectService):
                                      limit=limit, marker=marker)
 
     def find(self, bucket, **kwargs):
+        # pylint:disable=protected-access
         obj_list = [AWSBucketObject(self.provider, o)
                     for o in bucket._bucket.objects.all()]
         filters = ['name']
@@ -707,8 +801,9 @@ class AWSInstanceService(BaseInstanceService):
 
     @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)
+    def delete(self, instance):
+        aws_inst = (instance if isinstance(instance, AWSInstance) else
+                    self.get(instance))
         if aws_inst:
             # pylint:disable=protected-access
             aws_inst._ec2_instance.terminate()
@@ -789,6 +884,8 @@ class AWSNetworkingService(BaseNetworkingService):
         self._network_service = AWSNetworkService(self.provider)
         self._subnet_service = AWSSubnetService(self.provider)
         self._router_service = AWSRouterService(self.provider)
+        self._gateway_service = AWSGatewayService(self.provider)
+        self._floating_ip_service = AWSFloatingIPService(self.provider)
 
     @property
     def networks(self):
@@ -802,6 +899,14 @@ class AWSNetworkingService(BaseNetworkingService):
     def routers(self):
         return self._router_service
 
+    @property
+    def _gateways(self):
+        return self._gateway_service
+
+    @property
+    def _floating_ips(self):
+        return self._floating_ip_service
+
 
 class AWSNetworkService(BaseNetworkService):
 
@@ -849,8 +954,9 @@ class AWSNetworkService(BaseNetworkService):
 
     @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)
+    def delete(self, network):
+        network = (network if isinstance(network, AWSNetwork)
+                   else self.get(network))
         if network:
             # pylint:disable=protected-access
             network._vpc.delete()
@@ -858,6 +964,7 @@ class AWSNetworkService(BaseNetworkService):
     def get_or_create_default(self):
         # # Look for provided default network
         # for net in self.provider.networking.networks:
+        # pylint:disable=protected-access
         #     if net._vpc.is_default:
         #         return net
 
@@ -967,6 +1074,7 @@ class AWSSubnetService(BaseSubnetService):
         snl = self.find(label=AWSSubnet.CB_DEFAULT_SUBNET_LABEL + "*")
 
         if snl:
+            # pylint:disable=protected-access
             snl.sort(key=lambda sn: sn._subnet.availability_zone)
             if not zone_name:
                 return snl[0]
@@ -989,7 +1097,7 @@ class AWSSubnetService(BaseSubnetService):
         # though because the provider-default network will have Internet
         # connectivity (unlike the CloudBridge-default network with this
         # being commented) and is hence left in the codebase.
-        # default_gtw = default_net.gateways.get_or_create_inet_gateway()
+        # default_gtw = default_net.gateways.get_or_create()
         # router_label = "{0}-router".format(
         #   AWSNetwork.CB_DEFAULT_NETWORK_LABEL)
         # default_routers = self.provider.networking.routers.find(
@@ -1091,3 +1199,95 @@ class AWSRouterService(BaseRouterService):
         if r:
             # pylint:disable=protected-access
             r._route_table.delete()
+
+
+class AWSGatewayService(BaseGatewayService):
+
+    def __init__(self, provider):
+        super(AWSGatewayService, self).__init__(provider)
+        self.svc = BotoEC2Service(provider=provider,
+                                  cb_resource=AWSInternetGateway,
+                                  boto_collection_name='internet_gateways')
+
+    @dispatch(event="provider.networking.gateways.get_or_create",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def get_or_create(self, network):
+        network_id = network.id if isinstance(
+            network, AWSNetwork) else network
+        # Don't filter by label because it may conflict with at least the
+        # default VPC that most accounts have but that network is typically
+        # without a name.
+        gtw = self.svc.find(filter_name='attachment.vpc-id',
+                            filter_value=network_id)
+        if gtw:
+            return gtw[0]  # There can be only one gtw attached to a VPC
+        # Gateway does not exist so create one and attach to the supplied net
+        cb_gateway = self.svc.create('create_internet_gateway')
+        cb_gateway._gateway.create_tags(
+            Tags=[{'Key': 'Name',
+                   'Value': AWSInternetGateway.CB_DEFAULT_INET_GATEWAY_NAME
+                   }])
+        cb_gateway._gateway.attach_to_vpc(VpcId=network_id)
+        return cb_gateway
+
+    @dispatch(event="provider.networking.gateways.delete",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def delete(self, network, gateway):
+        gw = (gateway if isinstance(gateway, AWSInternetGateway)
+              else self.svc.get(gateway))
+        try:
+            if gw.network_id:
+                # pylint:disable=protected-access
+                gw._gateway.detach_from_vpc(VpcId=gw.network_id)
+        except ClientError as e:
+            log.warn("Error deleting gateway {0}: {1}".format(self.id, e))
+        # pylint:disable=protected-access
+        gw._gateway.delete()
+
+    @dispatch(event="provider.networking.gateways.list",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def list(self, network, limit=None, marker=None):
+        log.debug("Listing current AWS internet gateways for net %s.",
+                  network.id)
+        fltr = [{'Name': 'attachment.vpc-id', 'Values': [network.id]}]
+        return self.svc.list(limit=None, marker=None, Filters=fltr)
+
+
+class AWSFloatingIPService(BaseFloatingIPService):
+
+    def __init__(self, provider):
+        super(AWSFloatingIPService, self).__init__(provider)
+        self.svc = BotoEC2Service(provider=self.provider,
+                                  cb_resource=AWSFloatingIP,
+                                  boto_collection_name='vpc_addresses')
+
+    @dispatch(event="provider.networking.floating_ips.get",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def get(self, gateway, fip_id):
+        log.debug("Getting AWS Floating IP Service with the id: %s", fip_id)
+        return self.svc.get(fip_id)
+
+    @dispatch(event="provider.networking.floating_ips.list",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def list(self, gateway, limit=None, marker=None):
+        return self.svc.list(limit, marker)
+
+    @dispatch(event="provider.networking.floating_ips.create",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def create(self, gateway):
+        log.debug("Creating a floating IP under gateway %s", gateway)
+        ip = self.provider.ec2_conn.meta.client.allocate_address(
+            Domain='vpc')
+        return AWSFloatingIP(
+            self.provider,
+            self.provider.ec2_conn.VpcAddress(ip.get('AllocationId')))
+
+    @dispatch(event="provider.networking.floating_ips.delete",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def delete(self, gateway, fip):
+        if isinstance(fip, AWSFloatingIP):
+            # pylint:disable=protected-access
+            aws_fip = fip._ip
+        else:
+            aws_fip = self.svc.get_raw(fip)
+        aws_fip.release()

+ 33 - 0
cloudbridge/cloud/providers/aws/subservices.py

@@ -0,0 +1,33 @@
+import logging
+
+from cloudbridge.cloud.base.subservices import BaseBucketObjectSubService
+from cloudbridge.cloud.base.subservices import BaseFloatingIPSubService
+from cloudbridge.cloud.base.subservices import \
+    BaseGatewaySubService
+from cloudbridge.cloud.base.subservices import BaseVMFirewallRuleSubService
+
+log = logging.getLogger(__name__)
+
+
+class AWSBucketObjectSubService(BaseBucketObjectSubService):
+
+    def __init__(self, provider, bucket):
+        super(AWSBucketObjectSubService, self).__init__(provider, bucket)
+
+
+class AWSGatewaySubService(BaseGatewaySubService):
+
+    def __init__(self, provider, network):
+        super(AWSGatewaySubService, self).__init__(provider, network)
+
+
+class AWSVMFirewallRuleSubService(BaseVMFirewallRuleSubService):
+
+    def __init__(self, provider, firewall):
+        super(AWSVMFirewallRuleSubService, self).__init__(provider, firewall)
+
+
+class AWSFloatingIPSubService(BaseFloatingIPSubService):
+
+    def __init__(self, provider, gateway):
+        super(AWSFloatingIPSubService, self).__init__(provider, gateway)

+ 16 - 156
cloudbridge/cloud/providers/azure/resources.py

@@ -3,7 +3,6 @@ DataTypes used by this provider
 """
 import collections
 import logging
-from uuid import uuid4
 
 from azure.common import AzureException
 from azure.mgmt.devtestlabs.models import GalleryImageReference
@@ -15,11 +14,8 @@ import pysftp
 
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
-from cloudbridge.cloud.base.resources import BaseBucketContainer
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
-from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
-from cloudbridge.cloud.base.resources import BaseGatewayContainer
 from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInternetGateway
 from cloudbridge.cloud.base.resources import BaseKeyPair
@@ -33,10 +29,8 @@ from cloudbridge.cloud.base.resources import BaseSnapshot
 from cloudbridge.cloud.base.resources import BaseSubnet
 from cloudbridge.cloud.base.resources import BaseVMFirewall
 from cloudbridge.cloud.base.resources import BaseVMFirewallRule
-from cloudbridge.cloud.base.resources import BaseVMFirewallRuleContainer
 from cloudbridge.cloud.base.resources import BaseVMType
 from cloudbridge.cloud.base.resources import BaseVolume
-from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.interfaces import InstanceState
 from cloudbridge.cloud.interfaces import VolumeState
 from cloudbridge.cloud.interfaces.resources import Instance
@@ -48,6 +42,10 @@ from cloudbridge.cloud.interfaces.resources import SubnetState
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 
 from . import helpers as azure_helpers
+from .subservices import AzureBucketObjectSubService
+from .subservices import AzureFloatingIPSubService
+from .subservices import AzureGatewaySubService
+from .subservices import AzureVMFirewallRuleSubService
 
 log = logging.getLogger(__name__)
 
@@ -57,7 +55,7 @@ class AzureVMFirewall(BaseVMFirewall):
         super(AzureVMFirewall, self).__init__(provider, vm_firewall)
         self._vm_firewall = vm_firewall
         self._vm_firewall.tags = self._vm_firewall.tags or {}
-        self._rule_container = AzureVMFirewallRuleContainer(provider, self)
+        self._rule_container = AzureVMFirewallRuleSubService(provider, self)
 
     @property
     def network_id(self):
@@ -123,70 +121,6 @@ class AzureVMFirewall(BaseVMFirewall):
         return js
 
 
-class AzureVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
-
-    def __init__(self, provider, firewall):
-        super(AzureVMFirewallRuleContainer, self).__init__(provider, firewall)
-
-    def list(self, limit=None, marker=None):
-        # Filter out firewall rules with priority < 3500 because values
-        # between 3500 and 4096 are assumed to be owned by cloudbridge
-        # default rules.
-        # pylint:disable=protected-access
-        rules = [AzureVMFirewallRule(self.firewall, rule) for rule
-                 in self.firewall._vm_firewall.security_rules
-                 if rule.priority < 3500]
-        return ClientPagedResultList(self._provider, rules,
-                                     limit=limit, marker=marker)
-
-    def create(self, direction, protocol=None, from_port=None, to_port=None,
-               cidr=None, src_dest_fw=None):
-        if protocol and from_port and to_port:
-            return self._create_rule(direction, protocol, from_port,
-                                     to_port, cidr)
-        elif src_dest_fw:
-            result = None
-            fw = (self._provider.security.vm_firewalls.get(src_dest_fw)
-                  if isinstance(src_dest_fw, str) else src_dest_fw)
-            for rule in fw.rules:
-                result = self._create_rule(
-                    rule.direction, rule.protocol, rule.from_port,
-                    rule.to_port, rule.cidr)
-            return result
-        else:
-            return None
-
-    def _create_rule(self, direction, protocol, from_port, to_port, cidr):
-
-        # If cidr is None, default values is set as 0.0.0.0/0
-        if not cidr:
-            cidr = '0.0.0.0/0'
-
-        count = len(self.firewall._vm_firewall.security_rules) + 1
-        rule_name = "cb-rule-" + str(count)
-        priority = 1000 + count
-        destination_port_range = str(from_port) + "-" + str(to_port)
-        source_port_range = '*'
-        destination_address_prefix = "*"
-        access = "Allow"
-        direction = ("Inbound" if direction == TrafficDirection.INBOUND
-                     else "Outbound")
-        parameters = {"priority": priority,
-                      "protocol": protocol,
-                      "source_port_range": source_port_range,
-                      "source_address_prefix": cidr,
-                      "destination_port_range": destination_port_range,
-                      "destination_address_prefix": destination_address_prefix,
-                      "access": access,
-                      "direction": direction}
-        result = self._provider.azure_client. \
-            create_vm_firewall_rule(self.firewall.id,
-                                    rule_name, parameters)
-        # pylint:disable=protected-access
-        self.firewall._vm_firewall.security_rules.append(result)
-        return AzureVMFirewallRule(self.firewall, result)
-
-
 # Tuple for port range
 PortRange = collections.namedtuple('PortRange', ['from_port', 'to_port'])
 
@@ -240,15 +174,6 @@ class AzureVMFirewallRule(BaseVMFirewallRule):
     def src_dest_fw(self):
         return self.firewall
 
-    def delete(self):
-        vm_firewall = self.firewall.name
-        self._provider.azure_client. \
-            delete_vm_firewall_rule(self.id, vm_firewall)
-        for i, o in enumerate(self.firewall._vm_firewall.security_rules):
-            if o.id == self.id:
-                del self.firewall._vm_firewall.security_rules[i]
-                break
-
 
 class AzureBucketObject(BaseBucketObject):
     def __init__(self, provider, container, key):
@@ -342,7 +267,7 @@ class AzureBucket(BaseBucket):
     def __init__(self, provider, bucket):
         super(AzureBucket, self).__init__(provider)
         self._bucket = bucket
-        self._object_container = AzureBucketContainer(provider, self)
+        self._object_container = AzureBucketObjectSubService(provider, self)
 
     @property
     def id(self):
@@ -366,12 +291,6 @@ class AzureBucket(BaseBucket):
         return self._object_container
 
 
-class AzureBucketContainer(BaseBucketContainer):
-
-    def __init__(self, provider, bucket):
-        super(AzureBucketContainer, self).__init__(provider, bucket)
-
-
 class AzureVolume(BaseVolume):
     VOLUME_STATE_MAP = {
         'InProgress': VolumeState.CREATING,
@@ -785,28 +704,6 @@ class AzureMachineImage(BaseMachineImage):
                 self._state = "unknown"
 
 
-class AzureGatewayContainer(BaseGatewayContainer):
-    def __init__(self, provider, network):
-        super(AzureGatewayContainer, self).__init__(provider, network)
-        # Azure doesn't have a notion of a route table or an internet
-        # gateway as OS and AWS so create placeholder objects of the
-        # AzureInternetGateway here.
-        # http://bit.ly/2BqGdVh
-        # Singleton returned by the list method
-        self.gateway_singleton = AzureInternetGateway(self._provider, None,
-                                                      network)
-
-    def get_or_create_inet_gateway(self):
-        gateway = AzureInternetGateway(self._provider, None, self._network)
-        return gateway
-
-    def list(self, limit=None, marker=None):
-        return [self.gateway_singleton]
-
-    def delete(self, gateway):
-        pass
-
-
 class AzureNetwork(BaseNetwork):
     NETWORK_STATE_MAP = {
         'InProgress': NetworkState.PENDING,
@@ -819,7 +716,7 @@ class AzureNetwork(BaseNetwork):
         self._state = self._network.provisioning_state
         if not self._network.tags:
             self._network.tags = {}
-        self._gateway_service = AzureGatewayContainer(provider, self)
+        self._gateway_service = AzureGatewaySubService(provider, self)
 
     @property
     def id(self):
@@ -920,45 +817,11 @@ class AzureNetwork(BaseNetwork):
         return self._gateway_service
 
 
-class AzureFloatingIPContainer(BaseFloatingIPContainer):
-
-    def __init__(self, provider, gateway, network_id):
-        super(AzureFloatingIPContainer, self).__init__(provider, gateway)
-        self._network_id = network_id
-
-    def get(self, fip_id):
-        log.debug("Getting Azure Floating IP container with the id: %s",
-                  fip_id)
-        fip = [fip for fip in self if fip.id == fip_id]
-        return fip[0] if fip else None
-
-    def list(self, limit=None, marker=None):
-        floating_ips = [AzureFloatingIP(self._provider, floating_ip,
-                                        self._network_id)
-                        for floating_ip in self._provider.azure_client.
-                        list_floating_ips()]
-        return ClientPagedResultList(self._provider, floating_ips,
-                                     limit=limit, marker=marker)
-
-    def create(self):
-        public_ip_parameters = {
-            'location': self._provider.azure_client.region_name,
-            'public_ip_allocation_method': 'Static'
-        }
-
-        public_ip_name = 'cb-fip-' + uuid4().hex[:6]
-
-        floating_ip = self._provider.azure_client.\
-            create_floating_ip(public_ip_name, public_ip_parameters)
-        return AzureFloatingIP(self._provider, floating_ip, self._network_id)
-
-
 class AzureFloatingIP(BaseFloatingIP):
 
-    def __init__(self, provider, floating_ip, network_id):
+    def __init__(self, provider, floating_ip):
         super(AzureFloatingIP, self).__init__(provider)
         self._ip = floating_ip
-        self._network_id = network_id
 
     @property
     def id(self):
@@ -985,16 +848,12 @@ class AzureFloatingIP(BaseFloatingIP):
     def in_use(self):
         return True if self._ip.ip_configuration else False
 
-    def delete(self):
-        """
-        Delete an existing floating ip.
-        """
-        self._provider.azure_client.delete_floating_ip(self.id)
-
     def refresh(self):
-        net = self._provider.networking.networks.get(self._network_id)
-        gw = net.gateways.get_or_create_inet_gateway()
-        fip = gw.floating_ips.get(self.id)
+        # Gateway is not needed as it doesn't exist in Azure, so just
+        # getting the Floating IP again from the client
+        # pylint:disable=protected-access
+        fip = self._provider.networking._floating_ips.get(None, self.id)
+        # pylint:disable=protected-access
         self._ip = fip._ip
 
 
@@ -1089,6 +948,7 @@ class AzureSubnet(BaseSubnet):
         # Although Subnet doesn't support labels, we use the parent Network's
         # tags to track the subnet's labels
         network = self.network
+        # pylint:disable=protected-access
         az_network = network._network
         return az_network.tags.get(self.tag_name, None)
 
@@ -1097,6 +957,7 @@ class AzureSubnet(BaseSubnet):
     def label(self, value):
         self.assert_valid_resource_label(value)
         network = self.network
+        # pylint:disable=protected-access
         az_network = network._network
         kwargs = {self.tag_name: value or ""}
         az_network.tags.update(**kwargs)
@@ -1651,8 +1512,7 @@ class AzureInternetGateway(BaseInternetGateway):
         self._network_id = gateway_net.id if isinstance(
             gateway_net, AzureNetwork) else gateway_net
         self._state = ''
-        self._fips_container = AzureFloatingIPContainer(
-            provider, self, self._network_id)
+        self._fips_container = AzureFloatingIPSubService(provider, self)
 
     @property
     def id(self):

+ 202 - 11
cloudbridge/cloud/providers/azure/services.py

@@ -14,6 +14,8 @@ from cloudbridge.cloud.base.resources import ServerPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketObjectService
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseComputeService
+from cloudbridge.cloud.base.services import BaseFloatingIPService
+from cloudbridge.cloud.base.services import BaseGatewayService
 from cloudbridge.cloud.base.services import BaseImageService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseKeyPairService
@@ -25,6 +27,7 @@ from cloudbridge.cloud.base.services import BaseSecurityService
 from cloudbridge.cloud.base.services import BaseSnapshotService
 from cloudbridge.cloud.base.services import BaseStorageService
 from cloudbridge.cloud.base.services import BaseSubnetService
+from cloudbridge.cloud.base.services import BaseVMFirewallRuleService
 from cloudbridge.cloud.base.services import BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
@@ -35,13 +38,16 @@ 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 TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import Volume
 
 from .resources import AzureBucket
 from .resources import AzureBucketObject
+from .resources import AzureFloatingIP
 from .resources import AzureInstance
+from .resources import AzureInternetGateway
 from .resources import AzureKeyPair
 from .resources import AzureLaunchConfig
 from .resources import AzureMachineImage
@@ -51,6 +57,7 @@ from .resources import AzureRouter
 from .resources import AzureSnapshot
 from .resources import AzureSubnet
 from .resources import AzureVMFirewall
+from .resources import AzureVMFirewallRule
 from .resources import AzureVMType
 from .resources import AzureVolume
 
@@ -64,6 +71,7 @@ class AzureSecurityService(BaseSecurityService):
         # Initialize provider services
         self._key_pairs = AzureKeyPairService(provider)
         self._vm_firewalls = AzureVMFirewallService(provider)
+        self._vm_firewall_rule_svc = AzureVMFirewallRuleService(provider)
 
     @property
     def key_pairs(self):
@@ -73,6 +81,10 @@ class AzureSecurityService(BaseSecurityService):
     def vm_firewalls(self):
         return self._vm_firewalls
 
+    @property
+    def _vm_firewall_rules(self):
+        return self._vm_firewall_rule_svc
+
 
 class AzureVMFirewallService(BaseVMFirewallService):
     def __init__(self, provider):
@@ -143,11 +155,94 @@ class AzureVMFirewallService(BaseVMFirewallService):
 
     @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
+    def delete(self, vm_firewall):
+        fw_id = (vm_firewall.id if isinstance(vm_firewall, AzureVMFirewall)
+                 else vm_firewall)
         self.provider.azure_client.delete_vm_firewall(fw_id)
 
 
+class AzureVMFirewallRuleService(BaseVMFirewallRuleService):
+
+    def __init__(self, provider):
+        super(AzureVMFirewallRuleService, self).__init__(provider)
+
+    @dispatch(event="provider.security.vm_firewall_rules.list",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def list(self, firewall, limit=None, marker=None):
+        # Filter out firewall rules with priority < 3500 because values
+        # between 3500 and 4096 are assumed to be owned by cloudbridge
+        # default rules.
+        # pylint:disable=protected-access
+        rules = [AzureVMFirewallRule(firewall, rule) for rule
+                 in firewall._vm_firewall.security_rules
+                 if rule.priority < 3500]
+        return ClientPagedResultList(self.provider, rules,
+                                     limit=limit, marker=marker)
+
+    @dispatch(event="provider.security.vm_firewall_rules.create",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def create(self, firewall, direction, protocol=None, from_port=None,
+               to_port=None, cidr=None, src_dest_fw=None):
+        if protocol and from_port and to_port:
+            return self._create_rule(firewall, direction, protocol, from_port,
+                                     to_port, cidr)
+        elif src_dest_fw:
+            result = None
+            fw = (self.provider.security.vm_firewalls.get(src_dest_fw)
+                  if isinstance(src_dest_fw, str) else src_dest_fw)
+            for rule in fw.rules:
+                result = self._create_rule(
+                    rule.direction, rule.protocol, rule.from_port,
+                    rule.to_port, rule.cidr)
+            return result
+        else:
+            return None
+
+    def _create_rule(self, firewall, direction, protocol,
+                     from_port, to_port, cidr):
+
+        # If cidr is None, default values is set as 0.0.0.0/0
+        if not cidr:
+            cidr = '0.0.0.0/0'
+
+        count = len(firewall._vm_firewall.security_rules) + 1
+        rule_name = "cb-rule-" + str(count)
+        priority = 1000 + count
+        destination_port_range = str(from_port) + "-" + str(to_port)
+        source_port_range = '*'
+        destination_address_prefix = "*"
+        access = "Allow"
+        direction = ("Inbound" if direction == TrafficDirection.INBOUND
+                     else "Outbound")
+        parameters = {"priority": priority,
+                      "protocol": protocol,
+                      "source_port_range": source_port_range,
+                      "source_address_prefix": cidr,
+                      "destination_port_range": destination_port_range,
+                      "destination_address_prefix": destination_address_prefix,
+                      "access": access,
+                      "direction": direction}
+        result = self.provider.azure_client. \
+            create_vm_firewall_rule(firewall.id,
+                                    rule_name, parameters)
+        # pylint:disable=protected-access
+        firewall._vm_firewall.security_rules.append(result)
+        return AzureVMFirewallRule(firewall, result)
+
+    @dispatch(event="provider.security.vm_firewall_rules.delete",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def delete(self, firewall, rule):
+        rule_id = rule.id if isinstance(rule, AzureVMFirewallRule) else rule
+        fw_name = firewall.name
+        self.provider.azure_client. \
+            delete_vm_firewall_rule(rule_id, fw_name)
+        for i, o in enumerate(firewall._vm_firewall.security_rules):
+            if o.id == rule_id:
+                # pylint:disable=protected-access
+                del firewall._vm_firewall.security_rules[i]
+                break
+
+
 class AzureKeyPairService(BaseKeyPairService):
     PARTITION_KEY = '00000000-0000-0000-0000-000000000000'
 
@@ -226,8 +321,9 @@ class AzureKeyPairService(BaseKeyPairService):
 
     @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)
+    def delete(self, key_pair):
+        key_pair = (key_pair if isinstance(key_pair, AzureKeyPair) else
+                    self.get(key_pair))
         if key_pair:
             # pylint:disable=protected-access
             self.provider.azure_client.delete_public_key(key_pair._key_pair)
@@ -256,7 +352,7 @@ class AzureStorageService(BaseStorageService):
         return self._bucket_svc
 
     @property
-    def bucket_objects(self):
+    def _bucket_objects(self):
         return self._bucket_obj_svc
 
 
@@ -626,6 +722,7 @@ class AzureInstanceService(BaseInstanceService):
                                 zone_id):
 
         if image.is_gallery_image:
+            # pylint:disable=protected-access
             reference = image._image.as_dict()
             image_ref = {
                 'publisher': reference['publisher'],
@@ -908,30 +1005,36 @@ class AzureInstanceService(BaseInstanceService):
 
     @dispatch(event="provider.compute.instances.delete",
               priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
-    def delete(self, inst):
+    def delete(self, instance):
         """
         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:
+        ins = (instance if isinstance(instance, AzureInstance) else
+               self.get(instance))
+        if not instance:
             return
 
         # Remove IPs first to avoid a network interface conflict
+        # pylint:disable=protected-access
         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)
+        # pylint:disable=protected-access
         for nic_id in ins._nic_ids:
             self.provider.azure_client.delete_nic(nic_id)
+        # pylint:disable=protected-access
         for data_disk in ins._vm.storage_profile.data_disks:
             if data_disk.managed_disk:
+                # pylint:disable=protected-access
                 if ins._vm.tags.get('delete_on_terminate',
                                     'False') == 'True':
                     self.provider.azure_client. \
                         delete_disk(data_disk.managed_disk.id)
+        # pylint:disable=protected-access
         if ins._vm.storage_profile.os_disk.managed_disk:
             self.provider.azure_client. \
                 delete_disk(ins._vm.storage_profile.os_disk.managed_disk.id)
@@ -992,6 +1095,8 @@ class AzureNetworkingService(BaseNetworkingService):
         self._network_service = AzureNetworkService(self.provider)
         self._subnet_service = AzureSubnetService(self.provider)
         self._router_service = AzureRouterService(self.provider)
+        self._gateway_service = AzureGatewayService(self.provider)
+        self._floating_ip_service = AzureFloatingIPService(self.provider)
 
     @property
     def networks(self):
@@ -1005,6 +1110,14 @@ class AzureNetworkingService(BaseNetworkingService):
     def routers(self):
         return self._router_service
 
+    @property
+    def _gateways(self):
+        return self._gateway_service
+
+    @property
+    def _floating_ips(self):
+        return self._floating_ip_service
+
 
 class AzureNetworkService(BaseNetworkService):
     def __init__(self, provider):
@@ -1050,8 +1163,8 @@ class AzureNetworkService(BaseNetworkService):
 
     @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
+    def delete(self, network):
+        net_id = network.id if isinstance(network, AzureNetwork) else network
         if net_id:
             self.provider.azure_client.delete_network(net_id)
 
@@ -1154,7 +1267,7 @@ class AzureSubnetService(BaseSubnetService):
             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(
+            self.provider.azure_client.update_network_tags(
                 az_network.id, az_network)
 
 
@@ -1217,3 +1330,81 @@ class AzureRouterService(BaseRouterService):
         r = router if isinstance(router, AzureRouter) else self.get(router)
         if r:
             self.provider.azure_client.delete_route_table(r.name)
+
+
+class AzureGatewayService(BaseGatewayService):
+    def __init__(self, provider):
+        super(AzureGatewayService, self).__init__(provider)
+
+    # Azure doesn't have a notion of a route table or an internet
+    # gateway as OS and AWS so create placeholder objects of the
+    # AzureInternetGateway here.
+    # http://bit.ly/2BqGdVh
+    # Singleton returned by the list and get methods
+    def _gateway_singleton(self, network):
+        return AzureInternetGateway(self.provider, None, network)
+
+    @dispatch(event="provider.networking.gateways.get_or_create",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def get_or_create(self, network):
+        return self._gateway_singleton(network)
+
+    @dispatch(event="provider.networking.gateways.list",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def list(self, network, limit=None, marker=None):
+        gws = [self._gateway_singleton(network)]
+        return ClientPagedResultList(self.provider,
+                                     gws,
+                                     limit=limit, marker=marker)
+
+    @dispatch(event="provider.networking.gateways.delete",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def delete(self, network, gateway):
+        pass
+
+
+class AzureFloatingIPService(BaseFloatingIPService):
+
+    def __init__(self, provider):
+        super(AzureFloatingIPService, self).__init__(provider)
+
+    @dispatch(event="provider.networking.floating_ips.get",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def get(self, gateway, fip_id):
+        try:
+            az_ip = self.provider.azure_client.get_floating_ip(fip_id)
+        except (CloudError, InvalidValueException) as cloud_error:
+            # Azure raises the cloud error if the resource not available
+            log.exception(cloud_error)
+            return None
+        return AzureFloatingIP(self.provider, az_ip)
+
+    @dispatch(event="provider.networking.floating_ips.list",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def list(self, gateway, limit=None, marker=None):
+        floating_ips = [AzureFloatingIP(self.provider, floating_ip)
+                        for floating_ip in self.provider.azure_client.
+                        list_floating_ips()]
+        return ClientPagedResultList(self.provider, floating_ips,
+                                     limit=limit, marker=marker)
+
+    @dispatch(event="provider.networking.floating_ips.create",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def create(self, gateway):
+        public_ip_parameters = {
+            'location': self.provider.azure_client.region_name,
+            'public_ip_allocation_method': 'Static'
+        }
+
+        public_ip_name = AzureFloatingIP._generate_name_from_label(
+            None, 'cb-fip-')
+
+        floating_ip = self.provider.azure_client.\
+            create_floating_ip(public_ip_name, public_ip_parameters)
+        return AzureFloatingIP(self.provider, floating_ip)
+
+    @dispatch(event="provider.networking.floating_ips.delete",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def delete(self, gateway, fip):
+        fip_id = fip.id if isinstance(fip, AzureFloatingIP) else fip
+        self.provider.azure_client.delete_floating_ip(fip_id)

+ 32 - 0
cloudbridge/cloud/providers/azure/subservices.py

@@ -0,0 +1,32 @@
+import logging
+
+from cloudbridge.cloud.base.subservices import BaseBucketObjectSubService
+from cloudbridge.cloud.base.subservices import \
+    BaseFloatingIPSubService
+from cloudbridge.cloud.base.subservices import BaseGatewaySubService
+from cloudbridge.cloud.base.subservices import BaseVMFirewallRuleSubService
+
+log = logging.getLogger(__name__)
+
+
+class AzureBucketObjectSubService(BaseBucketObjectSubService):
+
+    def __init__(self, provider, bucket):
+        super(AzureBucketObjectSubService, self).__init__(provider, bucket)
+
+
+class AzureGatewaySubService(BaseGatewaySubService):
+    def __init__(self, provider, network):
+        super(AzureGatewaySubService, self).__init__(provider, network)
+
+
+class AzureVMFirewallRuleSubService(BaseVMFirewallRuleSubService):
+
+    def __init__(self, provider, firewall):
+        super(AzureVMFirewallRuleSubService, self).__init__(provider, firewall)
+
+
+class AzureFloatingIPSubService(BaseFloatingIPSubService):
+
+    def __init__(self, provider, gateway):
+        super(AzureFloatingIPSubService, self).__init__(provider, gateway)

+ 1 - 0
cloudbridge/cloud/providers/gce/helpers.py

@@ -181,6 +181,7 @@ def change_label(resource, key, value, res_att, request):
         request.body = str(request_body)
         request.body_size = len(str(request_body))
         response = request.execute()
+        # pylint:disable=protected-access
         resource._provider.wait_for_operation(
             response, zone=getattr(resource, 'zone_name', None))
     finally:

+ 4 - 2
cloudbridge/cloud/providers/gce/provider.py

@@ -15,7 +15,6 @@ from googleapiclient import discovery
 from oauth2client.client import GoogleCredentials
 from oauth2client.service_account import ServiceAccountCredentials
 
-import cloudbridge as cb
 from cloudbridge.cloud.base import BaseCloudProvider
 from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
 
@@ -24,6 +23,8 @@ from .services import GCENetworkingService
 from .services import GCESecurityService
 from .services import GCPStorageService
 
+log = logging.getLogger(__name__)
+
 
 class GCPResourceUrl(object):
 
@@ -101,6 +102,7 @@ class GCPResources(object):
         #   }
         #   ...
         # }
+        # pylint:disable=protected-access
         desc = connection._resourceDesc
         self._root_url = desc['rootUrl']
         self._service_path = desc['servicePath']
@@ -177,7 +179,7 @@ class GCPResources(object):
             return self.parse_url(url_or_name)
         # Otherwise, construct resource URL with default values.
         if resource not in self._resources:
-            cb.log.warning('Unknown resource: %s', resource)
+            log.warning('Unknown resource: %s', resource)
             return None
 
         parameter_defaults = self._parameter_defaults.copy()

+ 58 - 220
cloudbridge/cloud/providers/gce/resources.py

@@ -15,14 +15,10 @@ from collections import namedtuple
 
 import googleapiclient
 
-import cloudbridge as cb
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
-from cloudbridge.cloud.base.resources import BaseBucketContainer
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
-from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
-from cloudbridge.cloud.base.resources import BaseGatewayContainer
 from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInternetGateway
 from cloudbridge.cloud.base.resources import BaseKeyPair
@@ -36,11 +32,8 @@ from cloudbridge.cloud.base.resources import BaseSnapshot
 from cloudbridge.cloud.base.resources import BaseSubnet
 from cloudbridge.cloud.base.resources import BaseVMFirewall
 from cloudbridge.cloud.base.resources import BaseVMFirewallRule
-from cloudbridge.cloud.base.resources import BaseVMFirewallRuleContainer
 from cloudbridge.cloud.base.resources import BaseVMType
 from cloudbridge.cloud.base.resources import BaseVolume
-from cloudbridge.cloud.base.resources import ClientPagedResultList
-from cloudbridge.cloud.base.resources import ServerPagedResultList
 from cloudbridge.cloud.interfaces.resources import GatewayState
 from cloudbridge.cloud.interfaces.resources import InstanceState
 from cloudbridge.cloud.interfaces.resources import MachineImageState
@@ -50,9 +43,15 @@ from cloudbridge.cloud.interfaces.resources import SnapshotState
 from cloudbridge.cloud.interfaces.resources import SubnetState
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VolumeState
-from cloudbridge.cloud.providers.gce import helpers
+
+from . import helpers
+from .subservices import GCEFloatingIPSubService
+from .subservices import GCEGatewaySubService
+from .subservices import GCEVMFirewallRuleSubService
+from .subservices import GCSBucketObjectSubService
 
 # Older versions of Python do not have a built-in set data-structure.
+
 try:
     set
 except NameError:
@@ -295,8 +294,8 @@ class GCEFirewallsDelegate(object):
             firewall[src_dest_str + 'Ranges'] = [src_dest_range]
         if src_dest_tag is not None:
             if direction == TrafficDirection.OUTBOUND:
-                cb.log.warning('GCP does not support egress rules to network '
-                               'tags. Only IP ranges are acceptable.')
+                log.warning('GCP does not support egress rules to network '
+                            'tags. Only IP ranges are acceptable.')
             else:
                 firewall['sourceTags'] = [src_dest_tag]
         if priority is not None:
@@ -454,7 +453,8 @@ class GCEVMFirewall(BaseVMFirewall):
                              .get_or_create_default())
         else:
             self._network = network
-        self._rule_container = GCEVMFirewallRuleContainer(self)
+        self._rule_container = GCEVMFirewallRuleSubService(self._provider,
+                                                           self)
 
     @property
     def id(self):
@@ -524,8 +524,11 @@ class GCEVMFirewall(BaseVMFirewall):
         if fw:
             # pylint:disable=protected-access
             self._delegate = fw._delegate
+            # pylint:disable=protected-access
             self._description = fw._description
+            # pylint:disable=protected-access
             self._network = fw._network
+            # pylint:disable=protected-access
             self._rule_container = fw._rule_container
 
     @property
@@ -537,67 +540,6 @@ class GCEVMFirewall(BaseVMFirewall):
         return self._delegate
 
 
-class GCEVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
-
-    def __init__(self, firewall):
-        super(GCEVMFirewallRuleContainer, self).__init__(
-                firewall.delegate.provider, firewall)
-        self._dummy_rule = None
-
-    def list(self, limit=None, marker=None):
-        rules = []
-        for firewall in self.firewall.delegate.iter_firewalls(
-                self.firewall.name, self.firewall.network.name):
-            rule = GCEVMFirewallRule(self.firewall, firewall['id'])
-            if rule.is_dummy_rule():
-                self._dummy_rule = rule
-            else:
-                rules.append(rule)
-        return ClientPagedResultList(self._provider, rules,
-                                     limit=limit, marker=marker)
-
-    @property
-    def dummy_rule(self):
-        if not self._dummy_rule:
-            self.list()
-        return self._dummy_rule
-
-    @staticmethod
-    def to_port_range(from_port, to_port):
-        if from_port is not None and to_port is not None:
-            return '%d-%d' % (from_port, to_port)
-        elif from_port is not None:
-            return from_port
-        else:
-            return to_port
-
-    def create_with_priority(self, direction, protocol, priority,
-                             from_port=None, to_port=None, cidr=None,
-                             src_dest_fw=None):
-        port = GCEVMFirewallRuleContainer.to_port_range(from_port, to_port)
-        src_dest_tag = None
-        src_dest_fw_id = None
-        if src_dest_fw:
-            src_dest_tag = src_dest_fw.name
-            src_dest_fw_id = src_dest_fw.id
-        if not self.firewall.delegate.add_firewall(
-                self.firewall.name, direction, protocol, priority, port, cidr,
-                src_dest_tag, self.firewall.description,
-                self.firewall.network.name):
-            return None
-        rules = self.find(direction=direction, protocol=protocol,
-                          from_port=from_port, to_port=to_port, cidr=cidr,
-                          src_dest_fw_id=src_dest_fw_id)
-        if len(rules) < 1:
-            return None
-        return rules[0]
-
-    def create(self, direction, protocol, from_port=None, to_port=None,
-               cidr=None, src_dest_fw=None):
-        return self.create_with_priority(direction, protocol, 1000, from_port,
-                                         to_port, cidr, src_dest_fw)
-
-
 class GCEVMFirewallRule(BaseVMFirewallRule):
 
     def __init__(self, parent_fw, rule):
@@ -702,14 +644,6 @@ class GCEVMFirewallRule(BaseVMFirewallRule):
             return False
         return True
 
-    def delete(self):
-        if (self.is_dummy_rule()):
-            return
-        self.force_delete()
-
-    def force_delete(self):
-        self.firewall.delegate.delete_firewall_id(self._rule)
-
 
 class GCEMachineImage(BaseMachineImage):
 
@@ -1031,6 +965,7 @@ class GCEInstance(BaseInstance):
         """
         # Get instance again to avoid stale metadata
         ins = self._provider.compute.instances.get(self.id)
+        # pylint:disable=protected-access
         meta = ins._gce_instance.get('metadata', {})
         if meta:
             items = meta.get("items", [])
@@ -1047,7 +982,7 @@ class GCEInstance(BaseInstance):
         network_url = self._gce_instance.get('networkInterfaces')[0].get(
             'network')
         network = self._provider.networking.networks.get(network_url)
-        self._inet_gateway = network.gateways.get_or_create_inet_gateway()
+        self._inet_gateway = network.gateways.get_or_create()
         return self._inet_gateway
 
     def create_image(self, label):
@@ -1057,7 +992,7 @@ class GCEInstance(BaseInstance):
         self.assert_valid_resource_label(label)
         name = self._generate_name_from_label(label, 'cb-img')
         if 'disks' not in self._gce_instance:
-            cb.log.error('Failed to create image: no disks found.')
+            log.error('Failed to create image: no disks found.')
             return
         for disk in self._gce_instance['disks']:
             if 'boot' in disk and disk['boot']:
@@ -1076,7 +1011,7 @@ class GCEInstance(BaseInstance):
                 self._provider.wait_for_operation(operation)
                 img = self._provider.get_resource('images', name)
                 return GCEMachineImage(self._provider, img) if img else None
-        cb.log.error('Failed to create image: no boot disk found.')
+        log.error('Failed to create image: no boot disk found.')
 
     def _get_existing_target_instance(self):
         """
@@ -1093,7 +1028,7 @@ class GCEInstance(BaseInstance):
                 if url.parameters['instance'] == self.name:
                     return target_instance
         except Exception as e:
-            cb.log.warning('Exception while listing target instances: %s', e)
+            log.warning('Exception while listing target instances: %s', e)
         return None
 
     def _get_target_instance(self):
@@ -1119,8 +1054,7 @@ class GCEInstance(BaseInstance):
                             .execute())
             self._provider.wait_for_operation(response, zone=self.zone_name)
         except Exception as e:
-            cb.log.warning('Exception while inserting a target instance: %s',
-                           e)
+            log.warning('Exception while inserting a target instance: %s', e)
             return None
 
         # The following method should find the target instance that we
@@ -1160,7 +1094,7 @@ class GCEInstance(BaseInstance):
                                                   region=ip.region_name)
                 return True
         except Exception as e:
-            cb.log.warning(
+            log.warning(
                 'Exception while listing/changing forwarding rules: %s', e)
         return False
 
@@ -1186,8 +1120,7 @@ class GCEInstance(BaseInstance):
                             .execute())
             self._provider.wait_for_operation(response, region=ip.region_name)
         except Exception as e:
-            cb.log.warning('Exception while inserting a forwarding rule: %s',
-                           e)
+            log.warning('Exception while inserting a forwarding rule: %s', e)
             return False
         return True
 
@@ -1209,7 +1142,7 @@ class GCEInstance(BaseInstance):
                 temp_zone = parsed_target_url.parameters['zone']
                 temp_name = parsed_target_url.parameters['targetInstance']
                 if temp_zone != zone or temp_name != name:
-                    cb.log.warning(
+                    log.warning(
                             '"%s" is forwarded to "%s" in zone "%s"',
                             ip.public_ip, temp_name, temp_zone)
                     return False
@@ -1225,7 +1158,7 @@ class GCEInstance(BaseInstance):
                                                   region=ip.region_name)
                 return True
         except Exception as e:
-            cb.log.warning(
+            log.warning(
                 'Exception while listing/deleting forwarding rules: %s', e)
             return False
         return True
@@ -1238,17 +1171,17 @@ class GCEInstance(BaseInstance):
                else self.inet_gateway.floating_ips.get(floating_ip))
         if fip.in_use:
             if fip.private_ip not in self.private_ips:
-                cb.log.warning('Floating IP "%s" is not associated to "%s"',
-                               fip.public_ip, self.name)
+                log.warning('Floating IP "%s" is not associated to "%s"',
+                            fip.public_ip, self.name)
             return
         target_instance = self._get_target_instance()
         if not target_instance:
-            cb.log.warning('Could not create a targetInstance for "%s"',
-                           self.name)
+            log.warning('Could not create a targetInstance for "%s"',
+                        self.name)
             return
         if not self._forward(fip, target_instance):
-            cb.log.warning('Could not forward "%s" to "%s"',
-                           fip.public_ip, target_instance['selfLink'])
+            log.warning('Could not forward "%s" to "%s"',
+                        fip.public_ip, target_instance['selfLink'])
 
     def remove_floating_ip(self, floating_ip):
         """
@@ -1257,17 +1190,17 @@ class GCEInstance(BaseInstance):
         fip = (floating_ip if isinstance(floating_ip, GCEFloatingIP)
                else self.inet_gateway.floating_ips.get(floating_ip))
         if not fip.in_use or fip.private_ip not in self.private_ips:
-            cb.log.warning('Floating IP "%s" is not associated to "%s"',
-                           fip.public_ip, self.name)
+            log.warning('Floating IP "%s" is not associated to "%s"',
+                        fip.public_ip, self.name)
             return
         target_instance = self._get_target_instance()
         if not target_instance:
             # We should not be here.
-            cb.log.warning('Something went wrong! "%s" is associated to "%s" '
-                           'with no target instance', fip.public_ip, self.name)
+            log.warning('Something went wrong! "%s" is associated to "%s" '
+                        'with no target instance', fip.public_ip, self.name)
             return
         if not self._delete_existing_rule(fip, target_instance):
-            cb.log.warning(
+            log.warning(
                 'Could not remove floating IP "%s" from instance "%s"',
                 fip.public_ip, self.name)
 
@@ -1323,7 +1256,7 @@ class GCENetwork(BaseNetwork):
     def __init__(self, provider, network):
         super(GCENetwork, self).__init__(provider)
         self._network = network
-        self._gateway_container = GCEGatewayContainer(provider, self)
+        self._gateway_container = GCEGatewaySubService(provider, self)
 
     @property
     def resource_url(self):
@@ -1394,55 +1327,11 @@ class GCENetwork(BaseNetwork):
         return self._gateway_container
 
 
-class GCEFloatingIPContainer(BaseFloatingIPContainer):
-
-    def __init__(self, provider, gateway):
-        super(GCEFloatingIPContainer, self).__init__(provider, gateway)
-
-    def get(self, floating_ip_id):
-        fip = self._provider.get_resource('addresses', floating_ip_id)
-        return (GCEFloatingIP(self._provider, self.gateway, fip)
-                if fip else None)
-
-    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
-                        .addresses()
-                        .list(project=self._provider.project_name,
-                              region=self._provider.region_name,
-                              maxResults=max_result,
-                              pageToken=marker)
-                        .execute())
-        ips = [GCEFloatingIP(self._provider, self.gateway, ip)
-               for ip in response.get('items', [])]
-        if len(ips) > max_result:
-            cb.log.warning('Expected at most %d results; got %d',
-                           max_result, len(ips))
-        return ServerPagedResultList('nextPageToken' in response,
-                                     response.get('nextPageToken'),
-                                     False, data=ips)
-
-    def create(self):
-        region_name = self._provider.region_name
-        ip_name = 'ip-{0}'.format(uuid.uuid4())
-        response = (self._provider
-                    .gce_compute
-                    .addresses()
-                    .insert(project=self._provider.project_name,
-                            region=region_name,
-                            body={'name': ip_name})
-                    .execute())
-        self._provider.wait_for_operation(response, region=region_name)
-        return self.get(ip_name)
-
-
 class GCEFloatingIP(BaseFloatingIP):
     _DEAD_INSTANCE = 'dead instance'
 
-    def __init__(self, provider, gateway, floating_ip):
+    def __init__(self, provider, floating_ip):
         super(GCEFloatingIP, self).__init__(provider)
-        self._gateway = gateway
         self._ip = floating_ip
         self._process_ip_users()
 
@@ -1473,32 +1362,9 @@ class GCEFloatingIP(BaseFloatingIP):
     def in_use(self):
         return True if self._target_instance else False
 
-    def delete(self):
-        project_name = self._provider.project_name
-        # First, delete the forwarding rule, if there is any.
-        if self._rule:
-            response = (self._provider
-                            .gce_compute
-                            .forwardingRules()
-                            .delete(project=project_name,
-                                    region=self.region_name,
-                                    forwardingRule=self._rule['name'])
-                            .execute())
-            self._provider.wait_for_operation(response,
-                                              region=self.region_name)
-
-        # Release the address.
-        response = (self._provider
-                        .gce_compute
-                        .addresses()
-                        .delete(project=project_name,
-                                region=self.region_name,
-                                address=self._ip['name'])
-                        .execute())
-        self._provider.wait_for_operation(response, region=self.region_name)
-
     def refresh(self):
-        fip = self._gateway.floating_ips.get(self.id)
+        # pylint:disable=protected-access
+        fip = self._provider.networking._floating_ips.get(None, self.id)
         # pylint:disable=protected-access
         self._ip = fip._ip
         self._process_ip_users()
@@ -1510,8 +1376,8 @@ class GCEFloatingIP(BaseFloatingIP):
         if 'users' in self._ip and len(self._ip['users']) > 0:
             provider = self._provider
             if len(self._ip['users']) > 1:
-                cb.log.warning('Address "%s" in use by more than one resource',
-                               self._ip.get('address'))
+                log.warning('Address "%s" in use by more than one resource',
+                            self._ip.get('address'))
             resource_parsed_url = provider.parse_url(self._ip['users'][0])
             resource = resource_parsed_url.get_resource()
             if resource['kind'] == 'compute#forwardingRule':
@@ -1524,11 +1390,11 @@ class GCEFloatingIP(BaseFloatingIP):
                     except googleapiclient.errors.HttpError:
                         self._target_instance = GCEFloatingIP._DEAD_INSTANCE
                 else:
-                    cb.log.warning('Address "%s" is forwarded to a %s',
-                                   self._ip.get('address'), target['kind'])
+                    log.warning('Address "%s" is forwarded to a %s',
+                                self._ip.get('address'), target['kind'])
             else:
-                cb.log.warning('Address "%s" in use by a %s',
-                               self._ip.get('address'), resource['kind'])
+                log.warning('Address "%s" in use by a %s',
+                            self._ip.get('address'), resource['kind'])
 
 
 class GCERouter(BaseRouter):
@@ -1595,14 +1461,14 @@ class GCERouter(BaseRouter):
             subnet = self._provider.networking.subnets.get(subnet)
         if subnet.network_id == self.network_id:
             return
-        cb.log.warning('Google Cloud Routers automatically learn new subnets '
-                       'in your VPC network and announces them to your '
-                       'on-premises network')
+        log.warning('Google Cloud Routers automatically learn new subnets '
+                    'in your VPC network and announces them to your '
+                    'on-premises network')
 
     def detach_subnet(self, network_id):
-        cb.log.warning('Cannot detach from subnet. Google Cloud Routers '
-                       'automatically learn new subnets in your VPC network '
-                       'and announces them to your on-premises network')
+        log.warning('Cannot detach from subnet. Google Cloud Routers '
+                    'automatically learn new subnets in your VPC network '
+                    'and announces them to your on-premises network')
 
     def attach_gateway(self, gateway):
         pass
@@ -1611,36 +1477,12 @@ class GCERouter(BaseRouter):
         pass
 
 
-class GCEGatewayContainer(BaseGatewayContainer):
-    _DEFAULT_GATEWAY_NAME = 'default-internet-gateway'
-    _GATEWAY_URL_PREFIX = 'global/gateways/'
-
-    def __init__(self, provider, network):
-        super(GCEGatewayContainer, self).__init__(provider, network)
-        self._default_internet_gateway = GCEInternetGateway(
-            provider,
-            {'id': (GCEGatewayContainer._GATEWAY_URL_PREFIX +
-                    GCEGatewayContainer._DEFAULT_GATEWAY_NAME),
-             'name': GCEGatewayContainer._DEFAULT_GATEWAY_NAME})
-
-    def get_or_create_inet_gateway(self, name=None):
-        return self._default_internet_gateway
-
-    def delete(self, gateway):
-        pass
-
-    def list(self, limit=None, marker=None):
-        return ClientPagedResultList(self._provider,
-                                     [self._default_internet_gateway],
-                                     limit=limit, marker=marker)
-
-
 class GCEInternetGateway(BaseInternetGateway):
 
     def __init__(self, provider, gateway):
         super(GCEInternetGateway, self).__init__(provider)
         self._gateway = gateway
-        self._fip_container = GCEFloatingIPContainer(provider, self)
+        self._fip_container = GCEFloatingIPSubService(provider, self)
 
     @property
     def id(self):
@@ -1836,7 +1678,7 @@ class GCEVolume(BaseVolume):
         # the first user of a disk.
         if 'users' in self._volume and len(self._volume) > 0:
             if len(self._volume) > 1:
-                cb.log.warning("This volume is attached to multiple instances")
+                log.warning("This volume is attached to multiple instances")
             return BaseAttachmentInfo(self,
                                       self._volume.get('users')[0],
                                       None)
@@ -2087,8 +1929,9 @@ class GCSObject(BaseBucketObject):
             data = data.encode()
         media_body = googleapiclient.http.MediaIoBaseUpload(
                 io.BytesIO(data), mimetype='plain/text')
+        # pylint:disable=protected-access
         response = (self._provider
-                        .storage.bucket_objects
+                        .storage._bucket_objects
                         ._create_object_with_media_body(self._bucket,
                                                         self.name,
                                                         media_body))
@@ -2102,8 +1945,9 @@ class GCSObject(BaseBucketObject):
         with open(path, 'rb') as f:
             media_body = googleapiclient.http.MediaIoBaseUpload(
                     f, 'application/octet-stream')
+            # pylint:disable=protected-access
             response = (self._provider
-                        .storage.bucket_objects
+                        .storage._bucket_objects
                         ._create_object_with_media_body(self._bucket,
                                                         self.name,
                                                         media_body))
@@ -2139,18 +1983,12 @@ class GCSObject(BaseBucketObject):
         self._obj = self.bucket.objects.get(self.id)._obj
 
 
-class GCSBucketContainer(BaseBucketContainer):
-
-    def __init__(self, provider, bucket):
-        super(GCSBucketContainer, self).__init__(provider, bucket)
-
-
 class GCSBucket(BaseBucket):
 
     def __init__(self, provider, bucket):
         super(GCSBucket, self).__init__(provider)
         self._bucket = bucket
-        self._object_container = GCSBucketContainer(provider, self)
+        self._object_container = GCSBucketObjectSubService(provider, self)
 
     @property
     def id(self):

+ 254 - 45
cloudbridge/cloud/providers/gce/services.py

@@ -7,7 +7,6 @@ import uuid
 
 import googleapiclient
 
-import cloudbridge as cb
 from cloudbridge.cloud.base import helpers as cb_helpers
 from cloudbridge.cloud.base.middleware import dispatch
 from cloudbridge.cloud.base.resources import ClientPagedResultList
@@ -15,6 +14,8 @@ from cloudbridge.cloud.base.resources import ServerPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketObjectService
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseComputeService
+from cloudbridge.cloud.base.services import BaseFloatingIPService
+from cloudbridge.cloud.base.services import BaseGatewayService
 from cloudbridge.cloud.base.services import BaseImageService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseKeyPairService
@@ -26,6 +27,7 @@ from cloudbridge.cloud.base.services import BaseSecurityService
 from cloudbridge.cloud.base.services import BaseSnapshotService
 from cloudbridge.cloud.base.services import BaseStorageService
 from cloudbridge.cloud.base.services import BaseSubnetService
+from cloudbridge.cloud.base.services import BaseVMFirewallRuleService
 from cloudbridge.cloud.base.services import BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
@@ -36,7 +38,9 @@ from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.providers.gce import helpers
 
 from .resources import GCEFirewallsDelegate
+from .resources import GCEFloatingIP
 from .resources import GCEInstance
+from .resources import GCEInternetGateway
 from .resources import GCEKeyPair
 from .resources import GCELaunchConfig
 from .resources import GCEMachineImage
@@ -47,6 +51,7 @@ from .resources import GCERouter
 from .resources import GCESnapshot
 from .resources import GCESubnet
 from .resources import GCEVMFirewall
+from .resources import GCEVMFirewallRule
 from .resources import GCEVMType
 from .resources import GCEVolume
 from .resources import GCSBucket
@@ -63,6 +68,7 @@ class GCESecurityService(BaseSecurityService):
         # Initialize provider services
         self._key_pairs = GCEKeyPairService(provider)
         self._vm_firewalls = GCEVMFirewallService(provider)
+        self._vm_firewall_rule_svc = GCEVMFirewallRuleService(provider)
 
     @property
     def key_pairs(self):
@@ -72,6 +78,10 @@ class GCESecurityService(BaseSecurityService):
     def vm_firewalls(self):
         return self._vm_firewalls
 
+    @property
+    def _vm_firewall_rules(self):
+        return self._vm_firewall_rule_svc
+
 
 class GCEKeyPairService(BaseKeyPairService):
 
@@ -149,11 +159,12 @@ class GCEKeyPairService(BaseKeyPairService):
 
     @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:
+    def delete(self, key_pair):
+        key_pair = (key_pair if isinstance(key_pair, GCEKeyPair) else
+                    self.get(key_pair))
+        if key_pair:
             helpers.remove_metadata_item(
-                self.provider, GCEKeyPair.KP_TAG_PREFIX + kp.name)
+                self.provider, GCEKeyPair.KP_TAG_PREFIX + key_pair.name)
 
 
 class GCEVMFirewallService(BaseVMFirewallService):
@@ -191,18 +202,20 @@ class GCEVMFirewallService(BaseVMFirewallService):
         network = (network if isinstance(network, GCENetwork)
                    else self.provider.networking.networks.get(network))
         fw = GCEVMFirewall(self._delegate, label, network, description)
+        fw.label = label
         # This rule exists implicitly. Add it explicitly so that the firewall
         # is not empty and the rule is shown by list/get/find methods.
-        fw.rules.create_with_priority(
-                direction=TrafficDirection.OUTBOUND, protocol='tcp',
-                priority=65534, cidr='0.0.0.0/0')
-        fw.label = label
+        # pylint:disable=protected-access
+        self.provider.security._vm_firewall_rules.create_with_priority(
+            fw, direction=TrafficDirection.OUTBOUND, protocol='tcp',
+            priority=65534, cidr='0.0.0.0/0')
         return fw
 
     @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
+    def delete(self, vm_firewall):
+        fw_id = (vm_firewall.id if isinstance(vm_firewall, GCEVMFirewall)
+                 else vm_firewall)
         return self._delegate.delete_tag_network_with_id(fw_id)
 
     def find_by_network_and_tags(self, network_name, tags):
@@ -222,6 +235,80 @@ class GCEVMFirewallService(BaseVMFirewallService):
         return vm_firewalls
 
 
+class GCEVMFirewallRuleService(BaseVMFirewallRuleService):
+
+    def __init__(self, provider):
+        super(GCEVMFirewallRuleService, self).__init__(provider)
+        self._dummy_rule = None
+
+    @dispatch(event="provider.security.vm_firewall_rules.list",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def list(self, firewall, limit=None, marker=None):
+        rules = []
+        for fw in firewall.delegate.iter_firewalls(
+                firewall.name, firewall.network.name):
+            rule = GCEVMFirewallRule(firewall, fw['id'])
+            if rule.is_dummy_rule():
+                self._dummy_rule = rule
+            else:
+                rules.append(rule)
+        return ClientPagedResultList(self.provider, rules,
+                                     limit=limit, marker=marker)
+
+    @property
+    def dummy_rule(self):
+        if not self._dummy_rule:
+            self.list()
+        return self._dummy_rule
+
+    @staticmethod
+    def to_port_range(from_port, to_port):
+        if from_port is not None and to_port is not None:
+            return '%d-%d' % (from_port, to_port)
+        elif from_port is not None:
+            return from_port
+        else:
+            return to_port
+
+    def create_with_priority(self, firewall, direction, protocol, priority,
+                             from_port=None, to_port=None, cidr=None,
+                             src_dest_fw=None):
+        port = GCEVMFirewallRuleService.to_port_range(from_port, to_port)
+        src_dest_tag = None
+        src_dest_fw_id = None
+        if src_dest_fw:
+            src_dest_tag = src_dest_fw.name
+            src_dest_fw_id = src_dest_fw.id
+        if not firewall.delegate.add_firewall(
+                firewall.name, direction, protocol, priority, port, cidr,
+                src_dest_tag, firewall.description,
+                firewall.network.name):
+            return None
+        rules = self.find(firewall, direction=direction, protocol=protocol,
+                          from_port=from_port, to_port=to_port, cidr=cidr,
+                          src_dest_fw_id=src_dest_fw_id)
+        if len(rules) < 1:
+            return None
+        return rules[0]
+
+    @dispatch(event="provider.security.vm_firewall_rules.create",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def create(self, firewall, direction, protocol, from_port=None,
+               to_port=None, cidr=None, src_dest_fw=None):
+        return self.create_with_priority(firewall, direction, protocol,
+                                         1000, from_port, to_port, cidr,
+                                         src_dest_fw)
+
+    @dispatch(event="provider.security.vm_firewall_rules.delete",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def delete(self, firewall, rule):
+        rule = (rule if isinstance(rule, GCEVMFirewallRule)
+                else self.get(firewall, rule))
+        if rule.is_dummy_rule():
+            return True
+        firewall.delegate.delete_firewall_id(rule._rule)
+
+
 class GCEVMTypeService(BaseVMTypeService):
 
     def __init__(self, provider):
@@ -296,8 +383,8 @@ class GCERegionService(BaseRegionService):
         regions = [GCERegion(self.provider, region)
                    for region in regions_response['items']]
         if len(regions) > max_result:
-            cb.log.warning('Expected at most %d results; got %d',
-                           max_result, len(regions))
+            log.warning('Expected at most %d results; got %d',
+                        max_result, len(regions))
         return ServerPagedResultList('nextPageToken' in regions_response,
                                      regions_response.get('nextPageToken'),
                                      False, data=regions)
@@ -434,7 +521,7 @@ class GCEInstanceService(BaseInstanceService):
                     source_field = 'source'
                     source_value = volume.id
                 else:
-                    cb.log.warning('Unknown disk source')
+                    log.warning('Unknown disk source')
                     continue
                 autoDelete = True
                 if disk.delete_on_terminate is not None:
@@ -450,13 +537,13 @@ class GCEInstanceService(BaseInstanceService):
                                   source_field: source_value})
 
         if num_roots > 1:
-            cb.log.warning('The launch config contains %d boot disks. Will '
-                           'use the first one', num_roots)
+            log.warning('The launch config contains %d boot disks. Will '
+                        'use the first one', num_roots)
         if image:
             if boot_disk:
-                cb.log.warning('A boot image is given while the launch config '
-                               'contains a boot disk, too. The launch config '
-                               'will be used.')
+                log.warning('A boot image is given while the launch config '
+                            'contains a boot disk, too. The launch config '
+                            'will be used.')
             else:
                 if not isinstance(image, GCEMachineImage):
                     image = self.provider.compute.images.get(image)
@@ -470,7 +557,7 @@ class GCEInstanceService(BaseInstanceService):
                         'diskName': 'image-disk-{0}'.format(uuid.uuid4())}}
 
         if not boot_disk:
-            cb.log.warning('No boot disk is given for instance %s.', label)
+            log.warning('No boot disk is given for instance %s.', label)
             return None
         # The boot disk must be the first disk attached to the instance.
         disks.insert(0, boot_disk)
@@ -582,16 +669,17 @@ class GCEInstanceService(BaseInstanceService):
         instances = [GCEInstance(self.provider, inst)
                      for inst in response.get('items', [])]
         if len(instances) > max_result:
-            cb.log.warning('Expected at most %d results; got %d',
-                           max_result, len(instances))
+            log.warning('Expected at most %d results; got %d',
+                        max_result, len(instances))
         return ServerPagedResultList('nextPageToken' in response,
                                      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)
+    def delete(self, instance):
+        instance = (instance if isinstance(instance, GCEInstance) else
+                    self.get(instance))
         if instance:
             (self._provider
              .gce_compute
@@ -638,6 +726,8 @@ class GCENetworkingService(BaseNetworkingService):
         self._network_service = GCENetworkService(self.provider)
         self._subnet_service = GCESubnetService(self.provider)
         self._router_service = GCERouterService(self.provider)
+        self._gateway_service = GCEGatewayService(self.provider)
+        self._floating_ip_service = GCEFloatingIPService(self.provider)
 
     @property
     def networks(self):
@@ -651,6 +741,14 @@ class GCENetworkingService(BaseNetworkingService):
     def routers(self):
         return self._router_service
 
+    @property
+    def _gateways(self):
+        return self._gateway_service
+
+    @property
+    def _floating_ips(self):
+        return self._floating_ip_service
+
 
 class GCENetworkService(BaseNetworkService):
 
@@ -791,8 +889,8 @@ class GCERouterService(BaseRouterService):
         for router in response.get('items', []):
             routers.append(GCERouter(self.provider, router))
         if len(routers) > max_result:
-            cb.log.warning('Expected at most %d results; go %d',
-                           max_result, len(routers))
+            log.warning('Expected at most %d results; go %d',
+                        max_result, len(routers))
         return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      False, data=routers)
@@ -908,7 +1006,7 @@ class GCESubnetService(BaseSubnetService):
 #         for subnet in self.iter(network=network):
 #            if BaseNetwork.cidr_blocks_overlap(subnet.cidr_block, cidr_block):
 #                 if subnet.region_name != region_name:
-#                     cb.log.error('Failed to create subnetwork in region %s: '
+#                     log.error('Failed to create subnetwork in region %s: '
 #                                  'the given IP range %s overlaps with a '
 #                                  'subnetwork in a different region %s',
 #                                  region_name, cidr_block, subnet.region_name)
@@ -994,7 +1092,7 @@ class GCESubnetService(BaseSubnetService):
                 cidr_block=cidr_block, network=net, zone=zone)
         router = self.provider.networking.routers.get_or_create_default(net)
         router.attach_subnet(sn)
-        gateway = net.gateways.get_or_create_inet_gateway()
+        gateway = net.gateways.get_or_create()
         router.attach_gateway(gateway)
         return sn
 
@@ -1042,7 +1140,7 @@ class GCPStorageService(BaseStorageService):
         return self._bucket_svc
 
     @property
-    def bucket_objects(self):
+    def _bucket_objects(self):
         return self._bucket_obj_svc
 
 
@@ -1085,8 +1183,8 @@ class GCEVolumeService(BaseVolumeService):
         gce_vols = [GCEVolume(self.provider, vol)
                     for vol in response.get('items', [])]
         if len(gce_vols) > max_result:
-            cb.log.warning('Expected at most %d results; got %d',
-                           max_result, len(gce_vols))
+            log.warning('Expected at most %d results; got %d',
+                        max_result, len(gce_vols))
         return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      False, data=gce_vols)
@@ -1115,8 +1213,8 @@ class GCEVolumeService(BaseVolumeService):
         gce_vols = [GCEVolume(self.provider, vol)
                     for vol in response.get('items', [])]
         if len(gce_vols) > max_result:
-            cb.log.warning('Expected at most %d results; got %d',
-                           max_result, len(gce_vols))
+            log.warning('Expected at most %d results; got %d',
+                        max_result, len(gce_vols))
         return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      False, data=gce_vols)
@@ -1154,8 +1252,8 @@ class GCEVolumeService(BaseVolumeService):
 
     @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)
+    def delete(self, volume):
+        volume = volume if isinstance(volume, GCEVolume) else self.get(volume)
         if volume:
             (self._provider.gce_compute
                            .disks()
@@ -1200,8 +1298,8 @@ class GCESnapshotService(BaseSnapshotService):
         snapshots = [GCESnapshot(self.provider, snapshot)
                      for snapshot in response.get('items', [])]
         if len(snapshots) > max_result:
-            cb.log.warning('Expected at most %d results; got %d',
-                           max_result, len(snapshots))
+            log.warning('Expected at most %d results; got %d',
+                        max_result, len(snapshots))
         return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      False, data=snapshots)
@@ -1220,8 +1318,8 @@ class GCESnapshotService(BaseSnapshotService):
         snapshots = [GCESnapshot(self.provider, snapshot)
                      for snapshot in response.get('items', [])]
         if len(snapshots) > max_result:
-            cb.log.warning('Expected at most %d results; got %d',
-                           max_result, len(snapshots))
+            log.warning('Expected at most %d results; got %d',
+                        max_result, len(snapshots))
         return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      False, data=snapshots)
@@ -1254,8 +1352,9 @@ class GCESnapshotService(BaseSnapshotService):
 
     @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)
+    def delete(self, snapshot):
+        snapshot = (snapshot if isinstance(snapshot, GCESnapshot)
+                    else self.get(snapshot))
         if snapshot:
             (self.provider
                  .gce_compute
@@ -1314,8 +1413,8 @@ class GCSBucketService(BaseBucketService):
         for bucket in response.get('items', []):
             buckets.append(GCSBucket(self.provider, bucket))
         if len(buckets) > max_result:
-            cb.log.warning('Expected at most %d results; got %d',
-                           max_result, len(buckets))
+            log.warning('Expected at most %d results; got %d',
+                        max_result, len(buckets))
         return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      False, data=buckets)
@@ -1396,8 +1495,8 @@ class GCSBucketObjectService(BaseBucketObjectService):
         for obj in response.get('items', []):
             objects.append(GCSObject(self.provider, bucket, obj))
         if len(objects) > max_result:
-            cb.log.warning('Expected at most %d results; got %d',
-                           max_result, len(objects))
+            log.warning('Expected at most %d results; got %d',
+                        max_result, len(objects))
         return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      False, data=objects)
@@ -1427,3 +1526,113 @@ class GCSBucketObjectService(BaseBucketObjectService):
         return GCSObject(self._provider,
                          bucket,
                          response) if response else None
+
+
+class GCEGatewayService(BaseGatewayService):
+    _DEFAULT_GATEWAY_NAME = 'default-internet-gateway'
+    _GATEWAY_URL_PREFIX = 'global/gateways/'
+
+    def __init__(self, provider):
+        super(GCEGatewayService, self).__init__(provider)
+        self._default_internet_gateway = GCEInternetGateway(
+            provider,
+            {'id': (GCEGatewayService._GATEWAY_URL_PREFIX +
+                    GCEGatewayService._DEFAULT_GATEWAY_NAME),
+             'name': GCEGatewayService._DEFAULT_GATEWAY_NAME})
+
+    @dispatch(event="provider.networking.gateways.get_or_create",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def get_or_create(self, network):
+        return self._default_internet_gateway
+
+    @dispatch(event="provider.networking.gateways.delete",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def delete(self, network, gateway):
+        pass
+
+    @dispatch(event="provider.networking.gateways.list",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def list(self, network, limit=None, marker=None):
+        gws = [self._default_internet_gateway]
+        return ClientPagedResultList(self._provider,
+                                     gws,
+                                     limit=limit, marker=marker)
+
+
+class GCEFloatingIPService(BaseFloatingIPService):
+
+    def __init__(self, provider):
+        super(GCEFloatingIPService, self).__init__(provider)
+
+    @dispatch(event="provider.networking.floating_ips.get",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def get(self, gateway, floating_ip_id):
+        fip = self.provider.get_resource('addresses', floating_ip_id)
+        return (GCEFloatingIP(self.provider, fip)
+                if fip else None)
+
+    @dispatch(event="provider.networking.floating_ips.list",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def list(self, gateway, limit=None, marker=None):
+        max_result = limit if limit is not None and limit < 500 else 500
+        response = (self.provider
+                        .gce_compute
+                        .addresses()
+                        .list(project=self.provider.project_name,
+                              region=self.provider.region_name,
+                              maxResults=max_result,
+                              pageToken=marker)
+                        .execute())
+        ips = [GCEFloatingIP(self.provider, ip)
+               for ip in response.get('items', [])]
+        if len(ips) > max_result:
+            log.warning('Expected at most %d results; got %d',
+                        max_result, len(ips))
+        return ServerPagedResultList('nextPageToken' in response,
+                                     response.get('nextPageToken'),
+                                     False, data=ips)
+
+    @dispatch(event="provider.networking.floating_ips.create",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def create(self, gateway):
+        region_name = self.provider.region_name
+        ip_name = 'ip-{0}'.format(uuid.uuid4())
+        response = (self.provider
+                    .gce_compute
+                    .addresses()
+                    .insert(project=self.provider.project_name,
+                            region=region_name,
+                            body={'name': ip_name})
+                    .execute())
+        self.provider.wait_for_operation(response, region=region_name)
+        return self.get(gateway, ip_name)
+
+    @dispatch(event="provider.networking.floating_ips.delete",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def delete(self, gateway, fip):
+        fip = (fip if isinstance(fip, GCEFloatingIP)
+               else self.get(gateway, fip))
+        project_name = self.provider.project_name
+        # First, delete the forwarding rule, if there is any.
+        # pylint:disable=protected-access
+        if fip._rule:
+            response = (self.provider
+                        .gce_compute
+                        .forwardingRules()
+                        .delete(project=project_name,
+                                region=fip.region_name,
+                                forwardingRule=fip._rule['name'])
+                        .execute())
+            self.provider.wait_for_operation(response,
+                                             region=fip.region_name)
+
+        # Release the address.
+        response = (self.provider
+                    .gce_compute
+                    .addresses()
+                    .delete(project=project_name,
+                            region=fip.region_name,
+                            address=fip._ip['name'])
+                    .execute())
+        self.provider.wait_for_operation(response,
+                                         region=fip.region_name)

+ 33 - 0
cloudbridge/cloud/providers/gce/subservices.py

@@ -0,0 +1,33 @@
+import logging
+
+from cloudbridge.cloud.base.subservices import BaseBucketObjectSubService
+from cloudbridge.cloud.base.subservices import BaseFloatingIPSubService
+from cloudbridge.cloud.base.subservices import \
+    BaseGatewaySubService
+from cloudbridge.cloud.base.subservices import BaseVMFirewallRuleSubService
+
+
+log = logging.getLogger(__name__)
+
+
+class GCSBucketObjectSubService(BaseBucketObjectSubService):
+
+    def __init__(self, provider, bucket):
+        super(GCSBucketObjectSubService, self).__init__(provider, bucket)
+
+
+class GCEGatewaySubService(BaseGatewaySubService):
+    def __init__(self, provider, network):
+        super(GCEGatewaySubService, self).__init__(provider, network)
+
+
+class GCEVMFirewallRuleSubService(BaseVMFirewallRuleSubService):
+
+    def __init__(self, provider, firewall):
+        super(GCEVMFirewallRuleSubService, self).__init__(provider, firewall)
+
+
+class GCEFloatingIPSubService(BaseFloatingIPSubService):
+
+    def __init__(self, provider, gateway):
+        super(GCEFloatingIPSubService, self).__init__(provider, gateway)

+ 20 - 156
cloudbridge/cloud/providers/openstack/resources.py

@@ -5,6 +5,7 @@ import inspect
 import ipaddress
 import logging
 import os
+
 try:
     from urllib.parse import urlparse
     from urllib.parse import urljoin
@@ -16,23 +17,15 @@ from keystoneclient.v3.regions import Region
 
 import novaclient.exceptions as novaex
 
-from openstack.exceptions import HttpException
-from openstack.exceptions import NotFoundException
-from openstack.exceptions import ResourceNotFound
-
 import swiftclient
 from swiftclient.service import SwiftService
 from swiftclient.service import SwiftUploadObject
 from swiftclient.utils import generate_temp_url
 
-import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
-from cloudbridge.cloud.base.resources import BaseBucketContainer
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
-from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
-from cloudbridge.cloud.base.resources import BaseGatewayContainer
 from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInternetGateway
 from cloudbridge.cloud.base.resources import BaseKeyPair
@@ -45,11 +38,8 @@ from cloudbridge.cloud.base.resources import BaseSnapshot
 from cloudbridge.cloud.base.resources import BaseSubnet
 from cloudbridge.cloud.base.resources import BaseVMFirewall
 from cloudbridge.cloud.base.resources import BaseVMFirewallRule
-from cloudbridge.cloud.base.resources import BaseVMFirewallRuleContainer
 from cloudbridge.cloud.base.resources import BaseVMType
 from cloudbridge.cloud.base.resources import BaseVolume
-from cloudbridge.cloud.base.resources import ClientPagedResultList
-from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.resources import GatewayState
 from cloudbridge.cloud.interfaces.resources import InstanceState
 from cloudbridge.cloud.interfaces.resources import MachineImageState
@@ -60,6 +50,11 @@ from cloudbridge.cloud.interfaces.resources import SubnetState
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VolumeState
 
+from .subservices import OpenStackBucketObjectSubService
+from .subservices import OpenStackFloatingIPSubService
+from .subservices import OpenStackGatewaySubService
+from .subservices import OpenStackVMFirewallRuleSubService
+
 ONE_GIG = 1048576000  # in bytes
 FIVE_GIG = ONE_GIG * 5  # in bytes
 
@@ -155,6 +150,7 @@ class OpenStackMachineImage(BaseMachineImage):
         log.debug("Refreshing OpenStack Machine Image")
         image = self._provider.compute.images.get(self.id)
         if image:
+            # pylint:disable=protected-access
             self._os_image = image._os_image  # pylint:disable=protected-access
         else:
             # The image no longer exists and cannot be refreshed.
@@ -448,9 +444,7 @@ class OpenStackInstance(BaseInstance):
 
     def _get_fip(self, floating_ip):
         """Get a floating IP object based on the supplied ID."""
-        return OpenStackFloatingIP(
-            self._provider,
-            self._provider.os_conn.network.get_ip(floating_ip))
+        return self._provider.networking._floating_ips.get(floating_ip)
 
     def add_floating_ip(self, floating_ip):
         """
@@ -665,6 +659,7 @@ class OpenStackVolume(BaseVolume):
         vol = self._provider.storage.volumes.get(
             self.id)
         if vol:
+            # pylint:disable=protected-access
             self._volume = vol._volume  # pylint:disable=protected-access
         else:
             # The volume no longer exists and cannot be refreshed.
@@ -747,6 +742,7 @@ class OpenStackSnapshot(BaseSnapshot):
         snap = self._provider.storage.snapshots.get(
             self.id)
         if snap:
+            # pylint:disable=protected-access
             self._snapshot = snap._snapshot  # pylint:disable=protected-access
         else:
             # The snapshot no longer exists and cannot be refreshed.
@@ -767,50 +763,6 @@ class OpenStackSnapshot(BaseSnapshot):
         return cb_vol
 
 
-class OpenStackGatewayContainer(BaseGatewayContainer):
-    """For OpenStack, an internet gateway is a just an 'external' network."""
-
-    def __init__(self, provider, network):
-        super(OpenStackGatewayContainer, self).__init__(provider, network)
-
-    def _check_fip_connectivity(self, external_net):
-        # Due to current limitations in OpenStack:
-        # https://bugs.launchpad.net/neutron/+bug/1743480, it's not
-        # possible to differentiate between floating ip networks and provider
-        # external networks. Therefore, we systematically step through
-        # all available networks and perform an assignment test to infer valid
-        # floating ip nets.
-        dummy_router = self._provider.networking.routers.create(
-            label='cb-conn-test-router', network=self._network)
-        with cb_helpers.cleanup_action(lambda: dummy_router.delete()):
-            try:
-                dummy_router.attach_gateway(external_net)
-                return True
-            except Exception:
-                return False
-
-    def get_or_create_inet_gateway(self):
-        """For OS, inet gtw is any net that has `external` property set."""
-        external_nets = (n for n in self._provider.networking.networks
-                         if n.external)
-        for net in external_nets:
-            if self._check_fip_connectivity(net):
-                return OpenStackInternetGateway(self._provider, net)
-        return None
-
-    def delete(self, gateway):
-        log.debug("Deleting OpenStack Gateway: %s", gateway)
-        gateway.delete()
-
-    def list(self, limit=None, marker=None):
-        log.debug("OpenStack listing of all current internet gateways")
-        igl = [OpenStackInternetGateway(self._provider, n)
-               for n in self._provider.networking.networks
-               if n.external and self._check_fip_connectivity(n)]
-        return ClientPagedResultList(self._provider, igl, limit=limit,
-                                     marker=marker)
-
-
 class OpenStackNetwork(BaseNetwork):
 
     # Ref: https://github.com/openstack/neutron/blob/master/neutron/plugins/
@@ -829,7 +781,7 @@ class OpenStackNetwork(BaseNetwork):
     def __init__(self, provider, network):
         super(OpenStackNetwork, self).__init__(provider)
         self._network = network
-        self._gateway_service = OpenStackGatewayContainer(provider, self)
+        self._gateway_service = OpenStackGatewaySubService(provider, self)
 
     @property
     def id(self):
@@ -952,33 +904,6 @@ class OpenStackSubnet(BaseSubnet):
             self._state = SubnetState.UNKNOWN
 
 
-class OpenStackFloatingIPContainer(BaseFloatingIPContainer):
-
-    def __init__(self, provider, gateway):
-        super(OpenStackFloatingIPContainer, self).__init__(provider, gateway)
-
-    def get(self, fip_id):
-        try:
-            return OpenStackFloatingIP(
-                self._provider, self._provider.os_conn.network.get_ip(fip_id))
-        except (ResourceNotFound, NotFoundException):
-            log.debug("Floating IP %s not found.", fip_id)
-            return None
-
-    def list(self, limit=None, marker=None):
-        fips = [OpenStackFloatingIP(self._provider, fip)
-                for fip in self._provider.os_conn.network.ips(
-                    floating_network_id=self.gateway.id
-                )]
-        return ClientPagedResultList(self._provider, fips,
-                                     limit=limit, marker=marker)
-
-    def create(self):
-        return OpenStackFloatingIP(
-            self._provider, self._provider.os_conn.network.create_ip(
-                floating_network_id=self.gateway.id))
-
-
 class OpenStackFloatingIP(BaseFloatingIP):
 
     def __init__(self, provider, floating_ip):
@@ -1001,17 +926,18 @@ class OpenStackFloatingIP(BaseFloatingIP):
     def in_use(self):
         return bool(self._ip.port_id)
 
-    def delete(self):
-        self._ip.delete(self._provider.os_conn.session)
-
     def refresh(self):
         net = self._provider.networking.networks.get(
             self._ip.floating_network_id)
-        gw = net.gateways.get_or_create_inet_gateway()
+        gw = net.gateways.get_or_create()
         fip = gw.floating_ips.get(self.id)
         # pylint:disable=protected-access
         self._ip = fip._ip
 
+    @property
+    def _gateway_id(self):
+        return self._ip.floating_network_id
+
 
 class OpenStackRouter(BaseRouter):
 
@@ -1109,7 +1035,7 @@ class OpenStackInternetGateway(BaseInternetGateway):
             # pylint:disable=protected-access
             gateway_net = gateway_net._network
         self._gateway_net = gateway_net
-        self._fips_container = OpenStackFloatingIPContainer(provider, self)
+        self._fips_container = OpenStackFloatingIPSubService(provider, self)
 
     @property
     def id(self):
@@ -1138,10 +1064,6 @@ class OpenStackInternetGateway(BaseInternetGateway):
         return self.GATEWAY_STATE_MAP.get(
             self._gateway_net.state, GatewayState.UNKNOWN)
 
-    def delete(self):
-        """Do nothing on openstack"""
-        pass
-
     @property
     def floating_ips(self):
         return self._fips_container
@@ -1158,7 +1080,7 @@ class OpenStackVMFirewall(BaseVMFirewall):
 
     def __init__(self, provider, vm_firewall):
         super(OpenStackVMFirewall, self).__init__(provider, vm_firewall)
-        self._rule_svc = OpenStackVMFirewallRuleContainer(provider, self)
+        self._rule_svc = OpenStackVMFirewallRuleSubService(provider, self)
 
     @property
     def network_id(self):
@@ -1231,55 +1153,6 @@ class OpenStackVMFirewall(BaseVMFirewall):
         return js
 
 
-class OpenStackVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
-
-    def __init__(self, provider, firewall):
-        super(OpenStackVMFirewallRuleContainer, self).__init__(
-            provider, firewall)
-
-    def list(self, limit=None, marker=None):
-        # pylint:disable=protected-access
-        rules = [OpenStackVMFirewallRule(self.firewall, r)
-                 for r in self.firewall._vm_firewall.security_group_rules]
-        return ClientPagedResultList(self._provider, rules,
-                                     limit=limit, marker=marker)
-
-    def create(self,  direction, protocol=None, from_port=None,
-               to_port=None, cidr=None, src_dest_fw=None):
-        src_dest_fw_id = (src_dest_fw.id if isinstance(src_dest_fw,
-                                                       OpenStackVMFirewall)
-                          else src_dest_fw)
-
-        try:
-            if direction == TrafficDirection.INBOUND:
-                os_direction = 'ingress'
-            elif direction == TrafficDirection.OUTBOUND:
-                os_direction = 'egress'
-            else:
-                raise InvalidValueException("direction", direction)
-            # pylint:disable=protected-access
-            rule = self._provider.os_conn.network.create_security_group_rule(
-                security_group_id=self.firewall.id,
-                direction=os_direction,
-                port_range_max=to_port,
-                port_range_min=from_port,
-                protocol=protocol,
-                remote_ip_prefix=cidr,
-                remote_group_id=src_dest_fw_id)
-            self.firewall.refresh()
-            return OpenStackVMFirewallRule(self.firewall, rule.to_dict())
-        except HttpException as e:
-            self.firewall.refresh()
-            # 409=Conflict, raised for duplicate rule
-            if e.status_code == 409:
-                existing = self.find(direction=direction, protocol=protocol,
-                                     from_port=from_port, to_port=to_port,
-                                     cidr=cidr, src_dest_fw_id=src_dest_fw_id)
-                return existing[0]
-            else:
-                raise e
-
-
 class OpenStackVMFirewallRule(BaseVMFirewallRule):
 
     def __init__(self, parent_fw, rule):
@@ -1329,10 +1202,6 @@ class OpenStackVMFirewallRule(BaseVMFirewallRule):
             return self._provider.security.vm_firewalls.get(fw_id)
         return None
 
-    def delete(self):
-        self._provider.os_conn.network.delete_security_group_rule(self.id)
-        self.firewall.refresh()
-
 
 class OpenStackBucketObject(BaseBucketObject):
 
@@ -1453,7 +1322,8 @@ class OpenStackBucket(BaseBucket):
     def __init__(self, provider, bucket):
         super(OpenStackBucket, self).__init__(provider)
         self._bucket = bucket
-        self._object_container = OpenStackBucketContainer(provider, self)
+        self._object_container = OpenStackBucketObjectSubService(provider,
+                                                                 self)
 
     @property
     def id(self):
@@ -1466,9 +1336,3 @@ class OpenStackBucket(BaseBucket):
     @property
     def objects(self):
         return self._object_container
-
-
-class OpenStackBucketContainer(BaseBucketContainer):
-
-    def __init__(self, provider, bucket):
-        super(OpenStackBucketContainer, self).__init__(provider, bucket)

+ 203 - 27
cloudbridge/cloud/providers/openstack/services.py

@@ -10,6 +10,7 @@ from neutronclient.common.exceptions import PortNotFoundClient
 
 from novaclient.exceptions import NotFound as NovaNotFound
 
+from openstack.exceptions import HttpException
 from openstack.exceptions import NotFoundException
 from openstack.exceptions import ResourceNotFound
 
@@ -22,6 +23,8 @@ from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketObjectService
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseComputeService
+from cloudbridge.cloud.base.services import BaseFloatingIPService
+from cloudbridge.cloud.base.services import BaseGatewayService
 from cloudbridge.cloud.base.services import BaseImageService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseKeyPairService
@@ -33,26 +36,31 @@ from cloudbridge.cloud.base.services import BaseSecurityService
 from cloudbridge.cloud.base.services import BaseSnapshotService
 from cloudbridge.cloud.base.services import BaseStorageService
 from cloudbridge.cloud.base.services import BaseSubnetService
+from cloudbridge.cloud.base.services import BaseVMFirewallRuleService
 from cloudbridge.cloud.base.services import BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.interfaces.exceptions \
     import DuplicateResourceException
 from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
+from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 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 TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import Volume
-from cloudbridge.cloud.providers.openstack import helpers as oshelpers
 
+from . import helpers as oshelpers
 from .resources import OpenStackBucket
 from .resources import OpenStackBucketObject
+from .resources import OpenStackFloatingIP
 from .resources import OpenStackInstance
+from .resources import OpenStackInternetGateway
 from .resources import OpenStackKeyPair
 from .resources import OpenStackMachineImage
 from .resources import OpenStackNetwork
@@ -61,6 +69,7 @@ from .resources import OpenStackRouter
 from .resources import OpenStackSnapshot
 from .resources import OpenStackSubnet
 from .resources import OpenStackVMFirewall
+from .resources import OpenStackVMFirewallRule
 from .resources import OpenStackVMType
 from .resources import OpenStackVolume
 
@@ -75,27 +84,20 @@ class OpenStackSecurityService(BaseSecurityService):
         # Initialize provider services
         self._key_pairs = OpenStackKeyPairService(provider)
         self._vm_firewalls = OpenStackVMFirewallService(provider)
+        self._vm_firewall_rule_svc = OpenStackVMFirewallRuleService(provider)
 
     @property
     def key_pairs(self):
-        """
-        Provides access to key pairs for this provider.
-
-        :rtype: ``object`` of :class:`.KeyPairService`
-        :return: a KeyPairService object
-        """
         return self._key_pairs
 
     @property
     def vm_firewalls(self):
-        """
-        Provides access to VM firewalls for this provider.
-
-        :rtype: ``object`` of :class:`.VMFirewallService`
-        :return: a VMFirewallService object
-        """
         return self._vm_firewalls
 
+    @property
+    def _vm_firewall_rules(self):
+        return self._vm_firewall_rule_svc
+
     def get_or_create_ec2_credentials(self):
         """
         A provider specific method than returns the ec2 credentials for the
@@ -201,8 +203,9 @@ class OpenStackKeyPairService(BaseKeyPairService):
 
     @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)
+    def delete(self, key_pair):
+        keypair = (key_pair if isinstance(key_pair, OpenStackKeyPair)
+                   else self.get(key_pair))
         if keypair:
             # pylint:disable=protected-access
             keypair._key_pair.delete()
@@ -259,13 +262,75 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
 
     @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)
+    def delete(self, vm_firewall):
+        fw = (vm_firewall if isinstance(vm_firewall, OpenStackVMFirewall)
+              else self.get(vm_firewall))
         if fw:
             # pylint:disable=protected-access
             fw._vm_firewall.delete(self.provider.os_conn.session)
 
 
+class OpenStackVMFirewallRuleService(BaseVMFirewallRuleService):
+
+    def __init__(self, provider):
+        super(OpenStackVMFirewallRuleService, self).__init__(provider)
+
+    @dispatch(event="provider.security.vm_firewall_rules.list",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def list(self, firewall, limit=None, marker=None):
+        # pylint:disable=protected-access
+        rules = [OpenStackVMFirewallRule(firewall, r)
+                 for r in firewall._vm_firewall.security_group_rules]
+        return ClientPagedResultList(self.provider, rules,
+                                     limit=limit, marker=marker)
+
+    @dispatch(event="provider.security.vm_firewall_rules.create",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def create(self, firewall, direction, protocol=None, from_port=None,
+               to_port=None, cidr=None, src_dest_fw=None):
+        src_dest_fw_id = (src_dest_fw.id if isinstance(src_dest_fw,
+                                                       OpenStackVMFirewall)
+                          else src_dest_fw)
+
+        try:
+            if direction == TrafficDirection.INBOUND:
+                os_direction = 'ingress'
+            elif direction == TrafficDirection.OUTBOUND:
+                os_direction = 'egress'
+            else:
+                raise InvalidValueException("direction", direction)
+            # pylint:disable=protected-access
+            rule = self.provider.os_conn.network.create_security_group_rule(
+                security_group_id=firewall.id,
+                direction=os_direction,
+                port_range_max=to_port,
+                port_range_min=from_port,
+                protocol=protocol,
+                remote_ip_prefix=cidr,
+                remote_group_id=src_dest_fw_id)
+            firewall.refresh()
+            return OpenStackVMFirewallRule(firewall, rule.to_dict())
+        except HttpException as e:
+            firewall.refresh()
+            # 409=Conflict, raised for duplicate rule
+            if e.status_code == 409:
+                existing = self.find(firewall, direction=direction,
+                                     protocol=protocol, from_port=from_port,
+                                     to_port=to_port, cidr=cidr,
+                                     src_dest_fw_id=src_dest_fw_id)
+                return existing[0]
+            else:
+                raise e
+
+    @dispatch(event="provider.security.vm_firewall_rules.delete",
+              priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
+    def delete(self, firewall, rule):
+        rule_id = (rule.id if isinstance(rule, OpenStackVMFirewallRule)
+                   else rule)
+        self.provider.os_conn.network.delete_security_group_rule(rule_id)
+        firewall.refresh()
+
+
 class OpenStackStorageService(BaseStorageService):
 
     def __init__(self, provider):
@@ -290,7 +355,7 @@ class OpenStackStorageService(BaseStorageService):
         return self._bucket_svc
 
     @property
-    def bucket_objects(self):
+    def _bucket_objects(self):
         return self._bucket_obj_svc
 
 
@@ -357,8 +422,9 @@ class OpenStackVolumeService(BaseVolumeService):
 
     @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)
+    def delete(self, volume):
+        volume = (volume if isinstance(volume, OpenStackVolume)
+                  else self.get(volume))
         if volume:
             # pylint:disable=protected-access
             volume._volume.delete()
@@ -428,8 +494,9 @@ class OpenStackSnapshotService(BaseSnapshotService):
 
     @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)
+    def delete(self, snapshot):
+        s = (snapshot if isinstance(snapshot, OpenStackSnapshot) else
+             self.get(snapshot))
         if s:
             # pylint:disable=protected-access
             s._snapshot.delete()
@@ -811,8 +878,9 @@ class OpenStackInstanceService(BaseInstanceService):
 
     @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)
+    def delete(self, instance):
+        ins = (instance if isinstance(instance, OpenStackInstance) else
+               self.get(instance))
         if ins:
             # pylint:disable=protected-access
             os_instance = ins._os_instance
@@ -890,6 +958,8 @@ class OpenStackNetworkingService(BaseNetworkingService):
         self._network_service = OpenStackNetworkService(self.provider)
         self._subnet_service = OpenStackSubnetService(self.provider)
         self._router_service = OpenStackRouterService(self.provider)
+        self._gateway_service = OpenStackGatewayService(self.provider)
+        self._floating_ip_service = OpenStackFloatingIPService(self.provider)
 
     @property
     def networks(self):
@@ -903,6 +973,14 @@ class OpenStackNetworkingService(BaseNetworkingService):
     def routers(self):
         return self._router_service
 
+    @property
+    def _gateways(self):
+        return self._gateway_service
+
+    @property
+    def _floating_ips(self):
+        return self._floating_ip_service
+
 
 class OpenStackNetworkService(BaseNetworkService):
 
@@ -955,8 +1033,9 @@ class OpenStackNetworkService(BaseNetworkService):
 
     @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)
+    def delete(self, network):
+        network = (network if isinstance(network, OpenStackNetwork) else
+                   self.get(network))
         if not network:
             return
         if not network.external and network.id in str(
@@ -1036,7 +1115,7 @@ class OpenStackSubnetService(BaseSubnetService):
             router = self.provider.networking.routers.get_or_create_default(
                 net)
             router.attach_subnet(sn)
-            gateway = net.gateways.get_or_create_inet_gateway()
+            gateway = net.gateways.get_or_create()
             router.attach_gateway(gateway)
             return sn
         except NeutronClientException:
@@ -1083,3 +1162,100 @@ class OpenStackRouterService(BaseRouterService):
     def delete(self, router):
         r_id = router.id if isinstance(router, OpenStackRouter) else router
         self.provider.os_conn.delete_router(r_id)
+
+
+class OpenStackGatewayService(BaseGatewayService):
+    """For OpenStack, an internet gateway is a just an 'external' network."""
+
+    def __init__(self, provider):
+        super(OpenStackGatewayService, self).__init__(provider)
+
+    def _check_fip_connectivity(self, network, external_net):
+        # Due to current limitations in OpenStack:
+        # https://bugs.launchpad.net/neutron/+bug/1743480, it's not
+        # possible to differentiate between floating ip networks and provider
+        # external networks. Therefore, we systematically step through
+        # all available networks and perform an assignment test to infer valid
+        # floating ip nets.
+        dummy_router = self._provider.networking.routers.create(
+            label='cb-conn-test-router', network=network)
+        with cb_helpers.cleanup_action(lambda: dummy_router.delete()):
+            try:
+                dummy_router.attach_gateway(external_net)
+                return True
+            except Exception:
+                return False
+
+    @dispatch(event="provider.networking.gateways.get_or_create",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def get_or_create(self, network):
+        """For OS, inet gtw is any net that has `external` property set."""
+        external_nets = (n for n in self._provider.networking.networks
+                         if n.external)
+        for net in external_nets:
+            if self._check_fip_connectivity(network, net):
+                return OpenStackInternetGateway(self._provider, net)
+        return None
+
+    @dispatch(event="provider.networking.gateways.delete",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def delete(self, network, gateway):
+        pass
+
+    @dispatch(event="provider.networking.gateways.list",
+              priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
+    def list(self, network, limit=None, marker=None):
+        log.debug("OpenStack listing of all current internet gateways")
+        igl = [OpenStackInternetGateway(self._provider, n)
+               for n in self._provider.networking.networks
+               if n.external and self._check_fip_connectivity(network, n)]
+        return ClientPagedResultList(self._provider, igl, limit=limit,
+                                     marker=marker)
+
+
+class OpenStackFloatingIPService(BaseFloatingIPService):
+
+    def __init__(self, provider):
+        super(OpenStackFloatingIPService, self).__init__(provider)
+
+    @dispatch(event="provider.networking.floating_ips.get",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def get(self, gateway, fip_id):
+        try:
+            return OpenStackFloatingIP(
+                self.provider,
+                self.provider.os_conn.network.get_ip(fip_id))
+        except (ResourceNotFound, NotFoundException):
+            log.debug("Floating IP %s not found.", fip_id)
+            return None
+
+    @dispatch(event="provider.networking.floating_ips.list",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def list(self, gateway, limit=None, marker=None):
+        fips = [OpenStackFloatingIP(self.provider, fip)
+                for fip in self.provider.os_conn.network.ips(
+                    floating_network_id=gateway.id
+                )]
+        return ClientPagedResultList(self.provider, fips,
+                                     limit=limit, marker=marker)
+
+    @dispatch(event="provider.networking.floating_ips.create",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def create(self, gateway):
+        return OpenStackFloatingIP(
+            self.provider, self.provider.os_conn.network.create_ip(
+                floating_network_id=gateway.id))
+
+    @dispatch(event="provider.networking.floating_ips.delete",
+              priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
+    def delete(self, gateway, fip):
+        if isinstance(fip, OpenStackFloatingIP):
+            # pylint:disable=protected-access
+            os_ip = fip._ip
+        else:
+            try:
+                os_ip = self.provider.os_conn.network.get_ip(fip)
+            except (ResourceNotFound, NotFoundException):
+                log.debug("Floating IP %s not found.", fip)
+                return True
+        os_ip.delete(self._provider.os_conn.session)

+ 35 - 0
cloudbridge/cloud/providers/openstack/subservices.py

@@ -0,0 +1,35 @@
+import logging
+
+from cloudbridge.cloud.base.subservices import BaseBucketObjectSubService
+from cloudbridge.cloud.base.subservices import BaseFloatingIPSubService
+from cloudbridge.cloud.base.subservices import BaseGatewaySubService
+from cloudbridge.cloud.base.subservices import \
+    BaseVMFirewallRuleSubService
+
+
+log = logging.getLogger(__name__)
+
+
+class OpenStackBucketObjectSubService(BaseBucketObjectSubService):
+
+    def __init__(self, provider, bucket):
+        super(OpenStackBucketObjectSubService, self).__init__(provider, bucket)
+
+
+class OpenStackGatewaySubService(BaseGatewaySubService):
+
+    def __init__(self, provider, network):
+        super(OpenStackGatewaySubService, self).__init__(provider, network)
+
+
+class OpenStackFloatingIPSubService(BaseFloatingIPSubService):
+
+    def __init__(self, provider, gateway):
+        super(OpenStackFloatingIPSubService, self).__init__(provider, gateway)
+
+
+class OpenStackVMFirewallRuleSubService(BaseVMFirewallRuleSubService):
+
+    def __init__(self, provider, firewall):
+        super(OpenStackVMFirewallRuleSubService, self).__init__(
+            provider, firewall)

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

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

+ 2 - 2
docs/getting_started.rst

@@ -135,7 +135,7 @@ attaching an internet gateway to the subnet via a router.
         cidr_block='10.0.0.0/28', label='cb-subnet', zone=zone)
     router = provider.networking.routers.create(network=net, label='cb-router')
     router.attach_subnet(sn)
-    gateway = net.gateways.get_or_create_inet_gateway()
+    gateway = net.gateways.get_or_create()
     router.attach_gateway(gateway)
 
 
@@ -250,7 +250,7 @@ their provider mappings, see :doc:`topics/resource_types_and_mapping`.
     router = router_list[0]
 
     # Gateway
-    gateway = net.gateways.get_or_create_inet_gateway()
+    gateway = net.gateways.get_or_create()
 
     # Firewall
     fw = provider.security.vm_firewalls.get('firewall ID')

+ 1 - 1
docs/topics/aws_mapping.rst

@@ -158,7 +158,7 @@ take a name parameter, and the `name` property is set automatically to a
 default value. Note that since this value is stored in the tag with key Name,
 the AWS dashboard does allow for its modification, although that is not
 encouraged as the default name is expected for the
-`get_or_create_inet_gateway` method.
+`get_or_create` method.
 
 Finally, Firewall Rules in AWS differ from traditional unlabeled resources
 by the fact that they do not take a `name` parameter at creation, and the

+ 1 - 1
docs/topics/launch.rst

@@ -68,7 +68,7 @@ that subnet.
     # make sure subnet has internet access
     router = self.provider.networking.routers.create(label='my-router', network=net)
     router.attach_subnet(sn)
-    gateway = net.gateways.get_or_create_inet_gateway()
+    gateway = net.gateways.get_or_create()
     router.attach_gateway(gateway)
 
     inst = provider.compute.instances.create(

+ 2 - 2
docs/topics/networking.rst

@@ -77,7 +77,7 @@ subnet (``/28``).
     sn = net.create_subnet(label='my-subnet', cidr_block='10.0.0.0/28', zone=zone)
     router = provider.networking.routers.create(label='my-router', network=net)
     router.attach_subnet(sn)
-    gateway = net.gateways.get_or_create_inet_gateway()
+    gateway = net.gateways.get_or_create()
     router.attach_gateway(gateway)
 
 
@@ -95,7 +95,7 @@ The additional step that's required here is to assign a floating IP to the VM:
 
     router = provider.networking.routers.create(label='my-router', network=net)
     router.attach_subnet(sn)
-    gateway = net.gateways.get_or_create_inet_gateway()
+    gateway = net.gateways.get_or_create()
     router.attach_gateway(gateway)
 
     fip = provider.networking.floating_ips.create()

+ 1 - 1
docs/topics/object_storage.rst

@@ -66,5 +66,5 @@ Once a provider is obtained, you can access the container as usual:
 .. code-block:: python
 
     bucket = provider.storage.buckets.get(container)
-    obj = bucket.create_object('my_object.txt')
+    obj = bucket.objects.create('my_object.txt')
     obj.upload_from_file(source)

+ 2 - 1
setup.py

@@ -6,7 +6,8 @@ import ast
 import os
 import re
 
-from setuptools import find_packages, setup
+from setuptools import find_packages
+from setuptools import setup
 
 # Cannot use "from cloudbridge import get_version" because that would try to
 # import the six package which may not be installed yet.

+ 1 - 1
test/helpers/__init__.py

@@ -204,7 +204,7 @@ def get_test_gateway(provider):
     """
     sn = get_or_create_default_subnet(provider)
     net = sn.network
-    return net.gateways.get_or_create_inet_gateway()
+    return net.gateways.get_or_create()
 
 
 def cleanup_gateway(gateway):

+ 2 - 0
test/test_block_store_service.py

@@ -21,6 +21,7 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
 
     @helpers.skipIfNoService(['storage.volumes', 'storage.volumes'])
     def test_storage_services_event_pattern(self):
+        # pylint:disable=protected-access
         self.assertEqual(
             self.provider.storage.volumes._service_event_pattern,
             "provider.storage.volumes",
@@ -29,6 +30,7 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                                      "provider.storage.volumes",
                                      self.provider.storage.volumes.
                                      _service_event_pattern))
+        # pylint:disable=protected-access
         self.assertEqual(
             self.provider.storage.snapshots._service_event_pattern,
             "provider.storage.snapshots",

+ 5 - 0
test/test_cloud_helpers.py

@@ -82,21 +82,26 @@ class CloudHelpersTestCase(ProviderTestBase):
     def test_type_validation(self):
         # Make sure internal type checking implementation properly sets types.
         self.provider.config['text_type_check'] = 'test-text'
+        # pylint:disable=protected-access
         config_value = self.provider._get_config_value('text_type_check', None)
         self.assertIsInstance(config_value, six.string_types)
 
+        # pylint:disable=protected-access
         env_value = self.provider._get_config_value(
             'some_config_value', get_env('MOTO_AMIS_PATH'))
         self.assertIsInstance(env_value, six.string_types)
 
+        # pylint:disable=protected-access
         none_value = self.provider._get_config_value(
             'some_config_value', get_env('MISSING_ENV', None))
         self.assertIsNone(none_value)
 
+        # pylint:disable=protected-access
         bool_value = self.provider._get_config_value(
             'some_config_value', get_env('MISSING_ENV', True))
         self.assertIsInstance(bool_value, bool)
 
+        # pylint:disable=protected-access
         int_value = self.provider._get_config_value(
             'default_result_limit', None)
         self.assertIsInstance(int_value, int)

+ 2 - 1
test/test_compute_service.py

@@ -22,6 +22,7 @@ class CloudComputeServiceTestCase(ProviderTestBase):
 
     @helpers.skipIfNoService(['compute.instances'])
     def test_storage_services_event_pattern(self):
+        # pylint:disable=protected-access
         self.assertEqual(
             self.provider.compute.instances._service_event_pattern,
             "provider.compute.instances",
@@ -357,7 +358,7 @@ class CloudComputeServiceTestCase(ProviderTestBase):
 
             # check floating ips
             router = self.provider.networking.routers.create(label, net)
-            gateway = net.gateways.get_or_create_inet_gateway()
+            gateway = net.gateways.get_or_create()
 
             def cleanup_router(router, gateway):
                 with helpers.cleanup_action(lambda: router.delete()):

+ 4 - 1
test/test_network_service.py

@@ -20,6 +20,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                               'networking.networks',
                               'networking.routers'])
     def test_storage_services_event_pattern(self):
+        # pylint:disable=protected-access
         self.assertEqual(
             self.provider.networking.networks._service_event_pattern,
             "provider.networking.networks",
@@ -28,6 +29,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                                      "provider.networking.networks",
                                      self.provider.networking.networks.
                                      _service_event_pattern))
+        # pylint:disable=protected-access
         self.assertEqual(
             self.provider.networking.subnets._service_event_pattern,
             "provider.networking.subnets",
@@ -36,6 +38,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                                      "provider.networking.subnets",
                                      self.provider.networking.subnets.
                                      _service_event_pattern))
+        # pylint:disable=protected-access
         self.assertEqual(
             self.provider.networking.routers._service_event_pattern,
             "provider.networking.routers",
@@ -257,7 +260,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                     len(router.subnets) == 1,
                     "Subnet {0} not attached to router {1}".format(sn, router)
                 )
-            gteway = net.gateways.get_or_create_inet_gateway()
+            gteway = net.gateways.get_or_create()
             router.attach_gateway(gteway)
             # TODO: add a check for routes after that's been implemented
 

+ 7 - 5
test/test_object_store_service.py

@@ -21,8 +21,9 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
 
     _multiprocess_can_split_ = True
 
-    @helpers.skipIfNoService(['storage.bucket_objects', 'storage.buckets'])
+    @helpers.skipIfNoService(['storage._bucket_objects', 'storage.buckets'])
     def test_storage_services_event_pattern(self):
+        # pylint:disable=protected-access
         self.assertEqual(
             self.provider.storage.buckets._service_event_pattern,
             "provider.storage.buckets",
@@ -31,13 +32,14 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
                                      "provider.storage.buckets",
                                      self.provider.storage.buckets.
                                      _service_event_pattern))
+        # pylint:disable=protected-access
         self.assertEqual(
-            self.provider.storage.bucket_objects._service_event_pattern,
-            "provider.storage.bucket_objects",
+            self.provider.storage._bucket_objects._service_event_pattern,
+            "provider.storage._bucket_objects",
             "Event pattern for {} service should be '{}', "
             "but found '{}'.".format("bucket_objects",
-                                     "provider.storage.bucket_objects",
-                                     self.provider.storage.bucket_objects.
+                                     "provider.storage._bucket_objects",
+                                     self.provider.storage._bucket_objects.
                                      _service_event_pattern))
 
     @helpers.skipIfNoService(['storage.buckets'])

+ 1 - 0
test/test_region_service.py

@@ -13,6 +13,7 @@ class CloudRegionServiceTestCase(ProviderTestBase):
 
     @helpers.skipIfNoService(['compute.regions'])
     def test_storage_services_event_pattern(self):
+        # pylint:disable=protected-access
         self.assertEqual(
             self.provider.compute.regions._service_event_pattern,
             "provider.compute.regions",