Bladeren bron

Merge master into GCE

Ehsan Chiniforooshan 8 jaren geleden
bovenliggende
commit
f873edb7f7
37 gewijzigde bestanden met toevoegingen van 939 en 848 verwijderingen
  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
 dist: trusty
 language: python
 language: python
+cache:
+  directories:
+    - $HOME/.cache/pip
+    - $TRAVIS_BUILD_DIR/.tox
 os:
 os:
   - linux
   - linux
 #  - osx
 #  - osx
@@ -65,8 +69,8 @@ install:
     - pip install coveralls
     - pip install coveralls
     - pip install codecov
     - pip install codecov
 script:
 script:
-    - travis_wait 110 tox -e $TOX_ENV
-after_success:
+    - tox -e $TOX_ENV
+after_script:
     - |
     - |
       case "$TRAVIS_EVENT_TYPE" in
       case "$TRAVIS_EVENT_TYPE" in
         push|pull_request)
         push|pull_request)
@@ -83,6 +87,7 @@ after_success:
            ;;
            ;;
         *)
         *)
            echo "Build triggered through API or CRON job. Running regardless of changes"
            echo "Build triggered through API or CRON job. Running regardless of changes"
+           coveralls & codecov & wait
            ;;
            ;;
       esac
       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.backends import default_backend
 from cryptography.hazmat.primitives import serialization as crypt_serialization
 from cryptography.hazmat.primitives import serialization as crypt_serialization
 from cryptography.hazmat.primitives.asymmetric import rsa
 from cryptography.hazmat.primitives.asymmetric import rsa
 
 
+from six import reraise
+
 
 
 def generate_key_pair():
 def generate_key_pair():
     """
     """
@@ -52,3 +58,38 @@ def generic_find(filter_names, kwargs, objs):
             % (kwargs, filter_names))
             % (kwargs, filter_names))
 
 
     return matches
     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 FloatingIP
 from cloudbridge.cloud.interfaces.resources import FloatingIPContainer
 from cloudbridge.cloud.interfaces.resources import FloatingIPContainer
 from cloudbridge.cloud.interfaces.resources import FloatingIpState
 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 GatewayState
 from cloudbridge.cloud.interfaces.resources import Instance
 from cloudbridge.cloud.interfaces.resources import Instance
 from cloudbridge.cloud.interfaces.resources import InstanceState
 from cloudbridge.cloud.interfaces.resources import InstanceState
@@ -916,6 +917,13 @@ class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
         return self.__provider
         return self.__provider
 
 
 
 
