Просмотр исходного кода

Azure changes made for Merge
Router Service implemented for testing

jatin 8 лет назад
Родитель
Сommit
aac6d3e05a

+ 76 - 7
cloudbridge/cloud/providers/azure/azure_client.py

@@ -149,7 +149,8 @@ class AzureClient(object):
     def update_security_group_tags(self, name, tags):
         return self.network_management_client.network_security_groups. \
             create_or_update(self.resource_group, name,
-                             {'tags': tags}).result()
+                             {'tags': tags,
+                              'location': self.region_name}).result()
 
     def create_security_group_rule(self, security_group,
                                    rule_name, parameters):
@@ -279,10 +280,7 @@ class AzureClient(object):
     def update_network_tags(self, network_name, tags):
         return self.network_management_client.virtual_networks. \
             create_or_update(self.resource_group,
-                             network_name,
-                             {
-                                 'tags': tags
-                             })
+                             network_name, tags).result()
 
     def list_disks(self):
         return self.compute_client.disks. \
@@ -339,7 +337,8 @@ class AzureClient(object):
         return self.compute_client.images. \
             create_or_update(self.resource_group, name,
                              {
-                                 'tags': tags
+                                 'tags': tags,
+                                 'location': self.region_name
                              }).result()
 
     def list_instance_types(self):
@@ -422,7 +421,7 @@ class AzureClient(object):
     def update_vm_tags(self, vm_name, tags):
         self.compute_client.virtual_machines. \
             create_or_update(self.resource_group,
-                             vm_name, {'tags': tags})
+                             vm_name, tags).result()
 
     def delete_nic(self, nic_name):
         self.network_management_client. \
@@ -483,3 +482,73 @@ class AzureClient(object):
             if not next_marker:
                 break
         return items
+
+    def delete_route_table(self, route_table_name):
+        self.network_management_client. \
+            route_tables.delete(self.resource_group, route_table_name
+                                ).wait()
+
+    def attach_subnet_to_route_table(self, network_name,
+                                     subnet_name, route_table_id):
+
+        subnet_info = self.network_management_client.subnets.get(
+            self.resource_group,
+            network_name,
+            subnet_name
+        )
+        if subnet_info:
+            subnet_info.route_table = {
+                'id': route_table_id
+            }
+
+            result_create = self.network_management_client. \
+                subnets.create_or_update(
+                self.resource_group,
+                network_name,
+                subnet_name,
+                subnet_info
+            )
+            subnet_info = result_create.result()
+
+        return subnet_info
+
+    def detach_subnet_to_route_table(self, network_name,
+                                     subnet_name, route_table_id):
+
+        subnet_info = self.network_management_client.subnets.get(
+            self.resource_group,
+            network_name,
+            subnet_name
+        )
+
+        if subnet_info and subnet_info.route_table.id == route_table_id:
+            subnet_info.route_table = None
+
+            result_create = self.network_management_client. \
+                subnets.create_or_update(
+                self.resource_group,
+                network_name,
+                subnet_name,
+                subnet_info)
+            subnet_info = result_create.result()
+
+        return subnet_info
+
+    def list_route_tables(self):
+        return self.network_management_client. \
+            route_tables.list(self.resource_group)
+
+    def get_route_table(self, router_id):
+        return self.network_management_client. \
+            route_tables.get(self.resource_group, router_id)
+
+    def create_route_table(self, route_table_name, params):
+        return self.network_management_client. \
+            route_tables.create_or_update(
+            self.resource_group,
+            route_table_name, params).result()
+
+    def update_route_table_tags(self, route_table_name, tags):
+        self.network_management_client.route_tables. \
+            create_or_update(self.resource_group,
+                             route_table_name, tags).result()

+ 4 - 5
cloudbridge/cloud/providers/azure/provider.py

@@ -5,7 +5,7 @@ from cloudbridge.cloud.base import BaseCloudProvider
 from cloudbridge.cloud.providers.azure.azure_client import AzureClient
 from cloudbridge.cloud.providers.azure.services \
     import AzureBlockStoreService, AzureComputeService, \
