Jelajahi Sumber

Merge master into GCE

Ehsan Chiniforooshan 8 tahun lalu
induk
melakukan
f873edb7f7
37 mengubah file dengan 939 tambahan dan 848 penghapusan
  1. 7 2
      .travis.yml
  2. 41 0
      cloudbridge/cloud/base/helpers.py
  3. 9 0
      cloudbridge/cloud/base/resources.py
  4. 0 8
      cloudbridge/cloud/base/services.py
  5. 9 0
      cloudbridge/cloud/interfaces/exceptions.py
  6. 60 4
      cloudbridge/cloud/interfaces/resources.py
  7. 0 58
      cloudbridge/cloud/interfaces/services.py
  8. 65 4
      cloudbridge/cloud/providers/aws/resources.py
  9. 14 55
      cloudbridge/cloud/providers/aws/services.py
  10. 276 129
      cloudbridge/cloud/providers/azure/azure_client.py
  11. 4 1
      cloudbridge/cloud/providers/azure/helpers.py
  12. 153 283
      cloudbridge/cloud/providers/azure/resources.py
  13. 149 211
      cloudbridge/cloud/providers/azure/services.py
  14. 1 1
      cloudbridge/cloud/providers/openstack/helpers.py
  15. 66 4
      cloudbridge/cloud/providers/openstack/resources.py
  16. 16 57
      cloudbridge/cloud/providers/openstack/services.py
  17. 1 1
      docs/getting_started.rst
  18. 1 1
      docs/topics/launch.rst
  19. 2 2
      docs/topics/networking.rst
  20. 1 0
      requirements.txt
  21. 16 0
      setup.cfg
  22. 5 4
      setup.py
  23. 9 6
      test/helpers/__init__.py
  24. 2 9
      test/helpers/standard_interface_tests.py
  25. 2 0
      test/test_block_store_service.py
  26. 2 0
      test/test_cloud_factory.py
  27. 2 0
      test/test_cloud_helpers.py
  28. 3 2
      test/test_compute_service.py
  29. 2 0
      test/test_image_service.py
  30. 2 0
      test/test_interface.py
  31. 5 4
      test/test_network_service.py
  32. 2 0
      test/test_object_life_cycle.py
  33. 2 0
      test/test_object_store_service.py
  34. 2 0
      test/test_region_service.py
  35. 4 1
      test/test_security_service.py
  36. 2 0
      test/test_vm_types_service.py
  37. 2 1
      tox.ini

+ 7 - 2
.travis.yml

@@ -1,5 +1,9 @@
 dist: trusty
 language: python
+cache:
+  directories:
+    - $HOME/.cache/pip
+    - $TRAVIS_BUILD_DIR/.tox
 os:
   - linux
 #  - osx
@@ -65,8 +69,8 @@ install:
     - pip install coveralls
     - pip install codecov
 script:
-    - travis_wait 110 tox -e $TOX_ENV
-after_success:
+    - tox -e $TOX_ENV
+after_script:
     - |
       case "$TRAVIS_EVENT_TYPE" in
         push|pull_request)
@@ -83,6 +87,7 @@ after_success:
            ;;
         *)
            echo "Build triggered through API or CRON job. Running regardless of changes"
+           coveralls & codecov & wait
            ;;
       esac
 

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

@@ -1,7 +1,13 @@
+import sys
+import traceback
+from contextlib import contextmanager
+
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import serialization as crypt_serialization
 from cryptography.hazmat.primitives.asymmetric import rsa
 
+from six import reraise
+
 
 def generate_key_pair():
     """
@@ -52,3 +58,38 @@ def generic_find(filter_names, kwargs, objs):
             % (kwargs, filter_names))
 
     return matches
+
+
+@contextmanager
+def cleanup_action(cleanup_func):
+    """
+    Context manager to carry out a given
+    cleanup action after carrying out a set
+    of tasks, or when an exception occurs.
+    If any errors occur during the cleanup
+    action, those are ignored, and the original
+    traceback is preserved.
+
+    :params func: This function is called if
+    an exception occurs or at the end of the
+    context block. If any exceptions raised
+        by func are ignored.
+    Usage:
+        with cleanup_action(lambda e: print("Oops!")):
+            do_something()
+    """
+    try:
+        yield
+    except Exception:
+        ex_class, ex_val, ex_traceback = sys.exc_info()
+        try:
+            cleanup_func()
+        except Exception as e:
+            print("Error during exception cleanup: {0}".format(e))
+            traceback.print_exc()
+        reraise(ex_class, ex_val, ex_traceback)
+    try:
+        cleanup_func()
+    except Exception as e:
+        print("Error during cleanup: {0}".format(e))
+        traceback.print_exc()

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

@@ -22,6 +22,7 @@ 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
@@ -916,6 +917,13 @@ class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
         return self.__provider
 
 
+class BaseGatewayContainer(GatewayContainer, BasePageableObjectMixin):
+
+    def __init__(self, provider, network):
+        self._network = network
+        self._provider = provider
+
+
 class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
 
     CB_DEFAULT_NETWORK_NAME = os.environ.get('CB_DEFAULT_NETWORK_NAME',
@@ -1067,6 +1075,7 @@ class BaseInternetGateway(BaseCloudResource, BaseObjectLifeCycleMixin,
 
     def __init__(self, provider):
         super(BaseInternetGateway, self).__init__(provider)
+        self.__provider = provider
 
     def __repr__(self):
         return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__, self.id,

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

@@ -8,7 +8,6 @@ from cloudbridge.cloud.interfaces.resources import Router
 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 GatewayService
 from cloudbridge.cloud.interfaces.services import ImageService
 from cloudbridge.cloud.interfaces.services import InstanceService
 from cloudbridge.cloud.interfaces.services import KeyPairService
@@ -210,10 +209,3 @@ class BaseRouterService(
             if router:
                 log.info("Router %s successful deleted.", router)
                 router.delete()
-
-
-class BaseGatewayService(
-        GatewayService, BaseCloudService):
-
-    def __init__(self, provider):
-        super(BaseGatewayService, self).__init__(provider)

+ 9 - 0
cloudbridge/cloud/interfaces/exceptions.py

@@ -69,3 +69,12 @@ class InvalidValueException(CloudBridgeBaseException):
         super(InvalidValueException, self).__init__(
             "Param %s has been given an unrecognised value %s" %
             (param, value))
+
+
+class DuplicateResourceException(CloudBridgeBaseException):
+    """
+    Marker interface for any attempt to create a CloudBridge resource that
+    already exists. For example, creating a KeyPair with the same name will
+    result in a DuplicateResourceException.
+    """
+    pass

+ 60 - 4
cloudbridge/cloud/interfaces/resources.py

@@ -583,11 +583,12 @@ class Instance(ObjectLifeCycleMixin, CloudResource):
         """
         Add a public IP address to this instance.
 
-        :type floating_ip: :class:``.FloatingIP``
+        :type floating_ip: :class:``.FloatingIP`` or floating IP ID
         :param floating_ip: The FloatingIP object to associate with the
                             instance. Note that is not the actual public IP
                             address but the CloudBridge object encapsulating
-                            the IP.
+                            the IP or the respective provider ID that
+                            identifies the address.
         """
         pass
 
@@ -596,11 +597,12 @@ class Instance(ObjectLifeCycleMixin, CloudResource):
         """
         Remove a public IP address from this instance.
 
-        :type floating_ip: :class:``.FloatingIP``
+        :type floating_ip: :class:``.FloatingIP`` or floating IP ID
         :param floating_ip: The FloatingIP object to remove from the
                             instance. Note that is not the actual public IP
                             address but the CloudBridge object encapsulating
-                            the IP.
+                            the IP or the respective provider ID that
+                            identifies the address.
         """
         pass
 
@@ -888,6 +890,16 @@ class Network(ObjectLifeCycleMixin, CloudResource):
         """
         pass
 
+    @abstractproperty
+    def gateways(self):
+        """
+        Provides access to the internet gateways attached to this network.
+
+        :rtype: :class:`.GatewayContainer`
+        :return: A GatewayContainer object
+        """
+        pass
+
 
 class SubnetState(object):
     """
@@ -1205,6 +1217,50 @@ class GatewayState(object):
     ERROR = "error"
 
 
+class GatewayContainer(PageableObjectMixin):
+    """
+    Manage internet gateway resources.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def get_or_create_inet_gateway(self, name=None):
+        """
+        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 name. This applies only if creating a gateway
+                     and if the provider supports it.
+
+        :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.

+ 0 - 58
cloudbridge/cloud/interfaces/services.py

@@ -580,17 +580,6 @@ class NetworkingService(CloudService):
         """
         pass
 
-    @abstractproperty
-    def gateways(self):
-        """
-        Provides access to all Gateway related services, such as
-        Internet Gateways.
-
-        :rtype: :class:`.GatewayService`
-        :return: a Router service object
-        """
-        pass
-
 
 class NetworkService(PageableObjectMixin, CloudService):
 
@@ -864,53 +853,6 @@ class RouterService(PageableObjectMixin, CloudService):
         pass
 
 
-class GatewayService(CloudService):
-    """
-    Manage internet gateway resources.
-    """
-    __metaclass__ = ABCMeta
-
-    @abstractmethod
-    def get_or_create_inet_gateway(self, network, name=None):
-        """
-        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: :class:`.Network` object or ``str``
-        :param network: Network object or ID to which the gateway is attached.
-
-        :type  name: ``str``
-        :param name: The gateway name. This applies only if creating a gateway
-                     and if the provider supports it.
-
-        :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 BucketService(PageableObjectMixin, CloudService):
 
     """

+ 65 - 4
cloudbridge/cloud/providers/aws/resources.py

@@ -14,6 +14,7 @@ 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
@@ -297,22 +298,32 @@ class AWSInstance(BaseInstance):
         image.refresh()
         return image
 
+    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])
+
     def add_floating_ip(self, floating_ip):
+        fip = (floating_ip if isinstance(floating_ip, AWSFloatingIP)
+               else self._get_fip(floating_ip))
         params = trim_empty_params({
             'InstanceId': self.id,
             'PublicIp': None if self._ec2_instance.vpc_id else
-            floating_ip.public_ip,
+            fip.public_ip,
             # pylint:disable=protected-access
-            'AllocationId': floating_ip._ip.allocation_id})
+            'AllocationId': fip._ip.allocation_id})
         self._provider.ec2_conn.meta.client.associate_address(**params)
         self.refresh()
 
     def remove_floating_ip(self, floating_ip):
+        fip = (floating_ip if isinstance(floating_ip, AWSFloatingIP)
+               else self._get_fip(floating_ip))
         params = trim_empty_params({
             'PublicIp': None if self._ec2_instance.vpc_id else
-            floating_ip.public_ip,
+            fip.public_ip,
             # pylint:disable=protected-access
-            'AssociationId': floating_ip._ip.association_id})
+            'AssociationId': fip._ip.association_id})
         self._provider.ec2_conn.meta.client.disassociate_address(**params)
         self.refresh()
 
@@ -879,6 +890,7 @@ class AWSNetwork(BaseNetwork):
     def __init__(self, provider, network):
         super(AWSNetwork, self).__init__(provider)
         self._vpc = network
+        self._gtw_container = AWSGatewayContainer(provider, self)
 
     @property
     def id(self):
@@ -935,6 +947,10 @@ class AWSNetwork(BaseNetwork):
             VpcIds=[self.id])
         self.refresh()
 
+    @property
+    def gateways(self):
+        return self._gtw_container
+
 
 class AWSSubnet(BaseSubnet):
 
@@ -1118,6 +1134,51 @@ 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, name=None):
+        log.debug("Get or create inet gateway %s on net %s", name,
+                  self._network)
+        if name:
+            AWSInternetGateway.assert_valid_resource_name(name)
+
+        network_id = self._network.id if isinstance(
+            self._network, AWSNetwork) else self._network
+        # Don't filter by name 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')
+        if name:
+            cb_gateway.name = 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):

+ 14 - 55
cloudbridge/cloud/providers/aws/services.py

@@ -8,7 +8,6 @@ import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseComputeService
-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
@@ -24,7 +23,7 @@ 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 InvalidConfigurationException
+    import DuplicateResourceException, InvalidConfigurationException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import PlacementZone
@@ -39,7 +38,6 @@ from .helpers import BotoEC2Service
 from .helpers import BotoS3Service
 from .resources import AWSBucket
 from .resources import AWSInstance
-from .resources import AWSInternetGateway
 from .resources import AWSKeyPair
 from .resources import AWSLaunchConfig
 from .resources import AWSMachineImage
@@ -105,10 +103,17 @@ class AWSKeyPairService(BaseKeyPairService):
         private_key = None
         if not public_key_material:
             public_key_material, private_key = cb_helpers.generate_key_pair()
-        kp = self.svc.create('import_key_pair', KeyName=name,
-                             PublicKeyMaterial=public_key_material)
-        kp.material = private_key
-        return kp
+        try:
+            kp = self.svc.create('import_key_pair', KeyName=name,
+                                 PublicKeyMaterial=public_key_material)
+            kp.material = private_key
+            return kp
+        except ClientError as e:
+            if e.response['Error']['Code'] == 'InvalidKeyPair.Duplicate':
+                raise DuplicateResourceException(
+                    'Keypair already exists with name {0}'.format(name))
+            else:
+                raise e
 
 
 class AWSVMFirewallService(BaseVMFirewallService):