+class BaseGatewayContainer(GatewayContainer, BasePageableObjectMixin):
+
+    def __init__(self, provider, network):
+        self._network = network
+        self._provider = provider
+
+
 class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
 class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
 
 
     CB_DEFAULT_NETWORK_NAME = os.environ.get('CB_DEFAULT_NETWORK_NAME',
     CB_DEFAULT_NETWORK_NAME = os.environ.get('CB_DEFAULT_NETWORK_NAME',
@@ -1067,6 +1075,7 @@ class BaseInternetGateway(BaseCloudResource, BaseObjectLifeCycleMixin,
 
 
     def __init__(self, provider):
     def __init__(self, provider):
         super(BaseInternetGateway, self).__init__(provider)
         super(BaseInternetGateway, self).__init__(provider)
+        self.__provider = provider
 
 
     def __repr__(self):
     def __repr__(self):
         return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__, self.id,
         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 BucketService
 from cloudbridge.cloud.interfaces.services import CloudService
 from cloudbridge.cloud.interfaces.services import CloudService
 from cloudbridge.cloud.interfaces.services import ComputeService
 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 ImageService
 from cloudbridge.cloud.interfaces.services import InstanceService
 from cloudbridge.cloud.interfaces.services import InstanceService
 from cloudbridge.cloud.interfaces.services import KeyPairService
 from cloudbridge.cloud.interfaces.services import KeyPairService
@@ -210,10 +209,3 @@ class BaseRouterService(
             if router:
             if router:
                 log.info("Router %s successful deleted.", router)
                 log.info("Router %s successful deleted.", router)
                 router.delete()
                 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__(
         super(InvalidValueException, self).__init__(
             "Param %s has been given an unrecognised value %s" %
             "Param %s has been given an unrecognised value %s" %
             (param, value))
             (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.
         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
         :param floating_ip: The FloatingIP object to associate with the
                             instance. Note that is not the actual public IP
                             instance. Note that is not the actual public IP
                             address but the CloudBridge object encapsulating
                             address but the CloudBridge object encapsulating
-                            the IP.
+                            the IP or the respective provider ID that
+                            identifies the address.
         """
         """
         pass
         pass
 
 
@@ -596,11 +597,12 @@ class Instance(ObjectLifeCycleMixin, CloudResource):
         """
         """
         Remove a public IP address from this instance.
         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
         :param floating_ip: The FloatingIP object to remove from the
                             instance. Note that is not the actual public IP
                             instance. Note that is not the actual public IP
                             address but the CloudBridge object encapsulating
                             address but the CloudBridge object encapsulating
-                            the IP.
+                            the IP or the respective provider ID that
+                            identifies the address.
         """
         """
         pass
         pass
 
 
@@ -888,6 +890,16 @@ class Network(ObjectLifeCycleMixin, CloudResource):
         """
         """
         pass
         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):
 class SubnetState(object):
     """
     """
@@ -1205,6 +1217,50 @@ class GatewayState(object):
     ERROR = "error"
     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):
 class Gateway(CloudResource):
     """
     """
     Represents a gateway resource.
     Represents a gateway resource.

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

@@ -580,17 +580,6 @@ class NetworkingService(CloudService):
         """
         """
         pass
         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):
 class NetworkService(PageableObjectMixin, CloudService):
 
 
@@ -864,53 +853,6 @@ class RouterService(PageableObjectMixin, CloudService):
         pass
         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):
 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 BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
 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 BaseInstance
 from cloudbridge.cloud.base.resources import BaseInternetGateway
 from cloudbridge.cloud.base.resources import BaseInternetGateway
 from cloudbridge.cloud.base.resources import BaseKeyPair
 from cloudbridge.cloud.base.resources import BaseKeyPair
@@ -297,22 +298,32 @@ class AWSInstance(BaseInstance):
         image.refresh()
         image.refresh()
         return image
         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):
     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({
         params = trim_empty_params({
             'InstanceId': self.id,
             'InstanceId': self.id,
             'PublicIp': None if self._ec2_instance.vpc_id else
             'PublicIp': None if self._ec2_instance.vpc_id else
-            floating_ip.public_ip,
+            fip.public_ip,
             # pylint:disable=protected-access
             # 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._provider.ec2_conn.meta.client.associate_address(**params)
         self.refresh()
         self.refresh()
 
 
     def remove_floating_ip(self, floating_ip):
     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({
         params = trim_empty_params({
             'PublicIp': None if self._ec2_instance.vpc_id else
             'PublicIp': None if self._ec2_instance.vpc_id else
-            floating_ip.public_ip,
+            fip.public_ip,
             # pylint:disable=protected-access
             # 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._provider.ec2_conn.meta.client.disassociate_address(**params)
         self.refresh()
         self.refresh()
 
 
@@ -879,6 +890,7 @@ class AWSNetwork(BaseNetwork):
     def __init__(self, provider, network):
     def __init__(self, provider, network):
         super(AWSNetwork, self).__init__(provider)
         super(AWSNetwork, self).__init__(provider)
         self._vpc = network
         self._vpc = network
+        self._gtw_container = AWSGatewayContainer(provider, self)
 
 
     @property
     @property
     def id(self):
     def id(self):
@@ -935,6 +947,10 @@ class AWSNetwork(BaseNetwork):
             VpcIds=[self.id])
             VpcIds=[self.id])
         self.refresh()
         self.refresh()
 
 
+    @property
+    def gateways(self):
+        return self._gtw_container
+
 
 
 class AWSSubnet(BaseSubnet):
 class AWSSubnet(BaseSubnet):
 
 
@@ -1118,6 +1134,51 @@ class AWSRouter(BaseRouter):
             InternetGatewayId=gw_id, VpcId=self._route_table.vpc_id)
             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):
 class AWSInternetGateway(BaseInternetGateway):
 
 
     def __init__(self, provider, gateway):
     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.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseComputeService
 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 BaseImageService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseKeyPairService
 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 BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.interfaces.exceptions \
 from cloudbridge.cloud.interfaces.exceptions \
-    import InvalidConfigurationException
+    import DuplicateResourceException, InvalidConfigurationException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import PlacementZone
@@ -39,7 +38,6 @@ from .helpers import BotoEC2Service
 from .helpers import BotoS3Service
 from .helpers import BotoS3Service
 from .resources import AWSBucket
 from .resources import AWSBucket
 from .resources import AWSInstance
 from .resources import AWSInstance
-from .resources import AWSInternetGateway
 from .resources import AWSKeyPair
 from .resources import AWSKeyPair
 from .resources import AWSLaunchConfig
 from .resources import AWSLaunchConfig
 from .resources import AWSMachineImage
 from .resources import AWSMachineImage
@@ -105,10 +103,17 @@ class AWSKeyPairService(BaseKeyPairService):
         private_key = None
         private_key = None
         if not public_key_material:
         if not public_key_material:
             public_key_material, private_key = cb_helpers.generate_key_pair()
             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):
 class AWSVMFirewallService(BaseVMFirewallService):
@@ -321,7 +326,7 @@ class AWSBucketService(BaseBucketService):
 
 
     def create(self, name, location=None):
     def create(self, name, location=None):
         log.debug("Creating AWS Bucket with the params "
         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)
         AWSBucket.assert_valid_resource_name(name)
         loc_constraint = location or self.provider.region_name
         loc_constraint = location or self.provider.region_name
         # Due to an API issue in S3, specifying us-east-1 as a
         # 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 "
                   "[name: %s image: %s type: %s subnet: %s zone: %s "
                   "key pair: %s firewalls: %s user data: %s config %s "
                   "key pair: %s firewalls: %s user data: %s config %s "
                   "others: %s]", name, image, vm_type, subnet, zone,
                   "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)
         AWSInstance.assert_valid_resource_name(name)
 
 
         image_id = image.id if isinstance(image, MachineImage) else image
         image_id = image.id if isinstance(image, MachineImage) else image
@@ -615,7 +620,6 @@ class AWSNetworkingService(BaseNetworkingService):
         self._network_service = AWSNetworkService(self.provider)
         self._network_service = AWSNetworkService(self.provider)
         self._subnet_service = AWSSubnetService(self.provider)
         self._subnet_service = AWSSubnetService(self.provider)
         self._router_service = AWSRouterService(self.provider)
         self._router_service = AWSRouterService(self.provider)
-        self._gateway_service = AWSGatewayService(self.provider)
 
 
     @property
     @property
     def networks(self):
     def networks(self):
@@ -629,10 +633,6 @@ class AWSNetworkingService(BaseNetworkingService):
     def routers(self):
     def routers(self):
         return self._router_service
         return self._router_service
 
 
-    @property
-    def gateways(self):
-        return self._gateway_service
-
 
 
 class AWSNetworkService(BaseNetworkService):
 class AWSNetworkService(BaseNetworkService):
 
 
@@ -806,44 +806,3 @@ class AWSRouterService(BaseRouterService):
         if name:
         if name:
             cb_router.name = name
             cb_router.name = name
         return cb_router
         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.blob import BlockBlobService
 from azure.storage.table import TableService
 from azure.storage.table import TableService
 
 
+from . import helpers as azure_helpers
+
 log = logging.getLogger(__name__)
 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):
 class AzureClient(object):
     """
     """
@@ -147,32 +198,45 @@ class AzureClient(object):
             create_or_update(self.resource_group, name,
             create_or_update(self.resource_group, name,
                              parameters).result()
                              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. \
         return self.network_management_client.network_security_groups. \
             create_or_update(self.resource_group, name,
             create_or_update(self.resource_group, name,
                              {'tags': tags,
                              {'tags': tags,
                               'location': self.region_name}).result()
                               '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):
                                 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. \
         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()
                              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. \
         return self.network_management_client.security_rules. \
             delete(self.resource_group, vm_firewall, name).result()
             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):
     def list_containers(self, prefix=None):
         return self.blob_service.list_containers(prefix=prefix)
         return self.blob_service.list_containers(prefix=prefix)
 
 
@@ -222,23 +286,36 @@ class AzureClient(object):
         return self.compute_client.disks.create_or_update(
         return self.compute_client.disks.create_or_update(
             self.resource_group,
             self.resource_group,
             disk_name,
             disk_name,
-            params,
-            raw=True
-        )
+            params
+        ).result()
 
 
     def create_snapshot_disk(self, disk_name, params):
     def create_snapshot_disk(self, disk_name, params):
         return self.compute_client.disks.create_or_update(
         return self.compute_client.disks.create_or_update(
             self.resource_group,
             self.resource_group,
             disk_name,
             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)
             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(
         return self.compute_client.disks.update(
             self.resource_group,
             self.resource_group,
             disk_name,
             disk_name,
@@ -246,59 +323,14 @@ class AzureClient(object):
             raw=True
             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)
             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,
         return self.compute_client.snapshots.get(self.resource_group,
                                                  snapshot_name)
                                                  snapshot_name)
 
 
@@ -306,16 +338,20 @@ class AzureClient(object):
         return self.compute_client.snapshots.create_or_update(
         return self.compute_client.snapshots.create_or_update(
             self.resource_group,
             self.resource_group,
             snapshot_name,
             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(
         return self.compute_client.snapshots.update(
             self.resource_group,
             self.resource_group,
             snapshot_name,
             snapshot_name,
@@ -326,21 +362,28 @@ class AzureClient(object):
     def create_image(self, name, params):
     def create_image(self, name, params):
         return self.compute_client.images. \
         return self.compute_client.images. \
             create_or_update(self.resource_group, name,
             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):
     def list_images(self):
         return self.compute_client.images. \
         return self.compute_client.images. \
             list_by_resource_group(self.resource_group)
             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. \
         return self.compute_client.images. \
             create_or_update(self.resource_group, name,
             create_or_update(self.resource_group, name,
                              {
                              {
@@ -352,16 +395,60 @@ class AzureClient(object):
         return self.compute_client.virtual_machine_sizes. \
         return self.compute_client.virtual_machine_sizes. \
             list(self.region_name)
             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. \
         return self.network_management_client.subnets. \
             list(self.resource_group, network_name)
             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. \
         return self.network_management_client.subnets. \
             get(self.resource_group, network_name, subnet_name)
             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 \
         result_create = self.network_management_client \
             .subnets.create_or_update(
             .subnets.create_or_update(
                 self.resource_group,
                 self.resource_group,
@@ -373,7 +460,11 @@ class AzureClient(object):
 
 
         return subnet_info
         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 \
         result_delete = self.network_management_client \
             .subnets.delete(
             .subnets.delete(
                 self.resource_group,
                 self.resource_group,
@@ -382,24 +473,54 @@ class AzureClient(object):
             )
             )
         result_delete.wait()
         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):
     def list_vm(self):
         return self.compute_client.virtual_machines.list(
         return self.compute_client.virtual_machines.list(
             self.resource_group
             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(
         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(
         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(
         return self.compute_client.virtual_machines.get(
             self.resource_group,
             self.resource_group,
             vm_name,
             vm_name,
@@ -409,42 +530,66 @@ class AzureClient(object):
     def create_vm(self, vm_name, params):
     def create_vm(self, vm_name, params):
         return self.compute_client.virtual_machines. \
         return self.compute_client.virtual_machines. \
             create_or_update(self.resource_group,
             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. \
         return self.compute_client.virtual_machines. \
             create_or_update(self.resource_group,
             create_or_update(self.resource_group,
                              vm_name, params, raw=True)
                              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. \
         self.compute_client. \
             virtual_machines.deallocate(self.resource_group,
             virtual_machines.deallocate(self.resource_group,
                                         vm_name).wait()
                                         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. \
         self.compute_client.virtual_machines. \
             generalize(self.resource_group, vm_name)
             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. \
         self.compute_client.virtual_machines. \
             start(self.resource_group,
             start(self.resource_group,
                   vm_name).wait()
                   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. \
         self.compute_client.virtual_machines. \
             create_or_update(self.resource_group,
             create_or_update(self.resource_group,
                              vm_name, tags).result()
                              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. \
         self.network_management_client. \
             network_interfaces.delete(self.resource_group,
             network_interfaces.delete(self.resource_group,
                                       nic_name).wait()
                                       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. \
         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. \
         async_nic_creation = self.network_management_client. \
             network_interfaces.create_or_update(
             network_interfaces.create_or_update(
                 self.resource_group,
                 self.resource_group,
@@ -455,16 +600,12 @@ class AzureClient(object):
         return nic_info
         return nic_info
 
 
     def create_nic(self, nic_name, params):
     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. \
         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):
     def create_public_key(self, entity):
         return self.table_service. \
         return self.table_service. \
@@ -494,8 +635,11 @@ class AzureClient(object):
             route_tables.delete(self.resource_group, route_table_name
             route_tables.delete(self.resource_group, route_table_name
                                 ).wait()
                                 ).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(
         subnet_info = self.network_management_client.subnets.get(
             self.resource_group,
             self.resource_group,
@@ -517,8 +661,11 @@ class AzureClient(object):
 
 
         return subnet_info
         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(
         subnet_info = self.network_management_client.subnets.get(
             self.resource_group,
             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):
 def filter_by_tag(list_items, filters):
     """
     """
     This function filter items on the tags
     This function filter items on the tags
@@ -29,7 +32,7 @@ def parse_url(template_url, original_url):
     template_url_parts = template_url.split('/')
     template_url_parts = template_url.split('/')
     original_url_parts = original_url.split('/')
     original_url_parts = original_url.split('/')
     if len(template_url_parts) != len(original_url_parts):
     if len(template_url_parts) != len(original_url_parts):
-        raise Exception('Invalid url parameter passed')
+        raise InvalidValueException(template_url, original_url)
     resource_param = {}
     resource_param = {}
     for key, value in zip(template_url_parts, original_url_parts):
     for key, value in zip(template_url_parts, original_url_parts):
         if key.startswith('{') and key.endswith('}'):
         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 collections
 import logging
 import logging
-import time
 import uuid
 import uuid
 
 
 from azure.common import AzureException
 from azure.common import AzureException
@@ -12,8 +11,8 @@ from azure.mgmt.network.models import NetworkSecurityGroup
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo, \
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo, \
     BaseBucket, BaseBucketContainer, BaseBucketObject, BaseFloatingIP, \
     BaseBucket, BaseBucketContainer, BaseBucketObject, BaseFloatingIP, \
-    BaseFloatingIPContainer, BaseInstance, BaseInternetGateway, BaseKeyPair, \
-    BaseLaunchConfig, \
+    BaseFloatingIPContainer, BaseGatewayContainer, BaseInstance, \
+    BaseInternetGateway, BaseKeyPair, BaseLaunchConfig, \
     BaseMachineImage, BaseNetwork, BasePlacementZone, BaseRegion, BaseRouter, \
     BaseMachineImage, BaseNetwork, BasePlacementZone, BaseRegion, BaseRouter, \
     BaseSnapshot, BaseSubnet, BaseVMFirewall, BaseVMFirewallRule, \
     BaseSnapshot, BaseSubnet, BaseVMFirewall, BaseVMFirewallRule, \
     BaseVMFirewallRuleContainer, BaseVMType, BaseVolume, ClientPagedResultList
     BaseVMFirewallRuleContainer, BaseVMType, BaseVolume, ClientPagedResultList
@@ -26,55 +25,14 @@ from msrestazure.azure_exceptions import CloudError
 
 
 import pysftp
 import pysftp
 
 
-from . import helpers as azure_helpers
-
 log = logging.getLogger(__name__)
 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):
 class AzureVMFirewall(BaseVMFirewall):
     def __init__(self, provider, vm_firewall):
     def __init__(self, provider, vm_firewall):
         super(AzureVMFirewall, self).__init__(provider, vm_firewall)
         super(AzureVMFirewall, self).__init__(provider, vm_firewall)
         self._vm_firewall = 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)
         self._rule_container = AzureVMFirewallRuleContainer(provider, self)
 
 
     @property
     @property
@@ -87,7 +45,7 @@ class AzureVMFirewall(BaseVMFirewall):
 
 
     @property
     @property
     def id(self):
     def id(self):
-        return self._vm_firewall.name
+        return self._vm_firewall.id
 
 
     @property
     @property
     def name(self):
     def name(self):
@@ -97,13 +55,12 @@ class AzureVMFirewall(BaseVMFirewall):
     def name(self, value):
     def name(self, value):
         self.assert_valid_resource_name(value)
         self.assert_valid_resource_name(value)
         self._vm_firewall.tags.update(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
     @property
     def description(self):
     def description(self):
-        return self._vm_firewall.tags.get('Description', None)
+        return self._vm_firewall.tags.get('Description')
 
 
     @description.setter
     @description.setter
     def description(self, value):
     def description(self, value):
@@ -117,13 +74,7 @@ class AzureVMFirewall(BaseVMFirewall):
         return self._rule_container
         return self._rule_container
 
 
     def delete(self):
     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):
     def refresh(self):
         """
         """
@@ -221,7 +172,7 @@ class AzureVMFirewallRule(BaseVMFirewallRule):
 
 
     @property
     @property
     def id(self):
     def id(self):
-        return self._rule.name
+        return self._rule.id
 
 
     @property
     @property
     def direction(self):
     def direction(self):
@@ -238,12 +189,13 @@ class AzureVMFirewallRule(BaseVMFirewallRule):
 
 
     @property
     @property
     def from_port(self):
     def from_port(self):
-        return self._port_range_tuple().from_port
+        return self._port_range_tuple.from_port
 
 
     @property
     @property
     def to_port(self):
     def to_port(self):
-        return self._port_range_tuple().to_port
+        return self._port_range_tuple.to_port
 
 
+    @property
     def _port_range_tuple(self):
     def _port_range_tuple(self):
         if self._rule.destination_port_range == '*':
         if self._rule.destination_port_range == '*':
             return PortRange(1, 65535)
             return PortRange(1, 65535)
@@ -268,7 +220,7 @@ class AzureVMFirewallRule(BaseVMFirewallRule):
         self._provider.azure_client. \
         self._provider.azure_client. \
             delete_vm_firewall_rule(self.id, vm_firewall)
             delete_vm_firewall_rule(self.id, vm_firewall)
         for i, o in enumerate(self.firewall._vm_firewall.security_rules):
         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]
                 del self.firewall._vm_firewall.security_rules[i]
                 break
                 break
 
 
@@ -349,13 +301,8 @@ class AzureBucketObject(BaseBucketObject):
         :rtype: bool
         :rtype: bool
         :return: True if successful
         :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):
     def generate_url(self, expires_in=0):
         """
         """
@@ -386,12 +333,7 @@ class AzureBucket(BaseBucket):
         """
         """
         Delete this bucket.
         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):
     def exists(self, name):
         """
         """
@@ -478,7 +420,7 @@ class AzureVolume(BaseVolume):
 
 
     @property
     @property
     def id(self):
     def id(self):
-        return self._volume.name
+        return self._volume.id
 
 
     @property
     @property
     def resource_id(self):
     def resource_id(self):
@@ -535,13 +477,7 @@ class AzureVolume(BaseVolume):
 
 
     @property
     @property
     def source(self):
     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
     @property
     def attachments(self):
     def attachments(self):
@@ -554,11 +490,7 @@ class AzureVolume(BaseVolume):
         :return:
         :return:
         """
         """
         if self._volume.managed_by:
         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:
         else:
             return None
             return None
 
 
@@ -566,25 +498,20 @@ class AzureVolume(BaseVolume):
         """
         """
         Attach this volume to an instance.
         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):
     def detach(self, force=False):
         """
         """
@@ -595,8 +522,7 @@ class AzureVolume(BaseVolume):
                 if item.managed_disk and \
                 if item.managed_disk and \
                                 item.managed_disk.id == self.resource_id:
                                 item.managed_disk.id == self.resource_id:
                     vm.storage_profile.data_disks.remove(item)
                     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):
     def create_snapshot(self, name, description=None):
         """
         """
@@ -608,13 +534,7 @@ class AzureVolume(BaseVolume):
         """
         """
         Delete this volume.
         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
     @property
     def state(self):
     def state(self):
@@ -658,7 +578,7 @@ class AzureSnapshot(BaseSnapshot):
 
 
     @property
     @property
     def id(self):
     def id(self):
-        return self._snapshot.name
+        return self._snapshot.id
 
 
     @property
     @property
     def resource_id(self):
     def resource_id(self):
@@ -702,10 +622,7 @@ class AzureSnapshot(BaseSnapshot):
 
 
     @property
     @property
     def volume_id(self):
     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
     @property
     def create_time(self):
     def create_time(self):
@@ -735,12 +652,7 @@ class AzureSnapshot(BaseSnapshot):
         """
         """
         Delete this snapshot.
         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,
     def create_volume(self, placement=None,
                       size=None, volume_type=None, iops=None):
                       size=None, volume_type=None, iops=None):
@@ -748,7 +660,7 @@ class AzureSnapshot(BaseSnapshot):
         Create a new Volume from this Snapshot.
         Create a new Volume from this Snapshot.
         """
         """
         return self._provider.storage.volumes. \
         return self._provider.storage.volumes. \
-            create(self.id, self.size,
+            create(self.name, self.size,
                    zone=placement, snapshot=self)
                    zone=placement, snapshot=self)
 
 
 
 
@@ -775,7 +687,7 @@ class AzureMachineImage(BaseMachineImage):
         :rtype: ``str``
         :rtype: ``str``
         :return: ID for this instance as returned by the cloud middleware.
         :return: ID for this instance as returned by the cloud middleware.
         """
         """
-        return self._image.name
+        return self._image.id
 
 
     @property
     @property
     def resource_id(self):
     def resource_id(self):
@@ -850,8 +762,7 @@ class AzureMachineImage(BaseMachineImage):
         for its latest state.
         for its latest state.
         """
         """
         try:
         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
             self._state = self._image.provisioning_state
         except CloudError as cloudError:
         except CloudError as cloudError:
             log.exception(cloudError.message)
             log.exception(cloudError.message)
@@ -859,6 +770,32 @@ class AzureMachineImage(BaseMachineImage):
             self._state = "unknown"
             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):
 class AzureNetwork(BaseNetwork):
     NETWORK_STATE_MAP = {
     NETWORK_STATE_MAP = {
         'InProgress': NetworkState.PENDING,
         'InProgress': NetworkState.PENDING,
@@ -871,10 +808,11 @@ class AzureNetwork(BaseNetwork):
         self._state = self._network.provisioning_state
         self._state = self._network.provisioning_state
         if not self._network.tags:
         if not self._network.tags:
             self._network.tags = {}
             self._network.tags = {}
+        self._gateway_service = AzureGatewayContainer(provider, self)
 
 
     @property
     @property
     def id(self):
     def id(self):
-        return self._network.name
+        return self._network.id
 
 
     @property
     @property
     def resource_id(self):
     def resource_id(self):
@@ -940,13 +878,7 @@ class AzureNetwork(BaseNetwork):
         """
         """
         Delete an existing network.
         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
     @property
     def subnets(self):
     def subnets(self):
@@ -967,6 +899,10 @@ class AzureNetwork(BaseNetwork):
         return self._provider.networking.subnets. \
         return self._provider.networking.subnets. \
             create(network=self.id, cidr_block=cidr_block, name=name)
             create(network=self.id, cidr_block=cidr_block, name=name)
 
 
+    @property
+    def gateways(self):
+        return self._gateway_service
+
 
 
 class AzureFloatingIPContainer(BaseFloatingIPContainer):
 class AzureFloatingIPContainer(BaseFloatingIPContainer):
 
 
@@ -977,7 +913,7 @@ class AzureFloatingIPContainer(BaseFloatingIPContainer):
     def get(self, fip_id):
     def get(self, fip_id):
         log.debug("Getting Azure Floating IP container with the id: %s",
         log.debug("Getting Azure Floating IP container with the id: %s",
                   fip_id)
                   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
         return fip[0] if fip else None
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
@@ -1032,16 +968,11 @@ class AzureFloatingIP(BaseFloatingIP):
         """
         """
         Delete an existing floating ip.
         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):
     def refresh(self):
         net = self._provider.networking.networks.get(self._network_id)
         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)
         fip = gw.floating_ips.get(self.id)
         self._ip = fip._ip
         self._ip = fip._ip
 
 
@@ -1120,14 +1051,10 @@ class AzureSubnet(BaseSubnet):
         super(AzureSubnet, self).__init__(provider)
         super(AzureSubnet, self).__init__(provider)
         self._subnet = subnet
         self._subnet = subnet
         self._state = self._subnet.provisioning_state
         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
     @property
     def id(self):
     def id(self):
-        return self.network_id + '|$|' + self._subnet.name
+        return self._subnet.id
 
 
     @property
     @property
     def resource_id(self):
     def resource_id(self):
@@ -1144,8 +1071,9 @@ class AzureSubnet(BaseSubnet):
 
 
     @property
     @property
     def zone(self):
     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]
         return region.zones[0]
 
 
     @property
     @property
@@ -1154,26 +1082,18 @@ class AzureSubnet(BaseSubnet):
 
 
     @property
     @property
     def network_id(self):
     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):
     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
     @property
     def state(self):
     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):
     def refresh(self):
         """
         """
@@ -1181,12 +1101,12 @@ class AzureSubnet(BaseSubnet):
         for its latest state.
         for its latest state.
         """
         """
         try:
         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:
         except (CloudError, ValueError) as cloudError:
             log.exception(cloudError.message)
             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
             # set the state to unknown
             self._state = 'unknown'
             self._state = 'unknown'
 
 
@@ -1215,52 +1135,31 @@ class AzureInstance(BaseInstance):
         super(AzureInstance, self).__init__(provider)
         super(AzureInstance, self).__init__(provider)
         self._vm = vm_instance
         self._vm = vm_instance
         self._update_state()
         self._update_state()
-        self._get_network_attributes()
         if not self._vm.tags:
         if not self._vm.tags:
             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
     @property
     def id(self):
     def id(self):
         """
         """
         Get the instance identifier.
         Get the instance identifier.
         """
         """
-        return self._vm.name
+        return self._vm.id
 
 
     @property
     @property
     def resource_id(self):
     def resource_id(self):
@@ -1291,14 +1190,18 @@ class AzureInstance(BaseInstance):
         """
         """
         Get all the public IP addresses for this instance.
         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
     @property
     def private_ips(self):
     def private_ips(self):
         """
         """
         Get all the private IP addresses for this instance.
         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
     @property
     def vm_type_id(self):
     def vm_type_id(self):
@@ -1331,41 +1234,29 @@ class AzureInstance(BaseInstance):
         """
         """
         self._provider.azure_client.deallocate_vm(self.id)
         self._provider.azure_client.deallocate_vm(self.id)
         self._provider.azure_client.delete_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:
         for nic_id in self._nic_ids:
             self._provider.azure_client.delete_nic(nic_id)
             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:
         for data_disk in self._vm.storage_profile.data_disks:
             if data_disk.managed_disk:
             if data_disk.managed_disk:
-                disk_params = azure_helpers.\
-                    parse_url(VOLUME_RESOURCE_ID,
-                              data_disk.managed_disk.id)
                 disk = self._provider.azure_client.\
                 disk = self._provider.azure_client.\
-                    get_disk(disk_params.get(VOLUME_NAME))
+                    get_disk(data_disk.managed_disk.id)
                 if disk and disk.tags \
                 if disk and disk.tags \
                         and disk.tags.get('delete_on_terminate',
                         and disk.tags.get('delete_on_terminate',
                                           'False') == 'True':
                                           'False') == 'True':
                     self._provider.azure_client.\
                     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:
         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. \
             self._provider.azure_client. \
-                delete_disk(disk_params.get(VOLUME_NAME))
+                delete_disk(self._vm.storage_profile.os_disk.managed_disk.id)
 
 
     @property
     @property
     def image_id(self):
     def image_id(self):
         """
         """
         Get the image ID for this insance.
         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
     @property
     def zone_id(self):
     def zone_id(self):
@@ -1377,11 +1268,13 @@ class AzureInstance(BaseInstance):
     @property
     @property
     def vm_firewalls(self):
     def vm_firewalls(self):
         return [self._provider.security.vm_firewalls.get(group_id)
         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
     @property
     def vm_firewall_ids(self):
     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
     @property
     def key_pair_name(self):
     def key_pair_name(self):
@@ -1392,16 +1285,15 @@ class AzureInstance(BaseInstance):
 
 
     def create_image(self, name, private_key_path=None):
     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)
         self.assert_valid_resource_name(name)
@@ -1409,8 +1301,6 @@ class AzureInstance(BaseInstance):
         if not self._state == 'VM generalized':
         if not self._state == 'VM generalized':
             if not self._state == 'VM running':
             if not self._state == 'VM running':
                 self._provider.azure_client.start_vm(self.id)
                 self._provider.azure_client.start_vm(self.id)
-                time.sleep(10)  # Some time is required
-                self._get_network_attributes()
 
 
             # if private_key_path:
             # if private_key_path:
             self._deprovision(private_key_path)
             self._deprovision(private_key_path)
@@ -1424,11 +1314,8 @@ class AzureInstance(BaseInstance):
             },
             },
             'tags': {'Name': name}
             '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)
         return AzureMachineImage(self._provider, image)
 
 
     def _deprovision(self, private_key_path):
     def _deprovision(self, private_key_path):
@@ -1447,22 +1334,25 @@ class AzureInstance(BaseInstance):
         """
         """
         Attaches public ip to the instance.
         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 = {
         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):
     def remove_floating_ip(self, floating_ip):
         """
         """
         Remove a public IP address from this instance.
         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:
         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
                 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):
     def add_vm_firewall(self, fw):
         '''
         '''
@@ -1478,17 +1368,13 @@ class AzureInstance(BaseInstance):
         '''
         '''
         fw = (self._provider.security.vm_firewalls.get(fw)
         fw = (self._provider.security.vm_firewalls.get(fw)
               if isinstance(fw, str) else 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:
         if not nic.network_security_group:
             nic.network_security_group = NetworkSecurityGroup()
             nic.network_security_group = NetworkSecurityGroup()
             nic.network_security_group.id = fw.resource_id
             nic.network_security_group.id = fw.resource_id
         else:
         else:
-            fw_url_params = azure_helpers.\
-                parse_url(VM_FIREWALL_RESOURCE_ID,
-                          nic.network_security_group.id)
             existing_fw = self._provider.security.\
             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.\
             new_fw = self._provider.security.vm_firewalls.\
                 create('{0}-{1}'.format(fw.name, existing_fw.name),
                 create('{0}-{1}'.format(fw.name, existing_fw.name),
                        'Merged security groups {0} and {1}'.
                        'Merged security groups {0} and {1}'.
@@ -1497,7 +1383,7 @@ class AzureInstance(BaseInstance):
             new_fw.add_rule(src_dest_fw=existing_fw)
             new_fw.add_rule(src_dest_fw=existing_fw)
             nic.network_security_group.id = new_fw.resource_id
             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):
     def remove_vm_firewall(self, fw):
 
 
@@ -1513,13 +1399,13 @@ class AzureInstance(BaseInstance):
         else we are ignoring.
         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)
         fw = (self._provider.security.vm_firewalls.get(fw)
               if isinstance(fw, str) else fw)
               if isinstance(fw, str) else fw)
         if nic.network_security_group and \
         if nic.network_security_group and \
                 nic.network_security_group.id == fw.resource_id:
                 nic.network_security_group.id == fw.resource_id:
             nic.network_security_group = None
             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):
     def _update_state(self):
         """
         """
@@ -1554,7 +1440,6 @@ class AzureInstance(BaseInstance):
             if not self._vm.tags:
             if not self._vm.tags:
                 self._vm.tags = {}
                 self._vm.tags = {}
             self._update_state()
             self._update_state()
-            self._get_network_attributes()
         except (CloudError, ValueError) as cloudError:
         except (CloudError, ValueError) as cloudError:
             log.exception(cloudError.message)
             log.exception(cloudError.message)
             # The volume no longer exists and cannot be refreshed.
             # The volume no longer exists and cannot be refreshed.
@@ -1637,12 +1522,7 @@ class AzureKeyPair(BaseKeyPair):
         return self._key_pair.Name
         return self._key_pair.Name
 
 
     def delete(self):
     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):
 class AzureRouter(BaseRouter):
@@ -1697,26 +1577,17 @@ class AzureRouter(BaseRouter):
         return None
         return None
 
 
     def delete(self):
     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):
     def attach_subnet(self, subnet):
-        subnet_id_parts = subnet.id.split('|$|')
-        if (len(subnet_id_parts) != 2):
-            pass
         self._provider.azure_client. \
         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.resource_id)
         self.refresh()
         self.refresh()
 
 
     def detach_subnet(self, subnet):
     def detach_subnet(self, subnet):
-        subnet_id_parts = subnet.id.split('|$|')
-        if (len(subnet_id_parts) != 2):
-            pass
         self._provider.azure_client. \
         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.resource_id)
         self.refresh()
         self.refresh()
 
 
@@ -1732,7 +1603,6 @@ class AzureInternetGateway(BaseInternetGateway):
         super(AzureInternetGateway, self).__init__(provider)
         super(AzureInternetGateway, self).__init__(provider)
         self._gateway = gateway
         self._gateway = gateway
         self._name = None
         self._name = None
-        self._network_id = None
         self._network_id = gateway_net.id if isinstance(
         self._network_id = gateway_net.id if isinstance(
             gateway_net, AzureNetwork) else gateway_net
             gateway_net, AzureNetwork) else gateway_net
         self._state = ''
         self._state = ''

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

@@ -3,18 +3,20 @@ import logging
 import uuid
 import uuid
 
 
 from azure.common import AzureException
 from azure.common import AzureException
+from azure.mgmt.compute.models import DiskCreateOption
 
 
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import ClientPagedResultList, \
 from cloudbridge.cloud.base.resources import ClientPagedResultList, \
     ServerPagedResultList
     ServerPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService, \
 from cloudbridge.cloud.base.services import BaseBucketService, \
-    BaseComputeService, BaseGatewayService, \
+    BaseComputeService, \
     BaseImageService, BaseInstanceService, BaseKeyPairService, \
     BaseImageService, BaseInstanceService, BaseKeyPairService, \
     BaseNetworkService, BaseNetworkingService, BaseRegionService, \
     BaseNetworkService, BaseNetworkingService, BaseRegionService, \
     BaseRouterService, BaseSecurityService, BaseSnapshotService, \
     BaseRouterService, BaseSecurityService, BaseSnapshotService, \
     BaseStorageService, BaseSubnetService, BaseVMFirewallService, \
     BaseStorageService, BaseSubnetService, BaseVMFirewallService, \
     BaseVMTypeService, BaseVolumeService
     BaseVMTypeService, BaseVolumeService
-from cloudbridge.cloud.interfaces import InvalidConfigurationException
+from cloudbridge.cloud.interfaces.exceptions import \
+    DuplicateResourceException, InvalidValueException
 from cloudbridge.cloud.interfaces.resources import MachineImage, \
 from cloudbridge.cloud.interfaces.resources import MachineImage, \
     Network, PlacementZone, Snapshot, Subnet, VMFirewall, VMType, Volume
     Network, PlacementZone, Snapshot, Subnet, VMFirewall, VMType, Volume
 
 
@@ -22,7 +24,7 @@ from msrestazure.azure_exceptions import CloudError
 
 
 from . import helpers as azure_helpers
 from . import helpers as azure_helpers
 from .resources import AzureBucket, \
 from .resources import AzureBucket, \
-    AzureInstance, AzureInternetGateway, AzureKeyPair, \
+    AzureInstance, AzureKeyPair, \
     AzureLaunchConfig, AzureMachineImage, AzureNetwork, \
     AzureLaunchConfig, AzureMachineImage, AzureNetwork, \
     AzureRegion, AzureRouter, AzureSnapshot, AzureSubnet, \
     AzureRegion, AzureRouter, AzureSnapshot, AzureSubnet, \
     AzureVMFirewall, AzureVMType, AzureVolume
     AzureVMFirewall, AzureVMType, AzureVolume
@@ -55,10 +57,9 @@ class AzureVMFirewallService(BaseVMFirewallService):
         try:
         try:
             fws = self.provider.azure_client.get_vm_firewall(fw_id)
             fws = self.provider.azure_client.get_vm_firewall(fw_id)
             return AzureVMFirewall(self.provider, fws)
             return AzureVMFirewall(self.provider, fws)
-
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
             return None
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
@@ -86,7 +87,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
             rule.priority = rule.priority - 61440
             rule.priority = rule.priority - 61440
             rule.access = "Deny"
             rule.access = "Deny"
             self._provider.azure_client.create_vm_firewall_rule(
             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
         # Add a new custom rule allowing all outbound traffic to the internet
         parameters = {"priority": 3000,
         parameters = {"priority": 3000,
@@ -98,7 +99,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
                       "access": "Allow",
                       "access": "Allow",
                       "direction": "Outbound"}
                       "direction": "Outbound"}
         result = self._provider.azure_client.create_vm_firewall_rule(
         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)
         fw.security_rules.append(result)
 
 
         cb_fw = AzureVMFirewall(self.provider, fw)
         cb_fw = AzureVMFirewall(self.provider, fw)
@@ -119,13 +120,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
         return ClientPagedResultList(self.provider, fws)
         return ClientPagedResultList(self.provider, fws)
 
 
     def delete(self, group_id):
     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):
 class AzureKeyPairService(BaseKeyPairService):
@@ -175,7 +170,7 @@ class AzureKeyPairService(BaseKeyPairService):
         key_pair = self.get(name)
         key_pair = self.get(name)
 
 
         if key_pair:
         if key_pair:
-            raise Exception(
+            raise DuplicateResourceException(
                 'Keypair already exists with name {0}'.format(name))
                 'Keypair already exists with name {0}'.format(name))
 
 
         private_key = None
         private_key = None
@@ -207,7 +202,6 @@ class AzureBucketService(BaseBucketService):
         try:
         try:
             bucket = self.provider.azure_client.get_container(bucket_id)
             bucket = self.provider.azure_client.get_container(bucket_id)
             return AzureBucket(self.provider, bucket)
             return AzureBucket(self.provider, bucket)
-
         except AzureException as error:
         except AzureException as error:
             log.exception(error)
             log.exception(error)
             return None
             return None
@@ -276,9 +270,9 @@ class AzureVolumeService(BaseVolumeService):
         try:
         try:
             volume = self.provider.azure_client.get_disk(volume_id)
             volume = self.provider.azure_client.get_disk(volume_id)
             return AzureVolume(self.provider, volume)
             return AzureVolume(self.provider, volume)
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
@@ -321,13 +315,14 @@ class AzureVolumeService(BaseVolumeService):
                 'location':
                 'location':
                     zone_id or self.provider.azure_client.region_name,
                     zone_id or self.provider.azure_client.region_name,
                 'creation_data': {
                 'creation_data': {
-                    'create_option': 'copy',
+                    'create_option': DiskCreateOption.copy,
                     'source_uri': snapshot.resource_id
                     'source_uri': snapshot.resource_id
                 },
                 },
                 'tags': tags
                 'tags': tags
             }
             }
 
 
-            self.provider.azure_client.create_snapshot_disk(disk_name, params)
+            disk = self.provider.azure_client.create_snapshot_disk(disk_name,
+                                                                   params)
 
 
         else:
         else:
             params = {
             params = {
@@ -335,13 +330,14 @@ class AzureVolumeService(BaseVolumeService):
                     zone_id or self.provider.region_name,
                     zone_id or self.provider.region_name,
                 'disk_size_gb': size,
                 'disk_size_gb': size,
                 'creation_data': {
                 'creation_data': {
-                    'create_option': 'empty'
+                    'create_option': DiskCreateOption.empty
                 },
                 },
                 'tags': tags}
                 '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)
         cb_vol = AzureVolume(self.provider, azure_vol)
 
 
         return cb_vol
         return cb_vol
@@ -358,9 +354,9 @@ class AzureSnapshotService(BaseSnapshotService):
         try:
         try:
             snapshot = self.provider.azure_client.get_snapshot(ss_id)
             snapshot = self.provider.azure_client.get_snapshot(ss_id)
             return AzureSnapshot(self.provider, snapshot)
             return AzureSnapshot(self.provider, snapshot)
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
@@ -403,19 +399,16 @@ class AzureSnapshotService(BaseSnapshotService):
         params = {
         params = {
             'location': self.provider.azure_client.region_name,
             'location': self.provider.azure_client.region_name,
             'creation_data': {
             'creation_data': {
-                'create_option': 'Copy',
+                'create_option': DiskCreateOption.copy,
                 'source_uri': volume.resource_id
                 'source_uri': volume.resource_id
             },
             },
             'disk_size_gb': volume.size,
             'disk_size_gb': volume.size,
             'tags': tags
             '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):
 class AzureComputeService(BaseComputeService):
@@ -467,12 +460,18 @@ class AzureInstanceService(BaseInstanceService):
             # but useless. However, this will allow an instance to be launched
             # but useless. However, this will allow an instance to be launched
             # without specifying a keypair, so users may still be able to login
             # without specifying a keypair, so users may still be able to login
             # if they have a preinstalled keypair/password baked into the image
             # 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):
         if not isinstance(image, AzureMachineImage):
             raise Exception("Provided image %s is not a valid azure image"
             raise Exception("Provided image %s is not a valid azure image"
                             % image)
                             % image)
@@ -492,13 +491,8 @@ class AzureInstanceService(BaseInstanceService):
             self._resolve_launch_options(instance_name,
             self._resolve_launch_options(instance_name,
                                          subnet, zone_id, vm_firewalls)
                                          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 = {
         nic_params = {
                 'location': self._provider.region_name,
                 'location': self._provider.region_name,
@@ -549,34 +543,31 @@ class AzureInstanceService(BaseInstanceService):
                     'id': nic_info.id
                     '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}
             'tags': {'Name': name}
         }
         }
 
 
         if key_pair:
         if key_pair:
             params['tags'].update(Key_Pair=key_pair.name)
             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:
         if user_data:
             custom_data = base64.b64encode(bytes(ud, 'utf-8'))
             custom_data = base64.b64encode(bytes(ud, 'utf-8'))
             params['os_profile']['custom_data'] = str(custom_data, '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)
         return AzureInstance(self.provider, vm)
 
 
     def _resolve_launch_options(self, name, subnet=None, zone_id=None,
     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
         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,
     def _process_block_device_mappings(self, launch_config,
                                        vm_name, zone=None):
                                        vm_name, zone=None):
         """
         """
@@ -617,71 +631,67 @@ class AzureInstanceService(BaseInstanceService):
         are requested (source is None and destination is VOLUME), they will be
         are requested (source is None and destination is VOLUME), they will be
         created and the relevant volume ids included in the mapping.
         created and the relevant volume ids included in the mapping.
         """
         """
-        disks = []
-        volumes_count = 0
+        data_disks = []
         root_disk_size = None
         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
             # In azure, there is no option to specify terminate disks
             # (similar to AWS delete_on_terminate) on VM delete.
             # (similar to AWS delete_on_terminate) on VM delete.
             # This method uses the azure tags functionality to store
             # This method uses the azure tags functionality to store
             # the  delete_on_terminate option when the virtual machine
             # the  delete_on_terminate option when the virtual machine
             # is deleted, we parse the tags and delete accordingly
             # 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 device.is_volume:
-                if not device.is_root:
+                if device.is_root:
+                    root_disk_size = device.size
+                else:
                     # In azure, os disk automatically created,
                     # In azure, os disk automatically created,
                     # we are ignoring the root disk, if specified
                     # we are ignoring the root disk, if specified
                     if isinstance(device.source, Snapshot):
                     if isinstance(device.source, Snapshot):
                         snapshot_vol = device.source.create_volume()
                         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):
                     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):
                     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:
                     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
             else:  # device is ephemeral
                 # in azure we cannot add the ephemeral disks explicitly
                 # in azure we cannot add the ephemeral disks explicitly
                 pass
                 pass
 
 
-        return disks, root_disk_size
+        return data_disks, root_disk_size
 
 
     def create_launch_config(self):
     def create_launch_config(self):
         return AzureLaunchConfig(self.provider)
         return AzureLaunchConfig(self.provider)
@@ -703,9 +713,9 @@ class AzureInstanceService(BaseInstanceService):
         try:
         try:
             vm = self.provider.azure_client.get_vm(instance_id)
             vm = self.provider.azure_client.get_vm(instance_id)
             return AzureInstance(self.provider, vm)
             return AzureInstance(self.provider, vm)
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
@@ -734,9 +744,9 @@ class AzureImageService(BaseImageService):
         try:
         try:
             image = self.provider.azure_client.get_image(image_id)
             image = self.provider.azure_client.get_image(image_id)
             return AzureMachineImage(self.provider, image)
             return AzureMachineImage(self.provider, image)
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
@@ -790,7 +800,6 @@ class AzureNetworkingService(BaseNetworkingService):
         self._network_service = AzureNetworkService(self.provider)
         self._network_service = AzureNetworkService(self.provider)
         self._subnet_service = AzureSubnetService(self.provider)
         self._subnet_service = AzureSubnetService(self.provider)
         self._router_service = AzureRouterService(self.provider)
         self._router_service = AzureRouterService(self.provider)
-        self._gateway_service = AzureGatewayService(self.provider)
 
 
     @property
     @property
     def networks(self):
     def networks(self):
@@ -804,10 +813,6 @@ class AzureNetworkingService(BaseNetworkingService):
     def routers(self):
     def routers(self):
         return self._router_service
         return self._router_service
 
 
-    @property
-    def gateways(self):
-        return self._gateway_service
-
 
 
 class AzureNetworkService(BaseNetworkService):
 class AzureNetworkService(BaseNetworkService):
     def __init__(self, provider):
     def __init__(self, provider):
@@ -817,10 +822,9 @@ class AzureNetworkService(BaseNetworkService):
         try:
         try:
             network = self.provider.azure_client.get_network(network_id)
             network = self.provider.azure_client.get_network(network_id)
             return AzureNetwork(self.provider, network)
             return AzureNetwork(self.provider, network)
-
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
             return None
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
@@ -862,22 +866,16 @@ class AzureNetworkService(BaseNetworkService):
             },
             },
             'tags': {'Name': name or AzureNetwork.CB_DEFAULT_NETWORK_NAME}
             '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
         return cb_network
 
 
     def delete(self, network_id):
     def delete(self, network_id):
         """
         """
         Delete an existing network.
         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):
 class AzureRegionService(BaseRegionService):
@@ -919,16 +917,12 @@ class AzureSubnetService(BaseSubnetService):
         :return:
         :return:
         """
         """
         try:
         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,
             return AzureSubnet(self.provider,
                                azure_subnet) if azure_subnet else None
                                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
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
             return None
 
 
     def list(self, network=None, limit=None, marker=None):
     def list(self, network=None, limit=None, marker=None):
@@ -948,7 +942,7 @@ class AzureSubnetService(BaseSubnetService):
         else:
         else:
             for net in self.provider.azure_client.list_networks():
             for net in self.provider.azure_client.list_networks():
                 result_list.extend(self.provider.azure_client.list_subnets(
                 result_list.extend(self.provider.azure_client.list_subnets(
-                    net.name
+                    net.id
                 ))
                 ))
         subnets = [AzureSubnet(self.provider, subnet)
         subnets = [AzureSubnet(self.provider, subnet)
                    for subnet in result_list]
                    for subnet in result_list]
@@ -980,59 +974,30 @@ class AzureSubnetService(BaseSubnetService):
         return AzureSubnet(self.provider, subnet_info)
         return AzureSubnet(self.provider, subnet_info)
 
 
     def get_or_create_default(self, zone=None):
     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
         # 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)
         # 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(
             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):
     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):
 class AzureRouterService(BaseRouterService):
@@ -1043,10 +1008,9 @@ class AzureRouterService(BaseRouterService):
         try:
         try:
             route = self.provider.azure_client.get_route_table(router_id)
             route = self.provider.azure_client.get_route_table(router_id)
             return AzureRouter(self.provider, route)
             return AzureRouter(self.provider, route)
-
-        except CloudError as cloudError:
+        except (CloudError, InvalidValueException) as cloudError:
             # Azure raises the cloud error if the resource not available
             # Azure raises the cloud error if the resource not available
-            log.exception(cloudError.message)
+            log.exception(cloudError)
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
@@ -1079,29 +1043,3 @@ class AzureRouterService(BaseRouterService):
         route = self.provider.azure_client. \
         route = self.provider.azure_client. \
             create_route_table(name, parameters)
             create_route_table(name, parameters)
         return AzureRouter(self.provider, route)
         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
     limit = limit or provider.config.default_result_limit
     is_truncated = len(objects) > 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,
     results = ServerPagedResultList(is_truncated,
                                     next_token,
                                     next_token,
                                     False)
                                     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 BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
 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 BaseInstance
 from cloudbridge.cloud.base.resources import BaseInternetGateway
 from cloudbridge.cloud.base.resources import BaseInternetGateway
 from cloudbridge.cloud.base.resources import BaseKeyPair
 from cloudbridge.cloud.base.resources import BaseKeyPair
@@ -397,19 +398,29 @@ class OpenStackInstance(BaseInstance):
         return OpenStackMachineImage(
         return OpenStackMachineImage(
             self._provider, self._provider.compute.images.get(image_id))
             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):
     def add_floating_ip(self, floating_ip):
         """
         """
         Add a floating IP address to this instance.
         Add a floating IP address to this instance.
         """
         """
         log.debug("Adding floating IP adress: %s", floating_ip)
         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):
     def remove_floating_ip(self, floating_ip):
         """
         """
         Remove a floating IP address from this instance.
         Remove a floating IP address from this instance.
         """
         """
         log.debug("Removing floating IP adress: %s", floating_ip)
         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):
     def add_vm_firewall(self, firewall):
         """
         """
@@ -711,6 +722,53 @@ class OpenStackSnapshot(BaseSnapshot):
         return cb_vol
         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):
 class OpenStackNetwork(BaseNetwork):
 
 
     # Ref: https://github.com/openstack/neutron/blob/master/neutron/plugins/
     # Ref: https://github.com/openstack/neutron/blob/master/neutron/plugins/
@@ -729,6 +787,7 @@ class OpenStackNetwork(BaseNetwork):
     def __init__(self, provider, network):
     def __init__(self, provider, network):
         super(OpenStackNetwork, self).__init__(provider)
         super(OpenStackNetwork, self).__init__(provider)
         self._network = network
         self._network = network
+        self._gateway_service = OpenStackGatewayContainer(provider, self)
 
 
     @property
     @property
     def id(self):
     def id(self):
@@ -795,6 +854,10 @@ class OpenStackNetwork(BaseNetwork):
             # subnet no longer exists
             # subnet no longer exists
             self._network.state = NetworkState.UNKNOWN
             self._network.state = NetworkState.UNKNOWN
 
 
+    @property
+    def gateways(self):
+        return self._gateway_service
+
 
 
 class OpenStackSubnet(BaseSubnet):
 class OpenStackSubnet(BaseSubnet):
 
 
@@ -912,8 +975,7 @@ class OpenStackFloatingIP(BaseFloatingIP):
     def refresh(self):
     def refresh(self):
         net = self._provider.networking.networks.get(
         net = self._provider.networking.networks.get(
             self._ip.floating_network_id)
             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)
         fip = gw.floating_ips.get(self.id)
         # pylint:disable=protected-access
         # pylint:disable=protected-access
         self._ip = fip._ip
         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.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseComputeService
 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 BaseImageService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseKeyPairService
 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 BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.base.services import BaseVolumeService
+from cloudbridge.cloud.interfaces.exceptions \
+    import DuplicateResourceException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import PlacementZone
@@ -167,29 +168,23 @@ class OpenStackKeyPairService(BaseKeyPairService):
         return ClientPagedResultList(self.provider, results)
         return ClientPagedResultList(self.provider, results)
 
 
     def create(self, name, public_key_material=None):
     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)
         log.debug("Creating a new key pair with the name: %s", name)
         OpenStackKeyPair.assert_valid_resource_name(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
         private_key = None
         if not public_key_material:
         if not public_key_material:
             public_key_material, private_key = cb_helpers.generate_key_pair()
             public_key_material, private_key = cb_helpers.generate_key_pair()
+
         kp = self.provider.nova.keypairs.create(name,
         kp = self.provider.nova.keypairs.create(name,
                                                 public_key=public_key_material)
                                                 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):
 class OpenStackVMFirewallService(BaseVMFirewallService):
@@ -539,7 +534,7 @@ class OpenStackRegionService(BaseRegionService):
 
 
     def get(self, region_id):
     def get(self, region_id):
         log.debug("Getting OpenStack Region with the id: %s", 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)
         return next(region, None)
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
@@ -782,7 +777,6 @@ class OpenStackNetworkingService(BaseNetworkingService):
         self._network_service = OpenStackNetworkService(self.provider)
         self._network_service = OpenStackNetworkService(self.provider)
         self._subnet_service = OpenStackSubnetService(self.provider)
         self._subnet_service = OpenStackSubnetService(self.provider)
         self._router_service = OpenStackRouterService(self.provider)
         self._router_service = OpenStackRouterService(self.provider)
-        self._gateway_service = OpenStackGatewayService(self.provider)
 
 
     @property
     @property
     def networks(self):
     def networks(self):
@@ -796,10 +790,6 @@ class OpenStackNetworkingService(BaseNetworkingService):
     def routers(self):
     def routers(self):
         return self._router_service
         return self._router_service
 
 
-    @property
-    def gateways(self):
-        return self._gateway_service
-
 
 
 class OpenStackNetworkService(BaseNetworkService):
 class OpenStackNetworkService(BaseNetworkService):
 
 
@@ -858,7 +848,7 @@ class OpenStackSubnetService(BaseSubnetService):
         if network:
         if network:
             network_id = (network.id if isinstance(network, OpenStackNetwork)
             network_id = (network.id if isinstance(network, OpenStackNetwork)
                           else network)
                           else network)
-            subnets = [subnet for subnet in self.list() if network_id ==
+            subnets = [subnet for subnet in self if network_id ==
                        subnet.network_id]
                        subnet.network_id]
         else:
         else:
             subnets = [OpenStackSubnet(self.provider, subnet) for subnet in
             subnets = [OpenStackSubnet(self.provider, subnet) for subnet in
@@ -898,11 +888,8 @@ class OpenStackSubnetService(BaseSubnetService):
             router = self.provider.networking.routers.create(
             router = self.provider.networking.routers.create(
                 network=net, name=OpenStackRouter.CB_DEFAULT_ROUTER_NAME)
                 network=net, name=OpenStackRouter.CB_DEFAULT_ROUTER_NAME)
             router.attach_subnet(sn)
             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)
             router.attach_gateway(gteway)
             return sn
             return sn
         except NeutronClientException:
         except NeutronClientException:
@@ -914,7 +901,7 @@ class OpenStackSubnetService(BaseSubnetService):
                      else subnet)
                      else subnet)
         self.provider.neutron.delete_subnet(subnet_id)
         self.provider.neutron.delete_subnet(subnet_id)
         # Adhere to the interface docs
         # Adhere to the interface docs
-        if subnet_id not in self.list():
+        if subnet_id not in self:
             return True
             return True
         return False
         return False
 
 
@@ -955,31 +942,3 @@ class OpenStackRouterService(BaseRouterService):
         body = {'router': {'name': name}} if name else None
         body = {'router': {'name': name}} if name else None
         router = self.provider.neutron.create_router(body)
         router = self.provider.neutron.create_router(body)
         return OpenStackRouter(self.provider, router.get('router'))
         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')
     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 = self.provider.networking.routers.create(network=net, name='my-router')
     router.attach_subnet(sn)
     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)
     router.attach_gateway(gateway)
 
 
 
 

+ 1 - 1
docs/topics/launch.rst

@@ -67,7 +67,7 @@ that subnet.
     # make sure subnet has internet access
     # make sure subnet has internet access
     router = self.provider.networking.routers.create(network=net, name='my-router')
     router = self.provider.networking.routers.create(network=net, name='my-router')
     router.attach_subnet(sn)
     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)
     router.attach_gateway(gateway)
 
 
     inst = provider.compute.instances.create(
     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)
     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 = provider.networking.routers.create(network=net, name='my-router')
     router.attach_subnet(sn)
     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)
     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 = provider.networking.routers.create(network=net, name='my-router')
     router.attach_subnet(sn)
     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)
     router.attach_gateway(gateway)
 
 
     fip = provider.networking.floating_ips.create()
     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]"
 -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-cinderclient>=1.9.0',
     'python-swiftclient>=3.2.0',
     'python-swiftclient>=3.2.0',
     'python-neutronclient>=6.0.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
 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
 # httpretty is required with/for moto 1.0.0 or AWS tests fail
 REQS_DEV = ([
 REQS_DEV = ([
     'tox>=2.1.1',
     'tox>=2.1.1',
-    'moto>=1.1.11',
+    'nose',
+    # 'moto>=1.1.11',  # until https://github.com/spulec/moto/issues/1396
     'docutils>=0.14',
     'docutils>=0.14',
     'imagesize>=0.7.1',
     'imagesize>=0.7.1',
     'jinja2>=2.9.6',
     'jinja2>=2.9.6',
     'sphinx>=1.3.1',
     'sphinx>=1.3.1',
+    'pydevd',
     'flake8>=3.3.0',
     'flake8>=3.3.0',
     'flake8-import-order>=0.12'] + REQS_FULL
     'flake8-import-order>=0.12'] + REQS_FULL
 )
 )