-    AzureNetworkService, AzureObjectStoreService, AzureSecurityService
+    AzureNetworkingService, AzureObjectStoreService, AzureSecurityService
 
 
 from msrestazure.azure_exceptions import CloudError
@@ -18,7 +18,6 @@ class AzureCloudProvider(BaseCloudProvider):
 
     def __init__(self, config):
         super(AzureCloudProvider, self).__init__(config)
-        self.cloud_type = 'azure'
 
         # mandatory config values
         self.subscription_id = self. \
@@ -57,15 +56,15 @@ class AzureCloudProvider(BaseCloudProvider):
         self._object_store = AzureObjectStoreService(self)
         self._block_store = AzureBlockStoreService(self)
         self._compute = AzureComputeService(self)
-        self._network = AzureNetworkService(self)
+        self._networking = AzureNetworkingService(self)
 
     @property
     def compute(self):
         return self._compute
 
     @property
-    def network(self):
-        return self._network
+    def networking(self):
+        return self._networking
 
     @property
     def security(self):

+ 175 - 62
cloudbridge/cloud/providers/azure/resources.py

@@ -13,14 +13,16 @@ from azure.mgmt.network.models import NetworkSecurityGroup
 
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo, \
     BaseBucket, BaseBucketObject, BaseFloatingIP, \
-    BaseInstance, BaseInstanceType, BaseKeyPair,\
-    BaseLaunchConfig, BaseMachineImage, BaseNetwork, \
+    BaseInstance, BaseInstanceType, BaseInternetGateway, \
+    BaseKeyPair, BaseLaunchConfig, BaseMachineImage, BaseNetwork, \
     BasePlacementZone, BaseRegion, BaseRouter, BaseSecurityGroup, \
     BaseSecurityGroupRule, BaseSnapshot, BaseSubnet, \
     BaseVolume, ClientPagedResultList
 from cloudbridge.cloud.interfaces import InstanceState, VolumeState
+from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
 from cloudbridge.cloud.interfaces.resources import Instance, \
-    MachineImageState, NetworkState, RouterState, SnapshotState
+    MachineImageState, NetworkState, RouterState, \
+    SnapshotState, SubnetState
 from cloudbridge.cloud.providers.azure import helpers as azure_helpers
 
 from msrestazure.azure_exceptions import CloudError
@@ -93,10 +95,13 @@ class AzureSecurityGroup(BaseSecurityGroup):
 
     @name.setter
     def name(self, value):
-        self._security_group.tags.update(Name=value)
-        self._provider.azure_client. \
-            update_security_group_tags(self.id,
-                                       self._security_group.tags)
+        if self.is_valid_resource_name(value):
+            self._security_group.tags.update(Name=value)
+            self._provider.azure_client. \
+                update_security_group_tags(self.id,
+                                           self._security_group.tags)
+        else:
+            raise InvalidNameException(value)
 
     @property
     def description(self):
@@ -130,6 +135,19 @@ class AzureSecurityGroup(BaseSecurityGroup):
             log.exception(cloudError.message)
             return False
 
+    def refresh(self):
+        """
+        Refreshes the security group with tags if required.
+        """
+        try:
+            self._security_group = self._provider.azure_client. \
+                get_security_group(self.id)
+            if not self._security_group.tags:
+                self._security_group.tags = {}
+        except (CloudError, ValueError) as cloudError:
+            log.exception(cloudError.message)
+            # The security group no longer exists and cannot be refreshed.
+
     def add_rule(self, ip_protocol=None, from_port=None, to_port=None,
                  cidr_ip=None, src_group=None):
         """
@@ -223,7 +241,7 @@ class AzureSecurityGroup(BaseSecurityGroup):
         js['rules'] = [json.loads(r) for r in json_rules]
         if js.get('network_id'):
             js.pop('network_id')  # Omit for consistency across cloud providers
-        return json.dumps(js, sort_keys=True)
+        return js
 
 
 class AzureSecurityGroupRule(BaseSecurityGroupRule):
@@ -416,6 +434,11 @@ class AzureBucket(BaseBucket):
         return ClientPagedResultList(self._provider, objects,
                                      limit=limit, marker=marker)
 
+    def find(self, name, limit=None, marker=None):
+        objects = [obj for obj in self if obj.name == name]
+        return ClientPagedResultList(self._provider, objects,
+                                     limit=limit, marker=marker)
+
     def delete(self, delete_contents=True):
         """
         Delete this bucket.