@@ -321,7 +326,7 @@ class AWSBucketService(BaseBucketService):
 
     def create(self, name, location=None):
         log.debug("Creating AWS Bucket with the params "
-                  "[name: %s id: %s description: %s]", name, location)
+                  "[name: %s, location: %s]", name, location)
         AWSBucket.assert_valid_resource_name(name)
         loc_constraint = location or self.provider.region_name
         # Due to an API issue in S3, specifying us-east-1 as a
@@ -406,7 +411,7 @@ class AWSInstanceService(BaseInstanceService):
                   "[name: %s image: %s type: %s subnet: %s zone: %s "
                   "key pair: %s firewalls: %s user data: %s config %s "
                   "others: %s]", name, image, vm_type, subnet, zone,
-                  key_pair, vm_firewalls, user_data, launch_config, **kwargs)
+                  key_pair, vm_firewalls, user_data, launch_config, kwargs)
         AWSInstance.assert_valid_resource_name(name)
 
         image_id = image.id if isinstance(image, MachineImage) else image
@@ -615,7 +620,6 @@ 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)
 
     @property
     def networks(self):
@@ -629,10 +633,6 @@ class AWSNetworkingService(BaseNetworkingService):
     def routers(self):
         return self._router_service
 
-    @property
-    def gateways(self):
-        return self._gateway_service
-
 
 class AWSNetworkService(BaseNetworkService):
 
@@ -806,44 +806,3 @@ class AWSRouterService(BaseRouterService):
         if name:
             cb_router.name = name
         return cb_router
-
-
-class AWSGatewayService(BaseGatewayService):
-
-    def __init__(self, provider):
-        super(AWSGatewayService, self).__init__(provider)
-        self.svc = BotoEC2Service(provider=self.provider,
-                                  cb_resource=AWSInternetGateway,
-                                  boto_collection_name='internet_gateways')
-
-    def get_or_create_inet_gateway(self, network, name=None):
-        log.debug("Get or create inet gateway %s on net %s", name, network)
-        if name:
-            AWSInternetGateway.assert_valid_resource_name(name)
-
-        network_id = network.id if isinstance(network, AWSNetwork) else network
-        # Don't filter by name 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')
-        if name:
-            cb_gateway.name = 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.")
-        return self.svc.list(limit=None, marker=None)

+ 276 - 129
cloudbridge/cloud/providers/azure/azure_client.py

@@ -12,8 +12,59 @@ from azure.storage.blob import BlobPermissions
 from azure.storage.blob import BlockBlobService
 from azure.storage.table import TableService
 
+from . import helpers as azure_helpers
+
 log = logging.getLogger(__name__)
 
+IMAGE_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
+                    '{resourceGroupName}/providers/Microsoft.Compute/' \
+                    'images/{imageName}'
+NETWORK_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
+                     '{resourceGroupName}/providers/Microsoft.Network' \
+                     '/virtualNetworks/{virtualNetworkName}'
+NETWORK_INTERFACE_RESOURCE_ID = '/subscriptions/{subscriptionId}/' \
+                                'resourceGroups/{resourceGroupName}' \
+                                '/providers/Microsoft.Network/' \
+                                'networkInterfaces/{networkInterfaceName}'
+PUBLIC_IP_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups' \
+                        '/{resourceGroupName}/providers/Microsoft.Network' \
+                        '/publicIPAddresses/{publicIpAddressName}'
+SNAPSHOT_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
+                       '{resourceGroupName}/providers/Microsoft.Compute/' \
+                       'snapshots/{snapshotName}'
+SUBNET_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
+                     '{resourceGroupName}/providers/Microsoft.Network' \
+                     '/virtualNetworks/{virtualNetworkName}/subnets' \
+                     '/{subnetName}'
+VM_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
+                       '{resourceGroupName}/providers/Microsoft.Compute/' \
+                       'virtualMachines/{vmName}'
+VM_FIREWALL_RESOURCE_ID = '/subscriptions/{subscriptionId}/' \
+                             'resourceGroups/{resourceGroupName}/' \
+                             'providers/Microsoft.Network/' \
+                             'networkSecurityGroups/' \
+                             '{networkSecurityGroupName}'
+VM_FIREWALL_RULE_RESOURCE_ID = '/subscriptions/{subscriptionId}/' \
+                             'resourceGroups/{resourceGroupName}/' \
+                             'providers/Microsoft.Network/' \
+                             'networkSecurityGroups/' \
+                             '{networkSecurityGroupName}/' \
+                             'securityRules/{securityRuleName}'
+VOLUME_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
+                     '{resourceGroupName}/providers/Microsoft.Compute/' \
+                     'disks/{diskName}'
+
+IMAGE_NAME = 'imageName'
+NETWORK_NAME = 'virtualNetworkName'
+NETWORK_INTERFACE_NAME = 'networkInterfaceName'
+PUBLIC_IP_NAME = 'publicIpAddressName'
+SNAPSHOT_NAME = 'snapshotName'
+SUBNET_NAME = 'subnetName'
+VM_NAME = 'vmName'
+VM_FIREWALL_NAME = 'networkSecurityGroupName'
+VM_FIREWALL_RULE_NAME = 'securityRuleName'
+VOLUME_NAME = 'diskName'
+
 
 class AzureClient(object):
     """
@@ -147,32 +198,45 @@ class AzureClient(object):
             create_or_update(self.resource_group, name,
                              parameters).result()
 
-    def update_vm_firewall_tags(self, name, tags):
+    def update_vm_firewall_tags(self, fw_id, tags):
+        url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
+                                             fw_id)
+        name = url_params.get(VM_FIREWALL_NAME)
         return self.network_management_client.network_security_groups. \
             create_or_update(self.resource_group, name,
                              {'tags': tags,
                               'location': self.region_name}).result()
 
-    def create_vm_firewall_rule(self, vm_firewall,
+    def get_vm_firewall(self, fw_id):
+        url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
+                                             fw_id)
+        fw_name = url_params.get(VM_FIREWALL_NAME)
+        return self.network_management_client.network_security_groups. \
+            get(self.resource_group, fw_name)
+
+    def delete_vm_firewall(self, fw_id):
+        url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
+                                             fw_id)
+        name = url_params.get(VM_FIREWALL_NAME)
+        self.network_management_client \
+            .network_security_groups.delete(self.resource_group, name).wait()
+
+    def create_vm_firewall_rule(self, fw_id,
                                 rule_name, parameters):
+        url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
+                                             fw_id)
+        vm_firewall_name = url_params.get(VM_FIREWALL_NAME)
         return self.network_management_client.security_rules. \
-            create_or_update(self.resource_group, vm_firewall,
+            create_or_update(self.resource_group, vm_firewall_name,
                              rule_name, parameters).result()
 
-    def delete_vm_firewall_rule(self, name, vm_firewall):
+    def delete_vm_firewall_rule(self, fw_rule_id, vm_firewall):
+        url_params = azure_helpers.parse_url(VM_FIREWALL_RULE_RESOURCE_ID,
+                                             fw_rule_id)
+        name = url_params.get(VM_FIREWALL_RULE_NAME)
         return self.network_management_client.security_rules. \
             delete(self.resource_group, vm_firewall, name).result()
 
-    def get_vm_firewall(self, name):
-        return self.network_management_client.network_security_groups. \
-            get(self.resource_group, name)
-
-    def delete_vm_firewall(self, name):
-        delete_async = self.network_management_client \
-            .network_security_groups. \
-            delete(self.resource_group, name)
-        delete_async.wait()
-
     def list_containers(self, prefix=None):
         return self.blob_service.list_containers(prefix=prefix)
 
@@ -222,23 +286,36 @@ class AzureClient(object):
         return self.compute_client.disks.create_or_update(
             self.resource_group,
             disk_name,
-            params,
-            raw=True
-        )
+            params
+        ).result()
 
     def create_snapshot_disk(self, disk_name, params):
         return self.compute_client.disks.create_or_update(
             self.resource_group,
             disk_name,
-            params,
-            raw=True
-        )
+            params
+        ).result()
 
-    def list_snapshots(self):
-        return self.compute_client.snapshots. \
+    def get_disk(self, disk_id):
+        url_params = azure_helpers.parse_url(VOLUME_RESOURCE_ID,
+                                             disk_id)
+        disk_name = url_params.get(VOLUME_NAME)
+        return self.compute_client.disks.get(self.resource_group, disk_name)
+
+    def list_disks(self):
+        return self.compute_client.disks. \
             list_by_resource_group(self.resource_group)
 
-    def update_disk_tags(self, disk_name, tags):
+    def delete_disk(self, disk_id):
+        url_params = azure_helpers.parse_url(VOLUME_RESOURCE_ID,
+                                             disk_id)
+        disk_name = url_params.get(VOLUME_NAME)
+        self.compute_client.disks.delete(self.resource_group, disk_name).wait()
+
+    def update_disk_tags(self, disk_id, tags):
+        url_params = azure_helpers.parse_url(VOLUME_RESOURCE_ID,
+                                             disk_id)
+        disk_name = url_params.get(VOLUME_NAME)
         return self.compute_client.disks.update(
             self.resource_group,
             disk_name,
@@ -246,59 +323,14 @@ class AzureClient(object):
             raw=True
         )
 
-    def get_disk(self, disk_name):
-        return self.compute_client.disks. \
-            get(self.resource_group, disk_name)
-
-    def list_networks(self):
-        return self.network_management_client.virtual_networks.list(
-            self.resource_group)
-
-    def get_network(self, network_name):
-        return self.network_management_client.virtual_networks.get(
-            self.resource_group, network_name)
-
-    def create_network(self, name, params):
-        return self.network_management_client.virtual_networks. \
-            create_or_update(self.resource_group,
-                             name,
-                             parameters=params,
-                             raw=True)
-
-    def delete_network(self, network_name):
-        return self.network_management_client.virtual_networks. \
-            delete(self.resource_group, network_name).wait()
-
-    def create_floating_ip(self, public_ip_name, public_ip_parameters):
-        return self.network_management_client.public_ip_addresses. \
-            create_or_update(self.resource_group,
-                             public_ip_name,
-                             public_ip_parameters).result()
-
-    def delete_floating_ip(self, public_ip_address_name):
-        return self.network_management_client.public_ip_addresses. \
-            delete(self.resource_group,
-                   public_ip_address_name).result()
-
-    def list_floating_ips(self):
-        return self.network_management_client.public_ip_addresses.list(
-            self.resource_group)
-
-    def update_network_tags(self, network_name, tags):
-        return self.network_management_client.virtual_networks. \
-            create_or_update(self.resource_group,
-                             network_name, tags).result()
-
-    def list_disks(self):
-        return self.compute_client.disks. \
+    def list_snapshots(self):
+        return self.compute_client.snapshots. \
             list_by_resource_group(self.resource_group)
 
-    def delete_disk(self, disk_name):
-        async_deletion = self.compute_client.disks. \
-            delete(self.resource_group, disk_name)
-        async_deletion.wait()
-
-    def get_snapshot(self, snapshot_name):
+    def get_snapshot(self, snapshot_id):
+        url_params = azure_helpers.parse_url(SNAPSHOT_RESOURCE_ID,
+                                             snapshot_id)
+        snapshot_name = url_params.get(SNAPSHOT_NAME)
         return self.compute_client.snapshots.get(self.resource_group,
                                                  snapshot_name)
 
@@ -306,16 +338,20 @@ class AzureClient(object):
         return self.compute_client.snapshots.create_or_update(
             self.resource_group,
             snapshot_name,
-            params,
-            raw=True
-        )
-
-    def delete_snapshot(self, snapshot_name):
-        async_delete = self.compute_client.snapshots. \
-            delete(self.resource_group, snapshot_name)
-        async_delete.wait()
-
-    def update_snapshot_tags(self, snapshot_name, tags):
+            params
+        ).result()
+
+    def delete_snapshot(self, snapshot_id):
+        url_params = azure_helpers.parse_url(SNAPSHOT_RESOURCE_ID,
+                                             snapshot_id)
+        snapshot_name = url_params.get(SNAPSHOT_NAME)
+        self.compute_client.snapshots.delete(self.resource_group,
+                                             snapshot_name).wait()
+
+    def update_snapshot_tags(self, snapshot_id, tags):
+        url_params = azure_helpers.parse_url(SNAPSHOT_RESOURCE_ID,
+                                             snapshot_id)
+        snapshot_name = url_params.get(SNAPSHOT_NAME)
         return self.compute_client.snapshots.update(
             self.resource_group,
             snapshot_name,
@@ -326,21 +362,28 @@ class AzureClient(object):
     def create_image(self, name, params):
         return self.compute_client.images. \
             create_or_update(self.resource_group, name,
-                             params, raw=True)
+                             params).result()
 
-    def delete_image(self, name):
-        self.compute_client.images. \
-            delete(self.resource_group, name).wait()
+    def delete_image(self, image_id):
+        url_params = azure_helpers.parse_url(IMAGE_RESOURCE_ID,
+                                             image_id)
+        name = url_params.get(IMAGE_NAME)
+        self.compute_client.images.delete(self.resource_group, name).wait()
 
     def list_images(self):
         return self.compute_client.images. \
             list_by_resource_group(self.resource_group)
 
-    def get_image(self, image_name):
-        return self.compute_client.images. \
-            get(self.resource_group, image_name)
+    def get_image(self, image_id):
+        url_params = azure_helpers.parse_url(IMAGE_RESOURCE_ID,
+                                             image_id)
+        name = url_params.get(IMAGE_NAME)
+        return self.compute_client.images.get(self.resource_group, name)
 
-    def update_image_tags(self, name, tags):
+    def update_image_tags(self, image_id, tags):
+        url_params = azure_helpers.parse_url(IMAGE_RESOURCE_ID,
+                                             image_id)
+        name = url_params.get(IMAGE_NAME)
         return self.compute_client.images. \
             create_or_update(self.resource_group, name,
                              {
@@ -352,16 +395,60 @@ class AzureClient(object):
         return self.compute_client.virtual_machine_sizes. \
             list(self.region_name)
 
-    def list_subnets(self, network_name):
+    def list_networks(self):
+        return self.network_management_client.virtual_networks.list(
+            self.resource_group)
+
+    def get_network(self, network_id):
+        url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID,
+                                             network_id)
+        network_name = url_params.get(NETWORK_NAME)
+        return self.network_management_client.virtual_networks.get(
+            self.resource_group, network_name)
+
+    def create_network(self, name, params):
+        return self.network_management_client.virtual_networks. \
+            create_or_update(self.resource_group,
+                             name,
+                             parameters=params).result()
+
+    def delete_network(self, network_id):
+        url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
+        network_name = url_params.get(NETWORK_NAME)
+        return self.network_management_client.virtual_networks. \
+            delete(self.resource_group, network_name).wait()
+
+    def update_network_tags(self, network_id, tags):
+        url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
+        network_name = url_params.get(NETWORK_NAME)
+        return self.network_management_client.virtual_networks. \
+            create_or_update(self.resource_group,
+                             network_name, tags).result()
+
+    def get_network_id_for_subnet(self, subnet_id):
+        url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID, subnet_id)
+        network_id = NETWORK_RESOURCE_ID
+        for key, val in url_params.items():
+            network_id = network_id.replace("{" + key + "}", val)
+        return network_id
+
+    def list_subnets(self, network_id):
+        url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
+        network_name = url_params.get(NETWORK_NAME)
         return self.network_management_client.subnets. \
             list(self.resource_group, network_name)
 
-    def get_subnet(self, network_name, subnet_name):
+    def get_subnet(self, subnet_id):
+        url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID,
+                                             subnet_id)
+        network_name = url_params.get(NETWORK_NAME)
+        subnet_name = url_params.get(SUBNET_NAME)
         return self.network_management_client.subnets. \
             get(self.resource_group, network_name, subnet_name)
 
-    def create_subnet(self, network_name,
-                      subnet_name, params):
+    def create_subnet(self, network_id, subnet_name, params):
+        url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
+        network_name = url_params.get(NETWORK_NAME)
         result_create = self.network_management_client \
             .subnets.create_or_update(
                 self.resource_group,
@@ -373,7 +460,11 @@ class AzureClient(object):
 
         return subnet_info
 
-    def delete_subnet(self, network_name, subnet_name):
+    def delete_subnet(self, subnet_id):
+        url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID,
+                                             subnet_id)
+        network_name = url_params.get(NETWORK_NAME)
+        subnet_name = url_params.get(SUBNET_NAME)
         result_delete = self.network_management_client \
             .subnets.delete(
                 self.resource_group,
@@ -382,24 +473,54 @@ class AzureClient(object):
             )
         result_delete.wait()
 
+    def create_floating_ip(self, public_ip_name, public_ip_parameters):
+        return self.network_management_client.public_ip_addresses. \
+            create_or_update(self.resource_group,
+                             public_ip_name,
+                             public_ip_parameters).result()
+
+    def get_floating_ip(self, public_ip_id):
+        url_params = azure_helpers.parse_url(PUBLIC_IP_RESOURCE_ID,
+                                             public_ip_id)
+        public_ip_name = url_params.get(PUBLIC_IP_NAME)
+        return self.network_management_client. \
+            public_ip_addresses.get(self.resource_group, public_ip_name)
+
+    def delete_floating_ip(self, public_ip_id):
+        url_params = azure_helpers.parse_url(PUBLIC_IP_RESOURCE_ID,
+                                             public_ip_id)
+        public_ip_name = url_params.get(PUBLIC_IP_NAME)
+        self.network_management_client. \
+            public_ip_addresses.delete(self.resource_group,
+                                       public_ip_name).wait()
+
+    def list_floating_ips(self):
+        return self.network_management_client.public_ip_addresses.list(
+            self.resource_group)
+
     def list_vm(self):
         return self.compute_client.virtual_machines.list(
             self.resource_group
         )
 
-    def restart_vm(self, vm_name):
+    def restart_vm(self, vm_id):
+        url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
+                                             vm_id)
+        vm_name = url_params.get(VM_NAME)
         return self.compute_client.virtual_machines.restart(
-            self.resource_group,
-            vm_name
-        ).wait()
+            self.resource_group, vm_name).wait()
 
-    def delete_vm(self, vm_name):
+    def delete_vm(self, vm_id):
+        url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
+                                             vm_id)
+        vm_name = url_params.get(VM_NAME)
         return self.compute_client.virtual_machines.delete(
-            self.resource_group,
-            vm_name
-        ).wait()
+            self.resource_group, vm_name).wait()
 
-    def get_vm(self, vm_name):
+    def get_vm(self, vm_id):
+        url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
+                                             vm_id)
+        vm_name = url_params.get(VM_NAME)
         return self.compute_client.virtual_machines.get(
             self.resource_group,
             vm_name,
@@ -409,42 +530,66 @@ class AzureClient(object):
     def create_vm(self, vm_name, params):
         return self.compute_client.virtual_machines. \
             create_or_update(self.resource_group,
-                             vm_name, params, raw=True)
+                             vm_name, params).result()
 
-    def update_vm(self, vm_name, params):
+    def update_vm(self, vm_id, params):
+        url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
+                                             vm_id)
+        vm_name = url_params.get(VM_NAME)
         return self.compute_client.virtual_machines. \
             create_or_update(self.resource_group,
                              vm_name, params, raw=True)
 
-    def deallocate_vm(self, vm_name):
+    def deallocate_vm(self, vm_id):
+        url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
+                                             vm_id)
+        vm_name = url_params.get(VM_NAME)
         self.compute_client. \
             virtual_machines.deallocate(self.resource_group,
                                         vm_name).wait()
 
-    def generalize_vm(self, vm_name):
+    def generalize_vm(self, vm_id):
+        url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
+                                             vm_id)
+        vm_name = url_params.get(VM_NAME)
         self.compute_client.virtual_machines. \
             generalize(self.resource_group, vm_name)
 
-    def start_vm(self, vm_name):
+    def start_vm(self, vm_id):
+        url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
+                                             vm_id)
+        vm_name = url_params.get(VM_NAME)
         self.compute_client.virtual_machines. \
             start(self.resource_group,
                   vm_name).wait()
 
-    def update_vm_tags(self, vm_name, tags):
+    def update_vm_tags(self, vm_id, tags):
+        url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
+                                             vm_id)
+        vm_name = url_params.get(VM_NAME)
         self.compute_client.virtual_machines. \
             create_or_update(self.resource_group,
                              vm_name, tags).result()
 
-    def delete_nic(self, nic_name):
+    def delete_nic(self, nic_id):
+        nic_params = azure_helpers.\
+            parse_url(NETWORK_INTERFACE_RESOURCE_ID, nic_id)
+        nic_name = nic_params.get(NETWORK_INTERFACE_NAME)
         self.network_management_client. \
             network_interfaces.delete(self.resource_group,
                                       nic_name).wait()
 
-    def get_nic(self, name):
+    def get_nic(self, nic_id):
+        nic_params = azure_helpers.\
+            parse_url(NETWORK_INTERFACE_RESOURCE_ID, nic_id)
+        nic_name = nic_params.get(NETWORK_INTERFACE_NAME)
         return self.network_management_client. \
-            network_interfaces.get(self.resource_group, name)
+            network_interfaces.get(self.resource_group, nic_name)
 
-    def update_nic(self, nic_name, params):
+    def update_nic(self, nic_id, params):
+        nic_params = azure_helpers.\
+            parse_url(NETWORK_INTERFACE_RESOURCE_ID, nic_id)
+        nic_name = nic_params.get(NETWORK_INTERFACE_NAME)
         async_nic_creation = self.network_management_client. \
             network_interfaces.create_or_update(
                 self.resource_group,
@@ -455,16 +600,12 @@ class AzureClient(object):
         return nic_info
 
     def create_nic(self, nic_name, params):
-        return self.update_nic(nic_name, params)
-
-    def get_public_ip(self, name):
         return self.network_management_client. \
-            public_ip_addresses.get(self.resource_group, name)
-
-    def delete_public_ip(self, public_ip_name):
-        self.network_management_client. \
-            public_ip_addresses.delete(self.resource_group,
-                                       public_ip_name).wait()
+            network_interfaces.create_or_update(
+                self.resource_group,
+                nic_name,
+                params
+            ).result()
 
     def create_public_key(self, entity):
         return self.table_service. \
@@ -494,8 +635,11 @@ class AzureClient(object):
             route_tables.delete(self.resource_group, route_table_name
                                 ).wait()
 
-    def attach_subnet_to_route_table(self, network_name,
-                                     subnet_name, route_table_id):
+    def attach_subnet_to_route_table(self, subnet_id, route_table_id):
+        url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID,
+                                             subnet_id)
+        network_name = url_params.get(NETWORK_NAME)
+        subnet_name = url_params.get(SUBNET_NAME)
 
         subnet_info = self.network_management_client.subnets.get(
             self.resource_group,
@@ -517,8 +661,11 @@ class AzureClient(object):
 
         return subnet_info
 
-    def detach_subnet_to_route_table(self, network_name,
-                                     subnet_name, route_table_id):
+    def detach_subnet_to_route_table(self, subnet_id, route_table_id):
+        url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID,
+                                             subnet_id)
+        network_name = url_params.get(NETWORK_NAME)
+        subnet_name = url_params.get(SUBNET_NAME)
 
         subnet_info = self.network_management_client.subnets.get(
             self.resource_group,

+ 4 - 1
cloudbridge/cloud/providers/azure/helpers.py

@@ -1,3 +1,6 @@
+from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
+
+
 def filter_by_tag(list_items, filters):
     """
     This function filter items on the tags