@@ -64,7 +65,7 @@ setup(
     description='A simple layer of abstraction over multiple cloud providers.',
     description='A simple layer of abstraction over multiple cloud providers.',
     author='Galaxy and GVL Projects',
     author='Galaxy and GVL Projects',
     author_email='help@genome.edu.au',
     author_email='help@genome.edu.au',
-    url='http://cloudbridge.readthedocs.org/',
+    url='http://cloudbridge.cloudve.org/',
     install_requires=REQS_FULL,
     install_requires=REQS_FULL,
     extras_require={
     extras_require={
         ':python_version=="2.7"': ['py2-ipaddress'],
         ':python_version=="2.7"': ['py2-ipaddress'],

+ 9 - 6
test/helpers/__init__.py

@@ -99,9 +99,12 @@ TEST_DATA_CONFIG = {
         "placement":
         "placement":
             os.environ.get('CB_PLACEMENT_AZURE', 'eastus'),
             os.environ.get('CB_PLACEMENT_AZURE', 'eastus'),
         "image":
         "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":
         "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_name = 'cb_testgwnet-{0}'.format(get_uuid())
     net = provider.networking.networks.create(
     net = provider.networking.networks.create(
         name=net_name, cidr_block='10.0.0.0/16')
         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):
 def delete_test_gateway(network, gateway):
     """
     """
     Delete the supplied network and 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(
 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,
         len(find_objs) == 1,
         "Find objects for %s does not return the expected object: %s. Got %s"
         "Find objects for %s does not return the expected object: %s. Got %s"
         % (type(obj).__name__, obj.name, find_objs))
         % (type(obj).__name__, obj.name, find_objs))
-    test.assertEqual(find_objs[0], obj)
+    test.assertEqual(find_objs[0].id, obj.id)
     return find_objs
     return find_objs
 
 
 
 
@@ -88,7 +88,7 @@ def check_find_non_existent(test, service):
 
 
 def check_get(test, service, obj):
 def check_get(test, service, obj):
     get_obj = service.get(obj.id)
     get_obj = service.get(obj.id)
-    test.assertEqual(get_obj, obj)
+    test.assertEqual(get_obj.id, obj.id)
     test.assertIsInstance(get_obj, type(obj))
     test.assertIsInstance(get_obj, type(obj))
     return get_obj
     return get_obj
 
 
@@ -164,13 +164,6 @@ def check_standard_behaviour(test, service, obj):
     obj_get = check_get(test, service, obj)
     obj_get = check_get(test, service, obj)
     check_get_non_existent(test, service)
     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(
     test.assertTrue(
         obj.id == objs_list[0].id == objs_iter[0].id ==
         obj.id == objs_list[0].id == objs_iter[0].id ==
         objs_find[0].id == obj_get.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):
 class CloudBlockStoreServiceTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['storage.volumes'])
     @helpers.skipIfNoService(['storage.volumes'])
     def test_crud_volume(self):
     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):
 class CloudFactoryTestCase(unittest.TestCase):
 
 
+    _multiprocess_can_split_ = True
+
     def test_create_provider_valid(self):
     def test_create_provider_valid(self):
         """
         """
         Creating a provider with a known name should return
         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):
 class CloudHelpersTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     def setUp(self):
     def setUp(self):
         super(CloudHelpersTestCase, self).setUp()
         super(CloudHelpersTestCase, self).setUp()
         self.objects = [DummyResult(1, "One"),
         self.objects = [DummyResult(1, "One"),

+ 3 - 2
test/test_compute_service.py

@@ -16,6 +16,8 @@ import six
 
 
 class CloudComputeServiceTestCase(ProviderTestBase):
 class CloudComputeServiceTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['compute.instances', 'networking.networks'])
     @helpers.skipIfNoService(['compute.instances', 'networking.networks'])
     def test_crud_instance(self):
     def test_crud_instance(self):
         name = "cb-instcrud-{0}".format(helpers.get_uuid())
         name = "cb-instcrud-{0}".format(helpers.get_uuid())
@@ -342,8 +344,7 @@ class CloudComputeServiceTestCase(ProviderTestBase):
             with helpers.cleanup_action(lambda: cleanup_router(router,
             with helpers.cleanup_action(lambda: cleanup_router(router,
                                                                gateway)):
                                                                gateway)):
                 router.attach_subnet(subnet)
                 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)
                 router.attach_gateway(gateway)
                 # check whether adding an elastic ip works
                 # check whether adding an elastic ip works
                 fip = gateway.floating_ips.create()
                 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):
 class CloudImageServiceTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['compute.images', 'networking.networks',
     @helpers.skipIfNoService(['compute.images', 'networking.networks',
                               'compute.instances'])
                               'compute.instances'])
     def test_create_and_list_image(self):
     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):
 class CloudInterfaceTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     def test_name_property(self):
     def test_name_property(self):
         """
         """
         Name should always return a value and should not raise an exception
         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):
 class CloudNetworkServiceTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['networking.networks'])
     @helpers.skipIfNoService(['networking.networks'])
     def test_crud_network(self):
     def test_crud_network(self):
 
 