@@ -497,10 +520,13 @@ class AzureVolume(BaseVolume):
         Set the volume name.
         """
         # self._volume.name = value
-        self._volume.tags.update(Name=value)
-        self._provider.azure_client. \
-            update_disk_tags(self.id,
-                             self._volume.tags)
+        if self.is_valid_resource_name(value):
+            self._volume.tags.update(Name=value)
+            self._provider.azure_client. \
+                update_disk_tags(self.id,
+                                 self._volume.tags)
+        else:
+            raise InvalidNameException(value)
 
     @property
     def description(self):
@@ -685,10 +711,13 @@ class AzureSnapshot(BaseSnapshot):
         Set the snapshot name.
         """
         # self._snapshot.name = value
-        self._snapshot.tags.update(Name=value)
-        self._provider.azure_client. \
-            update_snapshot_tags(self.id,
-                                 self._snapshot.tags)
+        if self.is_valid_resource_name(value):
+            self._snapshot.tags.update(Name=value)
+            self._provider.azure_client. \
+                update_snapshot_tags(self.id,
+                                     self._snapshot.tags)
+        else:
+            raise InvalidNameException(value)
 
     @property
     def description(self):
@@ -801,9 +830,12 @@ class AzureMachineImage(BaseMachineImage):
         """
         Set the image name.
         """
-        self._image.tags.update(Name=value)
-        self._provider.azure_client. \
-            update_image_tags(self.id, self._image.tags)
+        if self.is_valid_resource_name(value):
+            self._image.tags.update(Name=value)
+            self._provider.azure_client. \
+                update_image_tags(self.id, self._image.tags)
+        else:
+            raise InvalidNameException(value)
 
     @property
     def description(self):
@@ -899,9 +931,12 @@ class AzureNetwork(BaseNetwork):
         """
         Set the network name.
         """
-        self._network.tags.update(Name=value)
-        self._provider.azure_client.\
-            update_network_tags(self.id, self._network.tags)
+        if self.is_valid_resource_name(value):
+            self._network.tags.update(Name=value)
+            self._provider.azure_client. \
+                update_network_tags(self.id, self._network)
+        else:
+            raise InvalidNameException(value)
 
     @property
     def external(self):
@@ -951,12 +986,13 @@ class AzureNetwork(BaseNetwork):
             log.exception(cloudError.message)
             return False
 
+    @property
     def subnets(self):
         """
         List all the subnets in this network
         :return:
         """
-        return self._provider.network.subnets.list(network=self.id)
+        return self._provider.networking.subnets.list(network=self.id)
 
     def create_subnet(self, cidr_block, name=None, zone=None):
         """
@@ -966,7 +1002,7 @@ class AzureNetwork(BaseNetwork):
         :param zone:
         :return:
         """
-        return self._provider.network.subnets.\
+        return self._provider.networking.subnets. \
             create(network=self.id, cidr_block=cidr_block, name=name)
 
 
@@ -1073,10 +1109,15 @@ class AzurePlacementZone(BasePlacementZone):
 
 
 class AzureSubnet(BaseSubnet):
+    _SUBNET_STATE_MAP = {
+        'InProgress': SubnetState.PENDING,
+        'Succeeded': SubnetState.AVAILABLE,
+    }
 
     def __init__(self, provider, subnet):
         super(AzureSubnet, self).__init__(provider)
         self._subnet = subnet