@@ -29,7 +32,7 @@ def parse_url(template_url, original_url):
     template_url_parts = template_url.split('/')
     original_url_parts = original_url.split('/')
     if len(template_url_parts) != len(original_url_parts):
-        raise Exception('Invalid url parameter passed')
+        raise InvalidValueException(template_url, original_url)
     resource_param = {}
     for key, value in zip(template_url_parts, original_url_parts):
         if key.startswith('{') and key.endswith('}'):

+ 153 - 283
cloudbridge/cloud/providers/azure/resources.py

@@ -3,7 +3,6 @@ DataTypes used by this provider
 """
 import collections
 import logging
-import time
 import uuid
 
 from azure.common import AzureException
@@ -12,8 +11,8 @@ from azure.mgmt.network.models import NetworkSecurityGroup
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo, \
     BaseBucket, BaseBucketContainer, BaseBucketObject, BaseFloatingIP, \
-    BaseFloatingIPContainer, BaseInstance, BaseInternetGateway, BaseKeyPair, \
-    BaseLaunchConfig, \
+    BaseFloatingIPContainer, BaseGatewayContainer, BaseInstance, \
+    BaseInternetGateway, BaseKeyPair, BaseLaunchConfig, \
     BaseMachineImage, BaseNetwork, BasePlacementZone, BaseRegion, BaseRouter, \
     BaseSnapshot, BaseSubnet, BaseVMFirewall, BaseVMFirewallRule, \
     BaseVMFirewallRuleContainer, BaseVMType, BaseVolume, ClientPagedResultList
@@ -26,55 +25,14 @@ from msrestazure.azure_exceptions import CloudError
 
 import pysftp
 
-from . import helpers as azure_helpers
-
 log = logging.getLogger(__name__)
 
-NETWORK_INTERFACE_RESOURCE_ID = '/subscriptions/{subscriptionId}/' \
-                                'resourceGroups/{resourceGroupName}' \
-                                '/providers/Microsoft.Network/' \
-                                'networkInterfaces/{networkInterfaceName}'
-PUBLIC_IP_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups' \
-                        '/{resourceGroupName}/providers/Microsoft.Network' \
-                        '/publicIPAddresses/{publicIpAddressName}'
-SUBNET_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
-                     '{resourceGroupName}/providers/Microsoft.Network' \
-                     '/virtualNetworks/{virtualNetworkName}/subnets' \
-                     '/{subnetName}'
-VOLUME_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
-                     '{resourceGroupName}/providers/Microsoft.Compute/' \
-                     'disks/{diskName}'
-VM_FIREWALL_RESOURCE_ID = '/subscriptions/{subscriptionId}/' \
-                             'resourceGroups/{resourceGroupName}/' \
-                             'providers/Microsoft.Network/' \
-                             'networkSecurityGroups/' \
-                             '{networkSecurityGroupName}'
-SNAPSHOT_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
-                       '{resourceGroupName}/providers/Microsoft.Compute/' \
-                       'snapshots/{snapshotName}'
-IMAGE_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
-                    '{resourceGroupName}/providers/Microsoft.Compute/' \
-                    'images/{imageName}'
-INSTANCE_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
-                       '{resourceGroupName}/providers/Microsoft.Compute/' \
-                       'virtualMachines/{vmName}'
-
-NETWORK_NAME = 'virtualNetworkName'
-NETWORK_INTERFACE_NAME = 'networkInterfaceName'
-PUBLIC_IP_NAME = 'publicIpAddressName'
-IMAGE_NAME = 'imageName'
-VM_NAME = 'vmName'
-VOLUME_NAME = 'diskName'
-VM_FIREWALL_NAME = 'networkSecurityGroupName'
-SNAPSHOT_NAME = 'snapshotName'
-
 
 class AzureVMFirewall(BaseVMFirewall):
     def __init__(self, provider, vm_firewall):
         super(AzureVMFirewall, self).__init__(provider, vm_firewall)
         self._vm_firewall = vm_firewall
-        if not self._vm_firewall.tags:
-            self._vm_firewall.tags = {}
+        self._vm_firewall.tags = self._vm_firewall.tags or {}
         self._rule_container = AzureVMFirewallRuleContainer(provider, self)
 
     @property
@@ -87,7 +45,7 @@ class AzureVMFirewall(BaseVMFirewall):
 
     @property
     def id(self):
-        return self._vm_firewall.name
+        return self._vm_firewall.id
 
     @property
     def name(self):
@@ -97,13 +55,12 @@ class AzureVMFirewall(BaseVMFirewall):
     def name(self, value):
         self.assert_valid_resource_name(value)
         self._vm_firewall.tags.update(Name=value)
-        self._provider.azure_client. \
-            update_vm_firewall_tags(self.id,
-                                    self._vm_firewall.tags)
+        self._provider.azure_client.update_vm_firewall_tags(
+            self.id, self._vm_firewall.tags)
 
     @property
     def description(self):
-        return self._vm_firewall.tags.get('Description', None)
+        return self._vm_firewall.tags.get('Description')
 
     @description.setter
     def description(self, value):
@@ -117,13 +74,7 @@ class AzureVMFirewall(BaseVMFirewall):
         return self._rule_container
 
     def delete(self):
-        try:
-            self._provider.azure_client.\
-                delete_vm_firewall(self.id)
-            return True
-        except CloudError as cloudError:
-            log.exception(cloudError.message)
-            return False
+        self._provider.azure_client.delete_vm_firewall(self.id)
 
     def refresh(self):
         """