@@ -99,6 +101,8 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                            "cb-crudsubnet", create_subnet, cleanup_subnet)
                            "cb-crudsubnet", create_subnet, cleanup_subnet)
 
 
     def test_crud_floating_ip(self):
     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):
         def create_fip(name):
             fip = gw.floating_ips.create()
             fip = gw.floating_ips.create()
@@ -107,8 +111,6 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
         def cleanup_fip(fip):
         def cleanup_fip(fip):
             gw.floating_ips.delete(fip.id)
             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(
         with helpers.cleanup_action(
                 lambda: helpers.delete_test_gateway(net, gw)):
                 lambda: helpers.delete_test_gateway(net, gw)):
             sit.check_crud(self, gw.floating_ips, FloatingIP,
             sit.check_crud(self, gw.floating_ips, FloatingIP,
@@ -181,8 +183,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
 #                     router.id, router.network_id))
 #                     router.id, router.network_id))
 
 
             router.attach_subnet(sn)
             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)
             router.attach_gateway(gteway)
             # TODO: add a check for routes after that's been implemented
             # 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):
 class CloudObjectLifeCycleTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['storage.volumes'])
     @helpers.skipIfNoService(['storage.volumes'])
     def test_object_life_cycle(self):
     def test_object_life_cycle(self):
         """
         """