+        self._state = self._subnet.provisioning_state
         self._url_params = azure_helpers\
             .parse_url(SUBNET_RESOURCE_ID, subnet.id)
         self._network = self._provider.azure_client.\
@@ -1127,6 +1168,26 @@ class AzureSubnet(BaseSubnet):
             log.exception(cloudError.message)
             return False
 
+    @property
+    def state(self):
+        return self._SUBNET_STATE_MAP.get(
+            self._state, NetworkState.UNKNOWN)
+
+    def refresh(self):
+        """
+        Refreshes the state of this network by re-querying the cloud provider
+        for its latest state.
+        """
+        try:
+            self._network = self._provider.azure_client. \
+                get_network(self.id)
+            self._state = self._network.provisioning_state
+        except (CloudError, ValueError) as cloudError:
+            log.exception(cloudError.message)
+            # The network no longer exists and cannot be refreshed.
+            # set the state to unknown
+            self._state = 'unknown'
+
 
 class AzureInstance(BaseInstance):
 
@@ -1218,9 +1279,12 @@ class AzureInstance(BaseInstance):
         """
         Set the instance name.
         """
-        self._vm.tags.update(Name=value)
-        self._provider.azure_client.\
-            update_vm_tags(self.id, self._vm.tags)
+        if self.is_valid_resource_name(value):
+            self._vm.tags.update(Name=value)
+            self._provider.azure_client. \
+                update_vm_tags(self.id, self._vm)
+        else:
+            raise InvalidNameException(value)
 
     @property
     def public_ips(self):
@@ -1620,25 +1684,19 @@ class AzureKeyPair(BaseKeyPair):
 
 
 class AzureRouter(BaseRouter):
-
-    def __init__(self, provider, router):
+    def __init__(self, provider, route_table):
         super(AzureRouter, self).__init__(provider)
-        self._router = router
-        self._ROUTE_CIDR = '0.0.0.0/0'
-        self._name = None
-        self._network_id = None
-        self._state = RouterState.DETACHED
-
-    def _route_table(self, subnet_id):
-        pass
+        self._route_table = route_table
+        if not self._route_table.tags:
+            self._route_table.tags = {}
 
     @property
     def id(self):
-        return self._name
+        return self._route_table.name
 
     @property
     def resource_id(self):
-        pass
+        return self._route_table.id
 
     @property
     def name(self):
@@ -1647,7 +1705,7 @@ class AzureRouter(BaseRouter):
 
         .. note:: the router must have a (case sensitive) tag ``Name``
         """
-        return self._name
+        return self._route_table.tags.get('Name', self._route_table.name)
 
     @name.setter
     # pylint:disable=arguments-differ
@@ -1655,47 +1713,102 @@ class AzureRouter(BaseRouter):
         """
         Set the router name.
         """
-        self._name = value
+        if self.is_valid_resource_name(value):
+            self._route_table.tags.update(Name=value)
+            self._provider.azure_client. \
+                update_route_table_tags(self._route_table.name,
+                                        self._route_table)
+        else:
+            raise InvalidNameException(value)
 
     def refresh(self):
-        pass
+        self._route_table = self._provider.azure_client. \
+            get_route_table(self._route_table.name)
 
     @property
     def state(self):
-        return self._state
+        self.refresh()  # Explicitly refresh the local object
+        if self._route_table.subnets:
+            return RouterState.ATTACHED
+        return RouterState.DETACHED
 
     @property
     def network_id(self):
-        return self._network_id
+        return None
 
     def delete(self):
-        pass
+        self._provider.azure_client. \
+            delete_route_table(self.name)
 
-    def attach_network(self, network_id):
-        self._network_id = network_id
-        self._state = RouterState.ATTACHED
+    def attach_subnet(self, subnet):
+        subnet_id_parts = subnet.id.split('|$|')
+        if (len(subnet_id_parts) != 2):
+            pass
+        self._provider.azure_client. \
+            attach_subnet_to_route_table(subnet_id_parts[0],
+                                         subnet_id_parts[1],
+                                         self.resource_id)
+        self.refresh()
+
+    def detach_subnet(self, subnet):
+        subnet_id_parts = subnet.id.split('|$|')
+        if (len(subnet_id_parts) != 2):
+            pass
+        self._provider.azure_client. \
+            detach_subnet_to_route_table(subnet_id_parts[0],
+                                         subnet_id_parts[1],
+                                         self.resource_id)
+        self.refresh()
 
-    def detach_network(self):
+    def attach_gateway(self, gateway):
         pass
 
-    def add_route(self, subnet_id):
-        """
-        Add a default route to this router.
+    def detach_gateway(self, gateway):
+        pass
+
+
+class AzureInternetGateway(BaseInternetGateway):
+    def __init__(self, provider, gateway):
+        super(AzureInternetGateway, self).__init__(provider)
+        self._gateway = gateway
+        self._name = None
+        self._network_id = None
+        self._state = ''
 