@@ -221,7 +172,7 @@ class AzureVMFirewallRule(BaseVMFirewallRule):
 
     @property
     def id(self):
-        return self._rule.name
+        return self._rule.id
 
     @property
     def direction(self):
@@ -238,12 +189,13 @@ class AzureVMFirewallRule(BaseVMFirewallRule):
 
     @property
     def from_port(self):
-        return self._port_range_tuple().from_port
+        return self._port_range_tuple.from_port
 
     @property
     def to_port(self):
-        return self._port_range_tuple().to_port
+        return self._port_range_tuple.to_port
 
+    @property
     def _port_range_tuple(self):
         if self._rule.destination_port_range == '*':
             return PortRange(1, 65535)
@@ -268,7 +220,7 @@ class AzureVMFirewallRule(BaseVMFirewallRule):
         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.name == self.name:
+            if o.id == self.id:
                 del self.firewall._vm_firewall.security_rules[i]
                 break
 
@@ -349,13 +301,8 @@ class AzureBucketObject(BaseBucketObject):
         :rtype: bool
         :return: True if successful
         """
-        try:
-            self._provider.azure_client.delete_blob(
-                self._container.name, self.name)
-            return True
-        except AzureException as azureEx:
-            log.exception(azureEx)
-            return False
+        self._provider.azure_client.delete_blob(self._container.name,
+                                                self.name)
 
     def generate_url(self, expires_in=0):
         """
@@ -386,12 +333,7 @@ class AzureBucket(BaseBucket):
         """
         Delete this bucket.
         """
-        try:
-            self._provider.azure_client.delete_container(self.name)
-            return True
-        except AzureException as azureEx:
-            log.exception(azureEx)
-            return False
+        self._provider.azure_client.delete_container(self.name)
 
     def exists(self, name):
         """
@@ -478,7 +420,7 @@ class AzureVolume(BaseVolume):
 
     @property
     def id(self):
-        return self._volume.name
+        return self._volume.id
 
     @property
     def resource_id(self):
@@ -535,13 +477,7 @@ class AzureVolume(BaseVolume):
 
     @property
     def source(self):
-        if self._volume.creation_data.source_uri:
-            url_params = azure_helpers.\
-                parse_url(SNAPSHOT_RESOURCE_ID,
-                          self._volume.creation_data.source_uri)
-            return self._provider.storage.snapshots. \
-                get(url_params.get(SNAPSHOT_NAME))
-        return None
+        return self._volume.creation_data.source_uri
 
     @property
     def attachments(self):
@@ -554,11 +490,7 @@ class AzureVolume(BaseVolume):
         :return:
         """
         if self._volume.managed_by:
-            url_params = azure_helpers.parse_url(INSTANCE_RESOURCE_ID,
-                                                 self._volume.managed_by)
-            return BaseAttachmentInfo(self,
-                                      url_params.get(VM_NAME),
-                                      None)
+            return BaseAttachmentInfo(self, self._volume.managed_by, None)
         else:
             return None
 
@@ -566,25 +498,20 @@ class AzureVolume(BaseVolume):
         """
         Attach this volume to an instance.
         """
-        try:
-            instance_id = instance.id if isinstance(
-                instance,
-                Instance) else instance
-            vm = self._provider.azure_client.get_vm(instance_id)
-
-            vm.storage_profile.data_disks.append({
-                'lun': len(vm.storage_profile.data_disks),
-                'name': self.id,
-                'create_option': 'attach',
-                'managed_disk': {
-                    'id': self.resource_id
-                }
-            })
-            self._provider.azure_client.update_vm(instance_id, vm)
-            return True
-        except CloudError as cloudError:
-            log.exception(cloudError.message)
-            return False
+        instance_id = instance.id if isinstance(
+            instance,
+            Instance) else instance
+        vm = self._provider.azure_client.get_vm(instance_id)
+
+        vm.storage_profile.data_disks.append({
+            'lun': len(vm.storage_profile.data_disks),
+            'name': self._volume.name,
+            'create_option': 'attach',
+            'managed_disk': {
+                'id': self.resource_id
+            }
+        })
+        self._provider.azure_client.update_vm(instance_id, vm)
 
     def detach(self, force=False):
         """
@@ -595,8 +522,7 @@ class AzureVolume(BaseVolume):
                 if item.managed_disk and \
                                 item.managed_disk.id == self.resource_id:
                     vm.storage_profile.data_disks.remove(item)
-                    self._provider.azure_client.update_vm(vm.name, vm)
-        return True
+                    self._provider.azure_client.update_vm(vm.id, vm)
 
     def create_snapshot(self, name, description=None):
         """
@@ -608,13 +534,7 @@ class AzureVolume(BaseVolume):
         """
         Delete this volume.
         """
-        try:
-            self._provider.azure_client. \
-                delete_disk(self.id)
-            return True
-        except CloudError as cloudError:
-            log.exception(cloudError.message)
-            return False
+        self._provider.azure_client.delete_disk(self.id)
 
     @property
     def state(self):
@@ -658,7 +578,7 @@ class AzureSnapshot(BaseSnapshot):
 
     @property
     def id(self):
-        return self._snapshot.name
+        return self._snapshot.id
 
     @property
     def resource_id(self):
@@ -702,10 +622,7 @@ class AzureSnapshot(BaseSnapshot):
 
     @property
     def volume_id(self):
-        url_params = azure_helpers.\
-            parse_url(VOLUME_RESOURCE_ID,
-                      self._snapshot.creation_data.source_resource_id)
-        return url_params.get(VOLUME_NAME)
+        return self._snapshot.creation_data.source_resource_id
 
     @property
     def create_time(self):
@@ -735,12 +652,7 @@ class AzureSnapshot(BaseSnapshot):
         """
         Delete this snapshot.
         """
-        try:
-            self._provider.azure_client.delete_snapshot(self.id)
-            return True
-        except CloudError as cloudError:
-            log.exception(cloudError.message)
-            return False
+        self._provider.azure_client.delete_snapshot(self.id)
 
     def create_volume(self, placement=None,
                       size=None, volume_type=None, iops=None):
@@ -748,7 +660,7 @@ class AzureSnapshot(BaseSnapshot):
         Create a new Volume from this Snapshot.
         """
         return self._provider.storage.volumes. \
-            create(self.id, self.size,
+            create(self.name, self.size,
                    zone=placement, snapshot=self)
 
 
@@ -775,7 +687,7 @@ class AzureMachineImage(BaseMachineImage):
         :rtype: ``str``
         :return: ID for this instance as returned by the cloud middleware.
         """
-        return self._image.name
+        return self._image.id
 
     @property
     def resource_id(self):
@@ -850,8 +762,7 @@ class AzureMachineImage(BaseMachineImage):
         for its latest state.
         """
         try:
-            self._image = self._provider.azure_client\
-                .get_image(self.id)
+            self._image = self._provider.azure_client.get_image(self.id)
             self._state = self._image.provisioning_state
         except CloudError as cloudError:
             log.exception(cloudError.message)
@@ -859,6 +770,32 @@ 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, name=None):
+        if name:
+            AzureInternetGateway.assert_valid_resource_name(name)
+        gateway = AzureInternetGateway(self._provider, None, self._network)
+        if name:
+            gateway.name = name
+        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,
@@ -871,10 +808,11 @@ class AzureNetwork(BaseNetwork):
         self._state = self._network.provisioning_state
         if not self._network.tags:
             self._network.tags = {}
+        self._gateway_service = AzureGatewayContainer(provider, self)
 
     @property
     def id(self):
-        return self._network.name
+        return self._network.id
 
     @property
     def resource_id(self):
@@ -940,13 +878,7 @@ class AzureNetwork(BaseNetwork):
         """
         Delete an existing network.
         """
-        try:
-            self._provider.azure_client.\
-                delete_network(self.id)
-            return True
-        except CloudError as cloudError:
-            log.exception(cloudError.message)
-            return False
+        self._provider.azure_client.delete_network(self.id)
 
     @property
     def subnets(self):
@@ -967,6 +899,10 @@ class AzureNetwork(BaseNetwork):
         return self._provider.networking.subnets. \
             create(network=self.id, cidr_block=cidr_block, name=name)
 
+    @property
+    def gateways(self):
+        return self._gateway_service
+
 
 class AzureFloatingIPContainer(BaseFloatingIPContainer):
 
@@ -977,7 +913,7 @@ class AzureFloatingIPContainer(BaseFloatingIPContainer):
     def get(self, fip_id):
         log.debug("Getting Azure Floating IP container with the id: %s",
                   fip_id)
-        fip = [fip for fip in self.list() if fip.id == 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):
@@ -1032,16 +968,11 @@ class AzureFloatingIP(BaseFloatingIP):
         """
         Delete an existing floating ip.
         """
-        try:
-            self._provider.azure_client.delete_floating_ip(self.id)
-            return True
-        except CloudError as cloud_error:
-            log.exception(cloud_error.message)
-            return False
+        self._provider.azure_client.delete_floating_ip(self.id)
 
     def refresh(self):
         net = self._provider.networking.networks.get(self._network_id)
-        gw = self._provider.networking.gateways.get_or_create_inet_gateway(net)
+        gw = net.gateways.get_or_create_inet_gateway()
         fip = gw.floating_ips.get(self.id)
         self._ip = fip._ip
 
@@ -1120,14 +1051,10 @@ class AzureSubnet(BaseSubnet):
         super(AzureSubnet, self).__init__(provider)
         self._subnet = subnet
         self._state = self._subnet.provisioning_state
-        self._url_params = azure_helpers\
-            .parse_url(SUBNET_RESOURCE_ID, subnet.id)
-        self._network = self._provider.azure_client.\
-            get_network(self._url_params.get(NETWORK_NAME))
 
     @property
     def id(self):
-        return self.network_id + '|$|' + self._subnet.name
+        return self._subnet.id
 
     @property
     def resource_id(self):
@@ -1144,8 +1071,9 @@ class AzureSubnet(BaseSubnet):
 
     @property
     def zone(self):
-        region = self._provider.\
-            compute.regions.get(self._network.location)
+        # pylint:disable=protected-access
+        region = self._provider.compute.regions.get(
+            self._network._network.location)
         return region.zones[0]
 
     @property
@@ -1154,26 +1082,18 @@ class AzureSubnet(BaseSubnet):
 
     @property
     def network_id(self):
-        return self._url_params.get(NETWORK_NAME)
+        return self._provider.azure_client.get_network_id_for_subnet(self.id)
+
+    @property
+    def _network(self):
+        return self._provider.networking.networks.get(self.network_id)
 
     def delete(self):
-        """
-        Delete the subnet
-        :return:
-        """
-        try:
-            subnet_id_parts = self.id.split('|$|')
-            self._provider.azure_client. \
-                delete_subnet(subnet_id_parts[0], subnet_id_parts[1])
-            return True
-        except CloudError as cloudError:
-            log.exception(cloudError.message)
-            return False
+        self._provider.azure_client.delete_subnet(self.id)
 
     @property
     def state(self):
-        return self._SUBNET_STATE_MAP.get(
-            self._state, NetworkState.UNKNOWN)
+        return self._SUBNET_STATE_MAP.get(self._state, NetworkState.UNKNOWN)
 
     def refresh(self):
         """