+ 2 - 0
test/test_object_store_service.py

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

+ 2 - 0
test/test_region_service.py

@@ -9,6 +9,8 @@ import six
 
 
 class CloudRegionServiceTestCase(ProviderTestBase):
 class CloudRegionServiceTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['compute.regions'])
     @helpers.skipIfNoService(['compute.regions'])
     def test_get_and_list_regions(self):
     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
 from test.helpers import standard_interface_tests as sit
 
 
 import cloudbridge.cloud.base.helpers as cb_helpers
 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 KeyPair
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMFirewall
@@ -12,6 +13,8 @@ from cloudbridge.cloud.interfaces.resources import VMFirewallRule
 
 
 class CloudSecurityServiceTestCase(ProviderTestBase):
 class CloudSecurityServiceTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['security.key_pairs'])
     @helpers.skipIfNoService(['security.key_pairs'])
     def test_crud_key_pair_service(self):
     def test_crud_key_pair_service(self):
 
 
@@ -23,7 +26,7 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
 
 
         def extra_tests(kp):
         def extra_tests(kp):
             # Recreating existing keypair should raise an exception
             # Recreating existing keypair should raise an exception
-            with self.assertRaises(Exception):
+            with self.assertRaises(DuplicateResourceException):
                 self.provider.security.key_pairs.create(name=kp.name)
                 self.provider.security.key_pairs.create(name=kp.name)
 
 
         sit.check_crud(self, self.provider.security.key_pairs, KeyPair,
         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):
 class CloudVMTypeServiceTestCase(ProviderTestBase):
 
 
+    _multiprocess_can_split_ = True
+
     @helpers.skipIfNoService(['compute.vm_types'])
     @helpers.skipIfNoService(['compute.vm_types'])
     def test_vm_type_properties(self):
     def test_vm_type_properties(self):
 
 

+ 2 - 1
tox.ini

@@ -16,7 +16,8 @@ envlist = {py27,py36,pypy}-{aws,azure,gce,openstack}
 
 
 [testenv]
 [testenv]
 commands = flake8 cloudbridge test setup.py
 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 =
 setenv =
     MOTO_AMIS_PATH=./test/fixtures/custom_amis.json
     MOTO_AMIS_PATH=./test/fixtures/custom_amis.json
     aws: CB_TEST_PROVIDER=aws
     aws: CB_TEST_PROVIDER=aws