-        For Azure, routes are added to a route table. A route table is assoc.
-        with a network vs. a subnet so we retrieve the network via the subnet.
-        Note that the subnet must belong to the same network as the router
-        is attached to.
+    @property
+    def id(self):
+        return self._name
 
-        Further, only a single route can be added, targeting the Internet
-        (i.e., destination CIDR block ``0.0.0.0/0``).
+    @property
+    def name(self):
         """
-        pass
+        Get the gateway name.
 
-    def remove_route(self, subnet_id):
+        .. note:: the gateway must have a (case sensitive) tag ``Name``
         """
-        Remove the default Internet route from this router.
+        return self._name
 
-        .. seealso:: ``add_route`` method
+    @name.setter
+    # pylint:disable=arguments-differ
+    def name(self, value):
         """
+        Set the router name.
+        """
+        if self.is_valid_resource_name(value):
+            self._name = value
+        else:
+            raise InvalidNameException(value)
+
+    def refresh(self):
+        pass
+
+    @property
+    def state(self):
+        return self._state
+
+    @property
+    def network_id(self):
+        return None
+
+    def delete(self):
         pass

+ 108 - 18
cloudbridge/cloud/providers/azure/services.py

@@ -6,23 +6,25 @@ from azure.common import AzureException
 
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBlockStoreService, \
-    BaseComputeService, BaseImageService, BaseInstanceService, \
-    BaseInstanceTypesService, BaseKeyPairService, \
-    BaseNetworkService, BaseObjectStoreService,\
-    BaseRegionService, BaseSecurityGroupService, BaseSecurityService, \
+    BaseComputeService, BaseGatewayService, BaseImageService, \
+    BaseInstanceService, BaseInstanceTypesService, \
+    BaseKeyPairService, BaseNetworkService, BaseNetworkingService, \
+    BaseObjectStoreService, BaseRegionService, BaseRouterService, \
+    BaseSecurityGroupService, BaseSecurityService, \
     BaseSnapshotService, BaseSubnetService, BaseVolumeService
 from cloudbridge.cloud.interfaces import InvalidConfigurationException
 
 from cloudbridge.cloud.interfaces.resources import InstanceType, \
-     MachineImage, Network, PlacementZone, SecurityGroup, \
-     Snapshot, Subnet, Volume
+    MachineImage, Network, PlacementZone, SecurityGroup, \
+    Snapshot, Subnet, Volume
 
 from cloudbridge.cloud.providers.azure import helpers as azure_helpers
 
 from msrestazure.azure_exceptions import CloudError
 
 from .resources import AzureBucket, AzureFloatingIP, \
-    AzureInstance, AzureInstanceType, AzureKeyPair,\
+    AzureInstance, AzureInstanceType, \
+    AzureInternetGateway, AzureKeyPair, \
     AzureLaunchConfig, AzureMachineImage, \
     AzureNetwork, AzureRegion, AzureRouter, AzureSecurityGroup, \
     AzureSnapshot, AzureSubnet, AzureVolume
@@ -446,8 +448,8 @@ class AzureInstanceService(BaseInstanceService):
                key_pair=None, security_groups=None, user_data=None,
                launch_config=None, **kwargs):
 