@@ -1181,12 +1101,12 @@ class AzureSubnet(BaseSubnet):
         for its latest state.
         """
         try:
-            self._network = self._provider.azure_client. \
-                get_network(self.id)
-            self._state = self._network.provisioning_state
+            self._subnet = self._provider.azure_client. \
+                get_subnet(self.id)
+            self._state = self._subnet.provisioning_state
         except (CloudError, ValueError) as cloudError:
             log.exception(cloudError.message)
-            # The network no longer exists and cannot be refreshed.
+            # The subnet no longer exists and cannot be refreshed.
             # set the state to unknown
             self._state = 'unknown'
 
@@ -1215,52 +1135,31 @@ class AzureInstance(BaseInstance):
         super(AzureInstance, self).__init__(provider)
         self._vm = vm_instance
         self._update_state()
-        self._get_network_attributes()
         if not self._vm.tags:
             self._vm.tags = {}
 
-    def _get_network_attributes(self):
-        """
-        This method used identify the public , private ip addresses
-        and security groups associated with network interfaces.
-        :return:
-        """
-        self._private_ips = []
-        self._public_ips = []
-        self._vm_firewall_ids = []
-        self._public_ip_ids = []
-        self._nic_ids = []
-        for nic in self._vm.network_profile.network_interfaces:
-            nic_params = azure_helpers.\
-                parse_url(NETWORK_INTERFACE_RESOURCE_ID, nic.id)
-            nic_name = nic_params.get(NETWORK_INTERFACE_NAME)
-            self._nic_ids.append(nic_name)
-            nic = self._provider.azure_client.get_nic(nic_name)
-            if nic.network_security_group:
-                fw_params = azure_helpers. \
-                    parse_url(VM_FIREWALL_RESOURCE_ID,
-                              nic.network_security_group.id)
-                self._vm_firewall_ids.\
-                    append(fw_params.get(VM_FIREWALL_NAME))
-            if nic.ip_configurations:
-                for ip_config in nic.ip_configurations:
-                    self._private_ips.append(ip_config.private_ip_address)
-                    if ip_config.public_ip_address:
-                        url_params = azure_helpers.\
-                            parse_url(PUBLIC_IP_RESOURCE_ID,
-                                      ip_config.public_ip_address.id)
-                        public_ip_name = url_params.get(PUBLIC_IP_NAME)
-                        public_ip = self._provider.azure_client.\
-                            get_public_ip(public_ip_name)
-                        self._public_ip_ids.append(public_ip_name)
-                        self._public_ips.append(public_ip.ip_address)
+    @property
+    def _nic_ids(self):
+        return (nic.id for nic in self._vm.network_profile.network_interfaces)
+
+    @property
+    def _nics(self):
+        return (self._provider.azure_client.get_nic(nic_id)
+                for nic_id in self._nic_ids)
+
+    @property
+    def _public_ip_ids(self):
+        return (ip_config.public_ip_address.id
+                for nic in self._nics
+                for ip_config in nic.ip_configurations
+                if nic.ip_configurations and ip_config.public_ip_address)
 
     @property
     def id(self):
         """
         Get the instance identifier.
         """
-        return self._vm.name
+        return self._vm.id
 
     @property
     def resource_id(self):
@@ -1291,14 +1190,18 @@ class AzureInstance(BaseInstance):
         """
         Get all the public IP addresses for this instance.
         """
-        return self._public_ips
+        return [self._provider.azure_client.get_floating_ip(pip).ip_address
+                for pip in self._public_ip_ids]
 
     @property
     def private_ips(self):
         """
         Get all the private IP addresses for this instance.
         """
-        return self._private_ips
+        return [ip_config.private_ip_address
+                for nic in self._nics
+                for ip_config in nic.ip_configurations
+                if nic.ip_configurations and ip_config.private_ip_address]
 
     @property
     def vm_type_id(self):
@@ -1331,41 +1234,29 @@ class AzureInstance(BaseInstance):
         """
         self._provider.azure_client.deallocate_vm(self.id)
         self._provider.azure_client.delete_vm(self.id)
+        for public_ip_id in self._public_ip_ids:
+            self._provider.azure_client.delete_floating_ip(public_ip_id)
         for nic_id in self._nic_ids:
             self._provider.azure_client.delete_nic(nic_id)
-        for public_ip_id in self._public_ip_ids:
-            self._provider.azure_client.delete_public_ip(public_ip_id)
         for data_disk in self._vm.storage_profile.data_disks:
             if data_disk.managed_disk:
-                disk_params = azure_helpers.\
-                    parse_url(VOLUME_RESOURCE_ID,
-                              data_disk.managed_disk.id)
                 disk = self._provider.azure_client.\
-                    get_disk(disk_params.get(VOLUME_NAME))
+                    get_disk(data_disk.managed_disk.id)
                 if disk and disk.tags \
                         and disk.tags.get('delete_on_terminate',
                                           'False') == 'True':
                     self._provider.azure_client.\
-                        delete_disk(disk_params.get(VOLUME_NAME))
+                        delete_disk(data_disk.managed_disk.id)
         if self._vm.storage_profile.os_disk.managed_disk:
-            disk_params = azure_helpers. \
-                parse_url(VOLUME_RESOURCE_ID,
-                          self._vm.storage_profile.os_disk.managed_disk.id)
             self._provider.azure_client. \
-                delete_disk(disk_params.get(VOLUME_NAME))
+                delete_disk(self._vm.storage_profile.os_disk.managed_disk.id)
 
     @property
     def image_id(self):
         """
         Get the image ID for this insance.
         """
-        image_ref_id = self._vm.storage_profile.image_reference.id
-        if image_ref_id:
-            url_params = azure_helpers.parse_url(IMAGE_RESOURCE_ID,
-                                                 image_ref_id)
-            return url_params.get(IMAGE_NAME)
-        else:
-            return None
+        return self._vm.storage_profile.image_reference.id
 
     @property
     def zone_id(self):
@@ -1377,11 +1268,13 @@ class AzureInstance(BaseInstance):
     @property
     def vm_firewalls(self):
         return [self._provider.security.vm_firewalls.get(group_id)
-                for group_id in self._vm_firewall_ids]
+                for group_id in self.vm_firewall_ids]
 
     @property
     def vm_firewall_ids(self):
-        return self._vm_firewall_ids
+        return [nic.network_security_group.id
+                for nic in self._nics
+                if nic.network_security_group]
 
     @property
     def key_pair_name(self):
@@ -1392,16 +1285,15 @@ class AzureInstance(BaseInstance):
 
     def create_image(self, name, private_key_path=None):
         """
-        Create a new image based on this instance.
-        Documentation for create image available at
-        https://docs.microsoft.com/en-us/azure/virtual-machines/linux/capture-image  # noqa
-        In azure, need to deprovision the VM before capturing.
-        To deprovision, login to VM and execute waagent deprovision command.
-        To do this programmatically, using pysftp to ssh into the VM
-        and executing deprovision command.
-        To SSH into the VM programmatically, need pass private key file path,
-        so we have modified the Cloud Bridge interface to pass
-        the private key file path
+        Create a new image based on this instance. Documentation for create
+        image available at https://docs.microsoft.com/en-us/azure/virtual-ma
+        chines/linux/capture-image. In azure, we need to deprovision the VM
+        before capturing.
+        To deprovision, login to the VM and execute the `waagent deprovision`
+        command. To do this programmatically, use pysftp to ssh into the VM
+        and executing deprovision command. To SSH into the VM programmatically
+        however, we need to pass private key file path, so we have modified the
+        CloudBridge interface to pass the private key file path
         """
 
         self.assert_valid_resource_name(name)
@@ -1409,8 +1301,6 @@ class AzureInstance(BaseInstance):
         if not self._state == 'VM generalized':
             if not self._state == 'VM running':
                 self._provider.azure_client.start_vm(self.id)
-                time.sleep(10)  # Some time is required
-                self._get_network_attributes()
 
             # if private_key_path:
             self._deprovision(private_key_path)
@@ -1424,11 +1314,8 @@ class AzureInstance(BaseInstance):
             },
             'tags': {'Name': name}
         }
-        self._provider.azure_client.\
-            create_image(name, create_params)
-        image = self._provider.azure_client.\
-            get_image(name)
 
+        image = self._provider.azure_client.create_image(name, create_params)
         return AzureMachineImage(self._provider, image)
 
     def _deprovision(self, private_key_path):
@@ -1447,22 +1334,25 @@ class AzureInstance(BaseInstance):
         """
         Attaches public ip to the instance.
         """
-        nic = self._provider.azure_client.get_nic(self._nic_ids[0])
+        floating_ip_id = floating_ip.id if isinstance(
+            floating_ip, AzureFloatingIP) else floating_ip
+        nic = next(self._nics)
         nic.ip_configurations[0].public_ip_address = {
-            'id': floating_ip.id
+            'id': floating_ip_id
         }
-        self._provider.azure_client.update_nic(self._nic_ids[0], nic)
+        self._provider.azure_client.update_nic(nic.id, nic)
 
     def remove_floating_ip(self, floating_ip):
         """
         Remove a public IP address from this instance.
         """
-        nic = self._provider.azure_client.get_nic(self._nic_ids[0])
+        floating_ip_id = floating_ip.id if isinstance(
+            floating_ip, AzureFloatingIP) else floating_ip
+        nic = next(self._nics)
         for ip_config in nic.ip_configurations:
-            if ip_config.public_ip_address.id == floating_ip.id:
+            if ip_config.public_ip_address.id == floating_ip_id:
                 nic.ip_configurations[0].public_ip_address = None
-                self._provider.azure_client.update_nic(self._nic_ids[0],
-                                                       nic)
+                self._provider.azure_client.update_nic(nic.id, nic)
 
     def add_vm_firewall(self, fw):
         '''
@@ -1478,17 +1368,13 @@ class AzureInstance(BaseInstance):
         '''
         fw = (self._provider.security.vm_firewalls.get(fw)
               if isinstance(fw, str) else fw)
-        nic = self._provider.azure_client.get_nic(self._nic_ids[0])
+        nic = next(self._nics)
         if not nic.network_security_group:
             nic.network_security_group = NetworkSecurityGroup()
             nic.network_security_group.id = fw.resource_id
         else:
-            fw_url_params = azure_helpers.\
-                parse_url(VM_FIREWALL_RESOURCE_ID,
-                          nic.network_security_group.id)
             existing_fw = self._provider.security.\
-                vm_firewalls.get(fw_url_params.get(VM_FIREWALL_NAME))
-
+                vm_firewalls.get(nic.network_security_group.id)
             new_fw = self._provider.security.vm_firewalls.\
                 create('{0}-{1}'.format(fw.name, existing_fw.name),
                        'Merged security groups {0} and {1}'.
@@ -1497,7 +1383,7 @@ class AzureInstance(BaseInstance):
             new_fw.add_rule(src_dest_fw=existing_fw)
             nic.network_security_group.id = new_fw.resource_id
 
-        self._provider.azure_client.create_nic(self._nic_ids[0], nic)
+        self._provider.azure_client.update_nic(nic.id, nic)
 
     def remove_vm_firewall(self, fw):
 
@@ -1513,13 +1399,13 @@ class AzureInstance(BaseInstance):
         else we are ignoring.
         '''
 
-        nic = self._provider.azure_client.get_nic(self._nic_ids[0])
+        nic = next(self._nics)
         fw = (self._provider.security.vm_firewalls.get(fw)
               if isinstance(fw, str) else fw)
         if nic.network_security_group and \
                 nic.network_security_group.id == fw.resource_id:
             nic.network_security_group = None
-            self._provider.azure_client.create_nic(self._nic_ids[0], nic)
+            self._provider.azure_client.update_nic(nic.id, nic)
 
     def _update_state(self):
         """
@@ -1554,7 +1440,6 @@ class AzureInstance(BaseInstance):
             if not self._vm.tags:
                 self._vm.tags = {}
             self._update_state()
-            self._get_network_attributes()
         except (CloudError, ValueError) as cloudError:
             log.exception(cloudError.message)
             # The volume no longer exists and cannot be refreshed.
@@ -1637,12 +1522,7 @@ class AzureKeyPair(BaseKeyPair):
         return self._key_pair.Name
 
     def delete(self):
-        try:
-            self._provider.azure_client.\
-                delete_public_key(self._key_pair)
-            return True
-        except CloudError:
-            return False
+        self._provider.azure_client.delete_public_key(self._key_pair)
 
 
 class AzureRouter(BaseRouter):
@@ -1697,26 +1577,17 @@ class AzureRouter(BaseRouter):
         return None
 
     def delete(self):
-        self._provider.azure_client. \
-            delete_route_table(self.name)
+        self._provider.azure_client.delete_route_table(self.name)
 
     def attach_subnet(self, subnet):
-        subnet_id_parts = subnet.id.split('|$|')
-        if (len(subnet_id_parts) != 2):
-            pass
         self._provider.azure_client. \
-            attach_subnet_to_route_table(subnet_id_parts[0],
-                                         subnet_id_parts[1],
+            attach_subnet_to_route_table(subnet.id,
                                          self.resource_id)
         self.refresh()
 
     def detach_subnet(self, subnet):
-        subnet_id_parts = subnet.id.split('|$|')
-        if (len(subnet_id_parts) != 2):
-            pass
         self._provider.azure_client. \
-            detach_subnet_to_route_table(subnet_id_parts[0],
-                                         subnet_id_parts[1],
+            detach_subnet_to_route_table(subnet.id,
                                          self.resource_id)
         self.refresh()
 
@@ -1732,7 +1603,6 @@ class AzureInternetGateway(BaseInternetGateway):
         super(AzureInternetGateway, self).__init__(provider)
         self._gateway = gateway
         self._name = None
-        self._network_id = None
         self._network_id = gateway_net.id if isinstance(
             gateway_net, AzureNetwork) else gateway_net
         self._state = ''

+ 149 - 211
cloudbridge/cloud/providers/azure/services.py

@@ -3,18 +3,20 @@ import logging
 import uuid
 
 from azure.common import AzureException
+from azure.mgmt.compute.models import DiskCreateOption
 
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import ClientPagedResultList, \
     ServerPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService, \
-    BaseComputeService, BaseGatewayService, \
+    BaseComputeService, \
     BaseImageService, BaseInstanceService, BaseKeyPairService, \
     BaseNetworkService, BaseNetworkingService, BaseRegionService, \
     BaseRouterService, BaseSecurityService, BaseSnapshotService, \
     BaseStorageService, BaseSubnetService, BaseVMFirewallService, \
     BaseVMTypeService, BaseVolumeService
-from cloudbridge.cloud.interfaces import InvalidConfigurationException
+from cloudbridge.cloud.interfaces.exceptions import \
+    DuplicateResourceException, InvalidValueException
 from cloudbridge.cloud.interfaces.resources import MachineImage, \
     Network, PlacementZone, Snapshot, Subnet, VMFirewall, VMType, Volume
 
@@ -22,7 +24,7 @@ from msrestazure.azure_exceptions import CloudError
 
 from . import helpers as azure_helpers
 from .resources import AzureBucket, \
-    AzureInstance, AzureInternetGateway, AzureKeyPair, \
+    AzureInstance, AzureKeyPair, \
     AzureLaunchConfig, AzureMachineImage, AzureNetwork, \
     AzureRegion, AzureRouter, AzureSnapshot, AzureSubnet, \
     AzureVMFirewall, AzureVMType, AzureVolume
@@ -55,10 +57,9 @@ class AzureVMFirewallService(BaseVMFirewallService):
         try:
             fws = self.provider.azure_client.get_vm_firewall(fw_id)
             return AzureVMFirewall(self.provider, fws)
-
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
 
     def list(self, limit=None, marker=None):
@@ -86,7 +87,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
             rule.priority = rule.priority - 61440
             rule.access = "Deny"
             self._provider.azure_client.create_vm_firewall_rule(
-                fw.name, rule_name, rule)
+                fw.id, rule_name, rule)
 
         # Add a new custom rule allowing all outbound traffic to the internet
         parameters = {"priority": 3000,
@@ -98,7 +99,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
                       "access": "Allow",
                       "direction": "Outbound"}
         result = self._provider.azure_client.create_vm_firewall_rule(
-            fw.name, "cb-default-internet-outbound", parameters)
+            fw.id, "cb-default-internet-outbound", parameters)
         fw.security_rules.append(result)
 
         cb_fw = AzureVMFirewall(self.provider, fw)
@@ -119,13 +120,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
         return ClientPagedResultList(self.provider, fws)
 
     def delete(self, group_id):
-        try:
-            self.provider.azure_client.delete_vm_firewall(group_id)
-            return True
-        except CloudError as cloudError:
-            # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
-            return False
+        self.provider.azure_client.delete_vm_firewall(group_id)
 
 
 class AzureKeyPairService(BaseKeyPairService):
@@ -175,7 +170,7 @@ class AzureKeyPairService(BaseKeyPairService):
         key_pair = self.get(name)
 
         if key_pair:
-            raise Exception(
+            raise DuplicateResourceException(
                 'Keypair already exists with name {0}'.format(name))
 
         private_key = None
@@ -207,7 +202,6 @@ class AzureBucketService(BaseBucketService):
         try:
             bucket = self.provider.azure_client.get_container(bucket_id)
             return AzureBucket(self.provider, bucket)
-
         except AzureException as error:
             log.exception(error)
             return None
@@ -276,9 +270,9 @@ class AzureVolumeService(BaseVolumeService):
         try:
             volume = self.provider.azure_client.get_disk(volume_id)
             return AzureVolume(self.provider, volume)
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
 
     def find(self, **kwargs):
@@ -321,13 +315,14 @@ class AzureVolumeService(BaseVolumeService):
                 'location':
                     zone_id or self.provider.azure_client.region_name,
                 'creation_data': {
-                    'create_option': 'copy',
+                    'create_option': DiskCreateOption.copy,
                     'source_uri': snapshot.resource_id
                 },
                 'tags': tags
             }
 
-            self.provider.azure_client.create_snapshot_disk(disk_name, params)
+            disk = self.provider.azure_client.create_snapshot_disk(disk_name,
+                                                                   params)
 
         else:
             params = {
@@ -335,13 +330,14 @@ class AzureVolumeService(BaseVolumeService):
                     zone_id or self.provider.region_name,
                 'disk_size_gb': size,
                 'creation_data': {
-                    'create_option': 'empty'
+                    'create_option': DiskCreateOption.empty
                 },
                 'tags': tags}
 
-            self.provider.azure_client.create_empty_disk(disk_name, params)
+            disk = self.provider.azure_client.create_empty_disk(disk_name,
+                                                                params)
 
-        azure_vol = self.provider.azure_client.get_disk(disk_name)
+        azure_vol = self.provider.azure_client.get_disk(disk.id)
         cb_vol = AzureVolume(self.provider, azure_vol)
 
         return cb_vol
@@ -358,9 +354,9 @@ class AzureSnapshotService(BaseSnapshotService):
         try:
             snapshot = self.provider.azure_client.get_snapshot(ss_id)
             return AzureSnapshot(self.provider, snapshot)
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
 
     def find(self, **kwargs):
@@ -403,19 +399,16 @@ class AzureSnapshotService(BaseSnapshotService):
         params = {
             'location': self.provider.azure_client.region_name,
             'creation_data': {
-                'create_option': 'Copy',
+                'create_option': DiskCreateOption.copy,
                 'source_uri': volume.resource_id
             },
             'disk_size_gb': volume.size,
             'tags': tags
         }
 
-        self.provider.azure_client. \
-            create_snapshot(snapshot_name, params)
-        azure_snap = self.provider.azure_client.get_snapshot(snapshot_name)
-        cb_snap = AzureSnapshot(self.provider, azure_snap)
-
-        return cb_snap
+        azure_snap = self.provider.azure_client.create_snapshot(snapshot_name,
+                                                                params)
+        return AzureSnapshot(self.provider, azure_snap)
 
 
 class AzureComputeService(BaseComputeService):
@@ -467,12 +460,18 @@ class AzureInstanceService(BaseInstanceService):
             # but useless. However, this will allow an instance to be launched
             # without specifying a keypair, so users may still be able to login
             # if they have a preinstalled keypair/password baked into the image
-            key_pair = self.provider.security.key_pairs.create(
-                name="cloudbridge_temp_key_pair")
-            temp_key_pair = key_pair
+            default_kp_name = "cb_default_key_pair"
+            default_kp = self.provider.security.key_pairs.find(
+                name=default_kp_name)
+            if default_kp:
+                key_pair = default_kp[0]
+            else:
+                key_pair = self.provider.security.key_pairs.create(
+                    name=default_kp_name)
+                temp_key_pair = key_pair
 
-        image = (self.provider.compute.images.get(image)
-                 if isinstance(image, str) else image)
+        image = (image if isinstance(image, AzureMachineImage) else
+                 self.provider.compute.images.get(image))
         if not isinstance(image, AzureMachineImage):
             raise Exception("Provided image %s is not a valid azure image"
                             % image)
@@ -492,13 +491,8 @@ class AzureInstanceService(BaseInstanceService):
             self._resolve_launch_options(instance_name,
                                          subnet, zone_id, vm_firewalls)
 
-        if launch_config:
-            disks, root_disk_size = \
-                self._process_block_device_mappings(launch_config,
-                                                    name, zone_id)
-        else:
-            disks = None
-            root_disk_size = None
+        storage_profile = self._create_storage_profile(image, launch_config,
+                                                       instance_name, zone_id)
 
         nic_params = {
                 'location': self._provider.region_name,
@@ -549,34 +543,31 @@ class AzureInstanceService(BaseInstanceService):
                     'id': nic_info.id
                 }]
             },
-            'storage_profile': {
-                'image_reference': {
-                    'id': image.resource_id
-                },
-                "os_disk": {
-                    "name": instance_name + '_os_disk',
-                    "create_option": "fromImage"
-                },
-                'data_disks': disks
-            },
+            'storage_profile': storage_profile,
             'tags': {'Name': name}
         }
 
         if key_pair:
             params['tags'].update(Key_Pair=key_pair.name)
 
-        if root_disk_size:
-            params['storage_profile']['os_disk']['disk_size_gb'] = \
-                root_disk_size
-
         if user_data:
             custom_data = base64.b64encode(bytes(ud, 'utf-8'))
             params['os_profile']['custom_data'] = str(custom_data, 'utf-8')
 
-        self.provider.azure_client.create_vm(instance_name, params)
-        vm = self._provider.azure_client.get_vm(instance_name)
-        if temp_key_pair:
-            temp_key_pair.delete()
+        try:
+            vm = self.provider.azure_client.create_vm(instance_name, params)
+        except Exception as e:
+            # If VM creation fails, attempt to clean up intermediary resources
+            self.provider.azure_client.delete_nic(nic_info.id)
+            for disk_def in storage_profile.get('data_disks', []):
+                if disk_def.get('tags', {}).get('delete_on_terminate'):
+                    disk_id = disk_def.get('managed_disk', {}).get('id')
+                    if disk_id:
+                        self.provider.storage.volumes.delete(disk_id)
+            raise e
+        finally:
+            if temp_key_pair:
+                temp_key_pair.delete()
         return AzureInstance(self.provider, vm)
 
     def _resolve_launch_options(self, name, subnet=None, zone_id=None,
@@ -609,6 +600,29 @@ class AzureInstanceService(BaseInstanceService):
 
         return subnet.resource_id, zone_id, vm_firewall_id
 
+    def _create_storage_profile(self, image, launch_config, instance_name,
+                                zone_id):
+
+        storage_profile = {
+            'image_reference': {
+                'id': image.resource_id
+            },
+            "os_disk": {
+                "name": instance_name + '_os_disk',
+                "create_option": DiskCreateOption.from_image
+            },
+        }
+
+        if launch_config:
+            data_disks, root_disk_size = self._process_block_device_mappings(
+                launch_config, instance_name, zone_id)
+            if data_disks:
+                storage_profile['data_disks'] = data_disks
+            if root_disk_size:
+                storage_profile['os_disk']['disk_size_gb'] = root_disk_size
+
+        return storage_profile
+
     def _process_block_device_mappings(self, launch_config,
                                        vm_name, zone=None):
         """
@@ -617,71 +631,67 @@ class AzureInstanceService(BaseInstanceService):
         are requested (source is None and destination is VOLUME), they will be
         created and the relevant volume ids included in the mapping.
         """
-        disks = []
-        volumes_count = 0
+        data_disks = []
         root_disk_size = None
 
-        def attach_volume(volume, delete_on_terminate):
-            disks.append({
-                'lun': volumes_count,
-                'name': volume.id,
-                'create_option': 'attach',
-                'managed_disk': {
-                    'id': volume.resource_id
-                }
-            })
-            delete_on_terminate = delete_on_terminate or False
-            volume.tags.update(delete_on_terminate=str(delete_on_terminate))
+        def append_disk(disk_def, device_no, delete_on_terminate):
             # In azure, there is no option to specify terminate disks
             # (similar to AWS delete_on_terminate) on VM delete.
             # This method uses the azure tags functionality to store
             # the  delete_on_terminate option when the virtual machine
             # is deleted, we parse the tags and delete accordingly
-            self.provider.azure_client.\
-                update_disk_tags(volume.id, volume.tags)
+            disk_def['lun'] = device_no
+            disk_def['tags'] = {
+                'delete_on_terminate': delete_on_terminate
+            }
+            data_disks.append(disk_def)
 
-        for device in launch_config.block_devices:
+        for device_no, device in enumerate(launch_config.block_devices):
             if device.is_volume:
-                if not device.is_root:
+                if device.is_root:
+                    root_disk_size = device.size
+                else:
                     # In azure, os disk automatically created,
                     # we are ignoring the root disk, if specified
                     if isinstance(device.source, Snapshot):
                         snapshot_vol = device.source.create_volume()
-                        attach_volume(snapshot_vol,
-                                      device.delete_on_terminate)
+                        disk_def = {
+                            # pylint:disable=protected-access
+                            'name': snapshot_vol._volume.name,
+                            'create_option': DiskCreateOption.attach,
+                            'managed_disk': {
+                                'id': snapshot_vol.id
+                            }
+                        }
                     elif isinstance(device.source, Volume):
-                        attach_volume(device.source,
-                                      device.delete_on_terminate)
+                        disk_def = {
+                            # pylint:disable=protected-access
+                            'name': device.source._volume.name,
+                            'create_option': DiskCreateOption.attach,
+                            'managed_disk': {
+                                'id': device.source.id
+                            }
+                        }
                     elif isinstance(device.source, MachineImage):
-                        # Not supported
-                        pass
+                        disk_def = {
+                            # pylint:disable=protected-access
+                            'name': device.source._volume.name,
+                            'create_option': DiskCreateOption.from_image,
+                            'source_resource_id': device.source.id
+                        }
                     else:
-                        # source is None, but destination is volume, therefore
-                        # create a blank volume. If the Zone is None, this
-                        # could fail since the volume and instance may
-                        # be created in two different zones.
-                        if not zone:
-                            raise InvalidConfigurationException(
-                                "A zone must be specified when "
-                                "launching with a"
-                                " new blank volume block device mapping.")
-                        vol_name = \
-                            "{0}_{1}_disk".format(vm_name,
-                                                  uuid.uuid4().hex[:6])
-                        new_vol = self.provider.storage.volumes.create(
-                            vol_name,
-                            device.size,
-                            zone)
-                        attach_volume(new_vol, device.delete_on_terminate)
-                    volumes_count += 1
-                else:
-                    root_disk_size = device.size
-
+                        disk_def = {
+                            # pylint:disable=protected-access
+                            'create_option': DiskCreateOption.empty,
+                            'disk_size_gb': device.size
+                        }
+                    append_disk(disk_def, device_no,
+                                device.delete_on_terminate)
             else:  # device is ephemeral
                 # in azure we cannot add the ephemeral disks explicitly
                 pass
 
-        return disks, root_disk_size
+        return data_disks, root_disk_size
 
     def create_launch_config(self):
         return AzureLaunchConfig(self.provider)
@@ -703,9 +713,9 @@ class AzureInstanceService(BaseInstanceService):
         try:
             vm = self.provider.azure_client.get_vm(instance_id)
             return AzureInstance(self.provider, vm)
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
 
     def find(self, **kwargs):
@@ -734,9 +744,9 @@ class AzureImageService(BaseImageService):
         try:
             image = self.provider.azure_client.get_image(image_id)
             return AzureMachineImage(self.provider, image)
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
 
     def find(self, **kwargs):
@@ -790,7 +800,6 @@ 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)
 
     @property
     def networks(self):
@@ -804,10 +813,6 @@ class AzureNetworkingService(BaseNetworkingService):
     def routers(self):
         return self._router_service
 
-    @property
-    def gateways(self):
-        return self._gateway_service
-
 
 class AzureNetworkService(BaseNetworkService):
     def __init__(self, provider):
@@ -817,10 +822,9 @@ class AzureNetworkService(BaseNetworkService):
         try:
             network = self.provider.azure_client.get_network(network_id)
             return AzureNetwork(self.provider, network)
-
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
 
     def list(self, limit=None, marker=None):
@@ -862,22 +866,16 @@ class AzureNetworkService(BaseNetworkService):
             },
             'tags': {'Name': name or AzureNetwork.CB_DEFAULT_NETWORK_NAME}
         }
-        self.provider.azure_client.create_network(network_name, params)
-        network = self.provider.azure_client.get_network(network_name)
-        cb_network = AzureNetwork(self.provider, network)
+        az_network = self.provider.azure_client.create_network(network_name,
+                                                               params)
+        cb_network = AzureNetwork(self.provider, az_network)
         return cb_network
 
     def delete(self, network_id):
         """
         Delete an existing network.
         """
-        try:
-            self.provider.azure_client.delete_network(network_id)
-            return True
-        except CloudError as cloudError:
-            # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
-            return False
+        self.provider.azure_client.delete_network(network_id)
 
 
 class AzureRegionService(BaseRegionService):
@@ -919,16 +917,12 @@ class AzureSubnetService(BaseSubnetService):
         :return:
         """
         try:
-            subnet_id_parts = subnet_id.split('|$|')
-            if (len(subnet_id_parts) != 2):
-                return None
-            azure_subnet = self.provider.azure_client.\
-                get_subnet(subnet_id_parts[0], subnet_id_parts[1])
+            azure_subnet = self.provider.azure_client.get_subnet(subnet_id)
             return AzureSubnet(self.provider,
                                azure_subnet) if azure_subnet else None
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
 
     def list(self, network=None, limit=None, marker=None):
@@ -948,7 +942,7 @@ class AzureSubnetService(BaseSubnetService):
         else:
             for net in self.provider.azure_client.list_networks():
                 result_list.extend(self.provider.azure_client.list_subnets(
-                    net.name
+                    net.id
                 ))
         subnets = [AzureSubnet(self.provider, subnet)
                    for subnet in result_list]
@@ -980,59 +974,30 @@ class AzureSubnetService(BaseSubnetService):
         return AzureSubnet(self.provider, subnet_info)
 
     def get_or_create_default(self, zone=None):
-        default_cdir = '10.0.1.0/24'
-        network = None
-        subnet = None
+        default_cidr = '10.0.1.0/24'
 
         # No provider-default Subnet exists, look for a library-default one
-        try:
-            subnet = self.provider.azure_client.get_subnet(
-                AzureNetwork.CB_DEFAULT_NETWORK_NAME,
-                AzureSubnet.CB_DEFAULT_SUBNET_NAME
-            )
-        except CloudError:
-            # Azure raises the cloud error if the resource not available
-            pass
-
-        if subnet:
-            return AzureSubnet(self.provider, subnet)
+        matches = self.find(name=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
+        if matches:
+            return matches[0]
 
         # No provider-default Subnet exists, try to create it (net + subnets)
-        default_net_name = AzureNetwork.CB_DEFAULT_NETWORK_NAME
-        try:
-            network = self.provider.azure_client \
-                .get_network(default_net_name)
-        except CloudError:
-            # Azure raises the cloud error if the resource not available
-            pass
+        networks = self.provider.networking.networks.find(
+            name=AzureNetwork.CB_DEFAULT_NETWORK_NAME)
 
-        if not network:
+        if networks:
+            network = networks[0]
+        else:
             network = self.provider.networking.networks.create(
-                name=default_net_name, cidr_block='10.0.0.0/16')
+                AzureNetwork.CB_DEFAULT_NETWORK_NAME, '10.0.0.0/16')
 
-        subnet = self.provider.azure_client.create_subnet(
-            network.id,
-            AzureSubnet.CB_DEFAULT_SUBNET_NAME,
-            {'address_prefix': default_cdir}
-        )
-
-        return AzureSubnet(self.provider, subnet)
+        subnet = self.create(network, default_cidr,
+                             name=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
+        return subnet
 
     def delete(self, subnet):
-        try:
-            # Azure does not provide an api to delete the subnet by id
-            # It also requires network id. To get the network id
-            # code is doing an explicit get and retrieving the network id
-
-            subnet_id = subnet.id if isinstance(subnet, Subnet) else subnet
-            subnet_id_parts = subnet_id.split('|$|')
-            self.provider.azure_client.\
-                delete_subnet(subnet_id_parts[0], subnet_id_parts[1])
-            return True
-        except CloudError as cloudError:
-            # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
-            return False
+        subnet_id = subnet.id if isinstance(subnet, Subnet) else subnet
+        self.provider.azure_client.delete_subnet(subnet_id)
 
 
 class AzureRouterService(BaseRouterService):
@@ -1043,10 +1008,9 @@ class AzureRouterService(BaseRouterService):
         try:
             route = self.provider.azure_client.get_route_table(router_id)
             return AzureRouter(self.provider, route)
-
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
 
     def find(self, **kwargs):
@@ -1079,29 +1043,3 @@ class AzureRouterService(BaseRouterService):
         route = self.provider.azure_client. \
             create_route_table(name, parameters)
         return AzureRouter(self.provider, route)
-
-
-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
-        self.inet_gateways = []
-
-    def get_or_create_inet_gateway(self, network, name=None):
-        if name:
-            AzureInternetGateway.assert_valid_resource_name(name)
-        gateway = AzureInternetGateway(self.provider, None, network)
-        if name:
-            gateway.name = name
-        if gateway not in self.inet_gateways:
-            self.inet_gateways.append(gateway)
-        return gateway
-
-    def list(self, limit=None, marker=None):
-        return self.inet_gateways
-
-    def delete(self, gateway):
-        pass

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

@@ -31,7 +31,7 @@ def to_server_paged_list(provider, objects, limit=None):
     """
     limit = limit or provider.config.default_result_limit
     is_truncated = len(objects) > limit
-    next_token = objects[limit].id if is_truncated else None
+    next_token = objects[limit-1].id if is_truncated else None
     results = ServerPagedResultList(is_truncated,
                                     next_token,
                                     False)

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

@@ -13,6 +13,7 @@ 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
@@ -397,19 +398,29 @@ class OpenStackInstance(BaseInstance):
         return OpenStackMachineImage(
             self._provider, self._provider.compute.images.get(image_id))
 
+    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))
+
     def add_floating_ip(self, floating_ip):
         """
         Add a floating IP address to this instance.
         """
         log.debug("Adding floating IP adress: %s", floating_ip)
-        self._os_instance.add_floating_ip(floating_ip.public_ip)
+        fip = (floating_ip if isinstance(floating_ip, OpenStackFloatingIP)
+               else self._get_fip(floating_ip))
+        self._os_instance.add_floating_ip(fip.public_ip)
 
     def remove_floating_ip(self, floating_ip):
         """
         Remove a floating IP address from this instance.
         """
         log.debug("Removing floating IP adress: %s", floating_ip)
-        self._os_instance.remove_floating_ip(floating_ip.public_ip)
+        fip = (floating_ip if isinstance(floating_ip, OpenStackFloatingIP)
+               else self._get_fip(floating_ip))
+        self._os_instance.remove_floating_ip(fip.public_ip)
 
     def add_vm_firewall(self, firewall):
         """
@@ -711,6 +722,53 @@ 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(
+            network=self._network, name='cb_conn_test_router')
+        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, name=None):
+        """For OS, inet gtw is any net that has `external` property set."""
+        if name:
+            OpenStackInternetGateway.assert_valid_resource_name(name)
+
+        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/
@@ -729,6 +787,7 @@ class OpenStackNetwork(BaseNetwork):
     def __init__(self, provider, network):
         super(OpenStackNetwork, self).__init__(provider)
         self._network = network
+        self._gateway_service = OpenStackGatewayContainer(provider, self)
 
     @property
     def id(self):
@@ -795,6 +854,10 @@ class OpenStackNetwork(BaseNetwork):
             # subnet no longer exists
             self._network.state = NetworkState.UNKNOWN
 
+    @property
+    def gateways(self):
+        return self._gateway_service
+
 
 class OpenStackSubnet(BaseSubnet):
 
@@ -912,8 +975,7 @@ class OpenStackFloatingIP(BaseFloatingIP):
     def refresh(self):
         net = self._provider.networking.networks.get(
             self._ip.floating_network_id)
-        gw = self._provider.networking.gateways.get_or_create_inet_gateway(
-            net)
+        gw = net.gateways.get_or_create_inet_gateway()
         fip = gw.floating_ips.get(self.id)
         # pylint:disable=protected-access
         self._ip = fip._ip

+ 16 - 57
cloudbridge/cloud/providers/openstack/services.py

@@ -12,7 +12,6 @@ from cloudbridge.cloud.base.resources import BaseLaunchConfig
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseComputeService
-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
@@ -27,6 +26,8 @@ from cloudbridge.cloud.base.services import BaseSubnetService
 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.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import PlacementZone
@@ -167,29 +168,23 @@ class OpenStackKeyPairService(BaseKeyPairService):
         return ClientPagedResultList(self.provider, results)
 
     def create(self, name, public_key_material=None):
-        """
-        Create a new key pair or raise an exception if one already exists.
-
-        :type name: str
-        :param name: The name of the key pair to be created.
-
-        :rtype: ``object`` of :class:`.KeyPair`
-        :return:  A key pair instance or ``None`` if one was not be created.
-        """
         log.debug("Creating a new key pair with the name: %s", name)
         OpenStackKeyPair.assert_valid_resource_name(name)
 
+        existing_kp = self.find(name=name)
+        if existing_kp:
+            raise DuplicateResourceException(
+                'Keypair already exists with name {0}'.format(name))
+
         private_key = None
         if not public_key_material:
             public_key_material, private_key = cb_helpers.generate_key_pair()
+
         kp = self.provider.nova.keypairs.create(name,
                                                 public_key=public_key_material)
-
-        if kp:
-            return OpenStackKeyPair(self.provider, kp)
-            kp.material = private_key
-        log.debug("Key Pair with the name %s already exists", name)
-        return None
+        cb_kp = OpenStackKeyPair(self.provider, kp)
+        cb_kp.material = private_key
+        return cb_kp
 
 
 class OpenStackVMFirewallService(BaseVMFirewallService):
@@ -539,7 +534,7 @@ class OpenStackRegionService(BaseRegionService):
 
     def get(self, region_id):
         log.debug("Getting OpenStack Region with the id: %s", region_id)
-        region = (r for r in self.list() if r.id == region_id)
+        region = (r for r in self if r.id == region_id)
         return next(region, None)
 
     def list(self, limit=None, marker=None):
@@ -782,7 +777,6 @@ 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)
 
     @property
     def networks(self):
@@ -796,10 +790,6 @@ class OpenStackNetworkingService(BaseNetworkingService):
     def routers(self):
         return self._router_service
 
-    @property
-    def gateways(self):
-        return self._gateway_service
-
 
 class OpenStackNetworkService(BaseNetworkService):
 
@@ -858,7 +848,7 @@ class OpenStackSubnetService(BaseSubnetService):
         if network:
             network_id = (network.id if isinstance(network, OpenStackNetwork)
                           else network)
-            subnets = [subnet for subnet in self.list() if network_id ==
+            subnets = [subnet for subnet in self if network_id ==
                        subnet.network_id]
         else:
             subnets = [OpenStackSubnet(self.provider, subnet) for subnet in
@@ -898,11 +888,8 @@ class OpenStackSubnetService(BaseSubnetService):
             router = self.provider.networking.routers.create(
                 network=net, name=OpenStackRouter.CB_DEFAULT_ROUTER_NAME)
             router.attach_subnet(sn)
-            gteway = (self.provider.networking.gateways
-                      .get_or_create_inet_gateway(
-                          net,
-                          OpenStackInternetGateway.CB_DEFAULT_INET_GATEWAY_NAME
-                          ))
+            gteway = net.gateways.get_or_create_inet_gateway(
+                        OpenStackInternetGateway.CB_DEFAULT_INET_GATEWAY_NAME)
             router.attach_gateway(gteway)
             return sn
         except NeutronClientException:
@@ -914,7 +901,7 @@ class OpenStackSubnetService(BaseSubnetService):
                      else subnet)
         self.provider.neutron.delete_subnet(subnet_id)
         # Adhere to the interface docs
-        if subnet_id not in self.list():
+        if subnet_id not in self:
             return True
         return False
 
@@ -955,31 +942,3 @@ class OpenStackRouterService(BaseRouterService):
         body = {'router': {'name': name}} if name else None
         router = self.provider.neutron.create_router(body)
         return OpenStackRouter(self.provider, router.get('router'))
-
-
-class OpenStackGatewayService(BaseGatewayService):
-    """For OpenStack, an internet gateway is a just an 'external' network."""
-
-    def __init__(self, provider):
-        super(OpenStackGatewayService, self).__init__(provider)
-
-    def get_or_create_inet_gateway(self, network, name=None):
-        """For OS, inet gtw is any net that has `external` property set."""
-        if name:
-            OpenStackInternetGateway.assert_valid_resource_name(name)
-
-        for n in self.provider.networking.networks:
-            if n.external:
-                return OpenStackInternetGateway(self.provider, n)
-        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]
-        return ClientPagedResultList(self.provider, igl, limit=limit,
-                                     marker=marker)

+ 1 - 1
docs/getting_started.rst

@@ -121,7 +121,7 @@ attaching an internet gateway to the subnet via a router.
     sn = net.create_subnet(name='my-subnet', cidr_block='10.0.0.0/28')
     router = self.provider.networking.routers.create(network=net, name='my-router')
     router.attach_subnet(sn)
-    gateway = self.provider.networking.gateways.get_or_create_inet_gateway(name)
+    gateway = net.gateways.get_or_create_inet_gateway(name)
     router.attach_gateway(gateway)
 
 

+ 1 - 1
docs/topics/launch.rst

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

+ 2 - 2
docs/topics/networking.rst

@@ -76,7 +76,7 @@ of the block and allow up to 16 IP addresses within a subnet (``/28``).
     sn = net.create_subnet(name='my-subnet', cidr_block='10.0.0.0/28', zone=zone)
     router = provider.networking.routers.create(network=net, name='my-router')
     router.attach_subnet(sn)
-    gateway = provider.networking.gateways.get_or_create_inet_gateway(name)
+    gateway = net.gateways.get_or_create_inet_gateway(name)
     router.attach_gateway(gateway)
 
 
@@ -94,7 +94,7 @@ The additional step that's required here is to assign a floating IP to the VM:
 
     router = provider.networking.routers.create(network=net, name='my-router')
     router.attach_subnet(sn)
-    gateway = provider.networking.gateways.get_or_create_inet_gateway(net, name)
+    gateway = net.gateways.get_or_create_inet_gateway(net, name)
     router.attach_gateway(gateway)
 
     fip = provider.networking.floating_ips.create()

+ 1 - 0
requirements.txt

@@ -1 +1,2 @@
+git+git://github.com/cloudve/moto@all_merged#egg=moto
 -e ".[dev]"

+ 16 - 0
setup.cfg

@@ -0,0 +1,16 @@
+[coverage:run]
+branch = True
+source = cloudbridge
+omit =
+  cloudbridge/cloud/interfaces/*
+
+[nosetests]
+with-coverage=1
+cover-branches=1
+cover-package=cloudbridge
+processes=5
+process-timeout=2700
+match=^[Tt]est 
+# Don't capture stdout - print immediately
+nocapture=1
+

+ 5 - 4
setup.py

@@ -42,18 +42,19 @@ REQS_OPENSTACK = [
     'python-cinderclient>=1.9.0',
     'python-swiftclient>=3.2.0',
     'python-neutronclient>=6.0.0',
-    'python-keystoneclient>=3.13.0',
-    'requests>=2.14.2'
+    'python-keystoneclient>=3.13.0'
 ]
 REQS_FULL = REQS_BASE + REQS_AWS + REQS_AZURE + REQS_GCP + REQS_OPENSTACK
 # httpretty is required with/for moto 1.0.0 or AWS tests fail
 REQS_DEV = ([
     'tox>=2.1.1',
-    'moto>=1.1.11',
+    'nose',
+    # 'moto>=1.1.11',  # until https://github.com/spulec/moto/issues/1396
     'docutils>=0.14',
     'imagesize>=0.7.1',
     'jinja2>=2.9.6',
     'sphinx>=1.3.1',
+    'pydevd',
     'flake8>=3.3.0',
     'flake8-import-order>=0.12'] + REQS_FULL
 )
@@ -64,7 +65,7 @@ setup(
     description='A simple layer of abstraction over multiple cloud providers.',
     author='Galaxy and GVL Projects',
     author_email='help@genome.edu.au',
-    url='http://cloudbridge.readthedocs.org/',
+    url='http://cloudbridge.cloudve.org/',
     install_requires=REQS_FULL,
     extras_require={
         ':python_version=="2.7"': ['py2-ipaddress'],

+ 9 - 6
test/helpers/__init__.py

@@ -99,9 +99,12 @@ TEST_DATA_CONFIG = {
         "placement":
             os.environ.get('CB_PLACEMENT_AZURE', 'eastus'),
         "image":
-            os.environ.get('CB_IMAGE_AZURE', 'cb-test-image'),
+            os.environ.get('CB_IMAGE_AZURE',
+                           '/subscriptions/7904d702-e01c-4826-8519-f5a25c866a9'
+                           '6/resourceGroups/cloudbridge/providers/Microsoft.C'
+                           'ompute/images/cb-test-image'),
         "vm_type":
-            os.environ.get('CB_VM_TYPE_AZURE', 'Basic_A0'),
+            os.environ.get('CB_VM_TYPE_AZURE', 'Basic_A2'),
     }
 }
 
@@ -149,16 +152,16 @@ def get_test_gateway(provider, name):
     net_name = 'cb_testgwnet-{0}'.format(get_uuid())
     net = provider.networking.networks.create(
         name=net_name, cidr_block='10.0.0.0/16')
-    return net, provider.networking.gateways.get_or_create_inet_gateway(
-        net, name)
+    return net, net.gateways.get_or_create_inet_gateway(name)
 
 
 def delete_test_gateway(network, gateway):
     """
     Delete the supplied network and gateway.
     """
-    gateway.delete()
-    network.delete()
+    with cleanup_action(lambda: network.delete()):
+        with cleanup_action(lambda: gateway.delete()):
+            pass
 
 
 def create_test_instance(

+ 2 - 9
test/helpers/standard_interface_tests.py

@@ -73,7 +73,7 @@ def check_find(test, service, obj):
         len(find_objs) == 1,
         "Find objects for %s does not return the expected object: %s. Got %s"
         % (type(obj).__name__, obj.name, find_objs))
-    test.assertEqual(find_objs[0], obj)
+    test.assertEqual(find_objs[0].id, obj.id)
     return find_objs
 
 
@@ -88,7 +88,7 @@ def check_find_non_existent(test, service):
 
 def check_get(test, service, obj):
     get_obj = service.get(obj.id)
-    test.assertEqual(get_obj, obj)
+    test.assertEqual(get_obj.id, obj.id)
     test.assertIsInstance(get_obj, type(obj))
     return get_obj
 
@@ -164,13 +164,6 @@ def check_standard_behaviour(test, service, obj):
     obj_get = check_get(test, service, obj)
     check_get_non_existent(test, service)
 
-    test.assertTrue(
-        obj == objs_list[0] == objs_iter[0] == objs_find[0] == obj_get,
-        "Objects returned by list: {0}, iter: {1}, find: {2} and get: {3} "
-        " are not as expected: {4}".format(objs_list[0].id, objs_iter[0].id,
-                                           objs_find[0].id, obj_get.id,
-                                           obj.id))
-
     test.assertTrue(
         obj.id == objs_list[0].id == objs_iter[0].id ==
         objs_find[0].id == obj_get.id,

+ 2 - 0
test/test_block_store_service.py

@@ -17,6 +17,8 @@ import six
 
 class CloudBlockStoreServiceTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['storage.volumes'])
     def test_crud_volume(self):
         """

+ 2 - 0
test/test_cloud_factory.py

@@ -12,6 +12,8 @@ from cloudbridge.cloud.providers.aws.provider import MockAWSCloudProvider
 
 class CloudFactoryTestCase(unittest.TestCase):
 
+    _multiprocess_can_split_ = True
+
     def test_create_provider_valid(self):
         """
         Creating a provider with a known name should return

+ 2 - 0
test/test_cloud_helpers.py

@@ -17,6 +17,8 @@ class DummyResult(object):
 
 class CloudHelpersTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     def setUp(self):
         super(CloudHelpersTestCase, self).setUp()
         self.objects = [DummyResult(1, "One"),

+ 3 - 2
test/test_compute_service.py

@@ -16,6 +16,8 @@ import six
 
 class CloudComputeServiceTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['compute.instances', 'networking.networks'])
     def test_crud_instance(self):
         name = "cb-instcrud-{0}".format(helpers.get_uuid())
@@ -342,8 +344,7 @@ class CloudComputeServiceTestCase(ProviderTestBase):
             with helpers.cleanup_action(lambda: cleanup_router(router,
                                                                gateway)):
                 router.attach_subnet(subnet)
-                gateway = (self.provider.networking.gateways
-                           .get_or_create_inet_gateway(net, name))
+                gateway = net.gateways.get_or_create_inet_gateway(name)
                 router.attach_gateway(gateway)
                 # check whether adding an elastic ip works
                 fip = gateway.floating_ips.create()

+ 2 - 0
test/test_image_service.py

@@ -8,6 +8,8 @@ from cloudbridge.cloud.interfaces.resources import MachineImage
 
 class CloudImageServiceTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['compute.images', 'networking.networks',
                               'compute.instances'])
     def test_create_and_list_image(self):

+ 2 - 0
test/test_interface.py

@@ -10,6 +10,8 @@ from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
 
 class CloudInterfaceTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     def test_name_property(self):
         """
         Name should always return a value and should not raise an exception

+ 5 - 4
test/test_network_service.py

@@ -12,6 +12,8 @@ from cloudbridge.cloud.interfaces.resources import Subnet
 
 class CloudNetworkServiceTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['networking.networks'])
     def test_crud_network(self):
 
@@ -99,6 +101,8 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                            "cb-crudsubnet", create_subnet, cleanup_subnet)
 
     def test_crud_floating_ip(self):
+        net, gw = helpers.get_test_gateway(
+            self.provider, 'cb-crudfipgw-{0}'.format(helpers.get_uuid()))
 
         def create_fip(name):
             fip = gw.floating_ips.create()
@@ -107,8 +111,6 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
         def cleanup_fip(fip):
             gw.floating_ips.delete(fip.id)
 
-        net, gw = helpers.get_test_gateway(
-            self.provider, 'cb-crudfipgw-{0}'.format(helpers.get_uuid()))
         with helpers.cleanup_action(
                 lambda: helpers.delete_test_gateway(net, gw)):
             sit.check_crud(self, gw.floating_ips, FloatingIP,
@@ -181,8 +183,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
 #                     router.id, router.network_id))
 
             router.attach_subnet(sn)
-            gteway = (self.provider.networking.gateways
-                      .get_or_create_inet_gateway(net, name))
+            gteway = net.gateways.get_or_create_inet_gateway(name)
             router.attach_gateway(gteway)
             # TODO: add a check for routes after that's been implemented
 

+ 2 - 0
test/test_object_life_cycle.py

@@ -7,6 +7,8 @@ from cloudbridge.cloud.interfaces.exceptions import WaitStateException
 
 class CloudObjectLifeCycleTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['storage.volumes'])
     def test_object_life_cycle(self):
         """

+ 2 - 0
test/test_object_store_service.py

@@ -20,6 +20,8 @@ import requests
 
 class CloudObjectStoreServiceTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['storage.buckets'])
     def test_crud_bucket(self):
         """

+ 2 - 0
test/test_region_service.py

@@ -9,6 +9,8 @@ import six
 
 class CloudRegionServiceTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['compute.regions'])
     def test_get_and_list_regions(self):
         """

+ 4 - 1
test/test_security_service.py

@@ -4,6 +4,7 @@ from test.helpers import ProviderTestBase
 from test.helpers import standard_interface_tests as sit
 
 import cloudbridge.cloud.base.helpers as cb_helpers
+from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
@@ -12,6 +13,8 @@ from cloudbridge.cloud.interfaces.resources import VMFirewallRule
 
 class CloudSecurityServiceTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['security.key_pairs'])
     def test_crud_key_pair_service(self):
 
@@ -23,7 +26,7 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
 
         def extra_tests(kp):
             # Recreating existing keypair should raise an exception
-            with self.assertRaises(Exception):
+            with self.assertRaises(DuplicateResourceException):
                 self.provider.security.key_pairs.create(name=kp.name)
 
         sit.check_crud(self, self.provider.security.key_pairs, KeyPair,

+ 2 - 0
test/test_vm_types_service.py

@@ -7,6 +7,8 @@ import six
 
 class CloudVMTypeServiceTestCase(ProviderTestBase):
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['compute.vm_types'])
     def test_vm_type_properties(self):
 

+ 2 - 1
tox.ini

@@ -16,7 +16,8 @@ envlist = {py27,py36,pypy}-{aws,azure,gce,openstack}
 
 [testenv]
 commands = flake8 cloudbridge test setup.py
-           {envpython} -m coverage run --branch --source=cloudbridge --omit=cloudbridge/cloud/interfaces/* setup.py test {posargs}
+           # see setup.cfg for options sent to nosetests and coverage
+           {envpython} setup.py nosetests {posargs}
 setenv =
     MOTO_AMIS_PATH=./test/fixtures/custom_amis.json
     aws: CB_TEST_PROVIDER=aws