-        instance_name = "{0}-{1}".format(name, uuid.uuid4().hex[:6])
-
+        instance_name = name.replace("_", "-") if name \
+            else "{0} - {1}".format("cb", uuid.uuid4())
         # Key_pair is mandatory in azure and it should not be None.
         if key_pair:
             key_pair = (self.provider.security.key_pairs.get(key_pair)
@@ -557,6 +559,7 @@ class AzureInstanceService(BaseInstanceService):
 
         self.provider.azure_client.create_vm(instance_name, params)
         vm = self._provider.azure_client.get_vm(instance_name)
+        print(str(vm))
         return AzureInstance(self.provider, vm)
 
     def _resolve_launch_options(self, name, subnet=None, zone_id=None,
@@ -782,6 +785,31 @@ class AzureInstanceTypesService(BaseInstanceTypesService):
                                      limit=limit, marker=marker)
 
 
+class AzureNetworkingService(BaseNetworkingService):
+    def __init__(self, provider):
+        super(AzureNetworkingService, self).__init__(provider)
+        self._network_service = AzureNetworkService(self.provider)
+        self._subnet_service = AzureSubnetService(self.provider)
+        self._router_service = AzureRouterService(self.provider)
+        self._gateway_service = AzureGatewayService(self.provider)
+
+    @property
+    def networks(self):
+        return self._network_service
+
+    @property
+    def subnets(self):
+        return self._subnet_service
+
+    @property
+    def routers(self):
+        return self._router_service
+
+    @property
+    def gateways(self):
+        return self._gateway_service
+
+
 class AzureNetworkService(BaseNetworkService):
     def __init__(self, provider):
         super(AzureNetworkService, self).__init__(provider)
@@ -806,7 +834,15 @@ class AzureNetworkService(BaseNetworkService):
         return ClientPagedResultList(self.provider, networks,
                                      limit=limit, marker=marker)
 
-    def create(self, name=None):
+    def find(self, name, limit=None, marker=None):
+        filters = {'Name': name}
+        networks = [AzureNetwork(self.provider, network)
+                    for network in azure_helpers.filter(
+                self.provider.azure_client.list_networks(), filters)]
+        return ClientPagedResultList(self.provider, networks,
+                                     limit=limit, marker=marker)
+
+    def create(self, name, cidr_block):
         # Azure requires CIDR block to be specified when creating a network
         # so set a default one and use the largest allowed netmask.
         network_name = AzureNetwork.CB_DEFAULT_NETWORK_NAME
@@ -816,15 +852,13 @@ class AzureNetworkService(BaseNetworkService):
         params = {
             'location': self.provider.azure_client.region_name,
             'address_space': {
-                'address_prefixes': ['10.0.0.0/16']
+                'address_prefixes': [cidr_block]
             },
             '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)
-
         return cb_network
 
     def create_floating_ip(self):
@@ -843,6 +877,7 @@ class AzureNetworkService(BaseNetworkService):
     def subnets(self):
         return self._subnet_svc
 
+    @property
     def floating_ips(self, network_id=None):
         """
                List all floating ips.
@@ -850,7 +885,6 @@ class AzureNetworkService(BaseNetworkService):
         floating_ips = [AzureFloatingIP(self.provider, floating_ip)
                         for floating_ip in self.provider.azure_client.
                         list_floating_ips()]
-
         return ClientPagedResultList(self.provider, floating_ips)
 
     def routers(self):
@@ -914,6 +948,8 @@ class AzureSubnetService(BaseSubnetService):
         """
         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])
             return AzureSubnet(self.provider,
@@ -989,18 +1025,20 @@ class AzureSubnetService(BaseSubnetService):
             return AzureSubnet(self.provider, subnet)
 
         # 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(AzureNetwork.CB_DEFAULT_NETWORK_NAME)
+            network = self.provider.azure_client \
+                .get_network(default_net_name)
         except CloudError:
             # Azure raises the cloud error if the resource not available
             pass
 
         if not network:
-            self.provider.network.create()
+            network = self.provider.networking.networks.create(
+                name=default_net_name, cidr_block='10.0.0.0/16')
 
         subnet = self.provider.azure_client.create_subnet(
-            AzureNetwork.CB_DEFAULT_NETWORK_NAME,
+            network.id,
             AzureSubnet.CB_DEFAULT_SUBNET_NAME,
             {'address_prefix': default_cdir}
         )
@@ -1022,3 +1060,55 @@ class AzureSubnetService(BaseSubnetService):
             # Azure raises the cloud error if the resource not available
             log.exception(cloudError.message)
             return False
+
+
+class AzureRouterService(BaseRouterService):
+    def __init__(self, provider):
+        super(AzureRouterService, self).__init__(provider)
+
+    def get(self, router_id):
+        try:
+            route = self.provider.azure_client.get_route_table(router_id)
+            return AzureRouter(self.provider, route)
+
+        except CloudError as cloudError:
+            # Azure raises the cloud error if the resource not available
+            log.exception(cloudError.message)
+            return None
+
+    def find(self, name, limit=None, marker=None):
+        filters = {'Name': name}
+        routes = [AzureRouter(self.provider, route)
+                  for route in azure_helpers.filter(
+                self.provider.azure_client.list_route_tables(), filters)]
+
+        return ClientPagedResultList(self.provider, routes,
+                                     limit=limit, marker=marker)
+
+    def list(self, limit=None, marker=None):
+        routes = [AzureRouter(self.provider, route)
+                  for route in
+                  self.provider.azure_client.list_route_tables()]
+        return ClientPagedResultList(self.provider,
+                                     routes,
+                                     limit=limit, marker=marker)
+
+    def create(self, name, network):
+        parameters = {"location": self.provider.region_name,
+                      'tags': {'Name': name}}
+        route = self.provider.azure_client. \
+            create_route_table(name, parameters)
+        return AzureRouter(self.provider, route)
+
+
+class AzureGatewayService(BaseGatewayService):
+    def __init__(self, provider):
+        super(AzureGatewayService, self).__init__(provider)
+
+    def get_or_create_inet_gateway(self, name):
+        gateway = AzureInternetGateway(self.provider, None)
+        gateway.name = name
+        return gateway
+
+    def delete(self, gateway):
+        pass

+ 2 - 2
docs/topics/contributor_guide.rst

@@ -10,6 +10,6 @@ CloudBridge Provider.
     Design Goals <design_goals.rst>
     Design Decisions <design-decisions.rst>
     Testing <testing.rst>
-                         Provider Development Walkthrough <provider_development.rst>
-                         Release Process <release_process.rst>
+                                                     Provider Development Walkthrough <provider_development.rst>
+                                                     Release Process <release_process.rst>
 

+ 1 - 0
test/helpers/__init__.py

@@ -206,6 +206,7 @@ class ProviderTestBase(unittest.TestCase):
         config = {'default_wait_interval':
                       self.get_provider_wait_interval(provider_class),
                   'default_result_limit': 1}
+
         return provider_class(config)
 
     @property

+ 3 - 2
test/test_block_store_service.py

@@ -113,8 +113,9 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                 self.assertEqual(test_vol.attachments.volume, test_vol)
                 self.assertEqual(test_vol.attachments.instance_id,
                                  test_instance.id)
-                self.assertEqual(test_vol.attachments.device,
-                                 "/dev/sda2")
+                if not self.provider.PROVIDER_ID == 'azure':
+                    self.assertEqual(test_vol.attachments.device,
+                                     "/dev/sda2")
                 test_vol.detach()
                 test_vol.name = 'newvolname1'
                 test_vol.wait_for(

+ 2 - 1
tox.ini

@@ -15,7 +15,8 @@
 envlist = {py27,py36,pypy}-{aws,azure,openstack}
 
 [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}
 setenv =
     aws: CB_TEST_PROVIDER=aws