Explorar el Código

Azure name and display_id

almahmoud hace 7 años
padre
commit
bd9164c7bb

+ 9 - 3
cloudbridge/cloud/base/helpers.py

@@ -1,3 +1,5 @@
+import fnmatch
+import re
 import sys
 import traceback
 from contextlib import contextmanager
@@ -38,9 +40,13 @@ def filter_by(prop_name, kwargs, objs):
     """
     prop_val = kwargs.pop(prop_name, None)
     if prop_val:
-        match = (o for o in objs if getattr(o, prop_name) == prop_val)
-        return match
-    return objs
+        regex = fnmatch.translate(prop_val)
+        results = [o for o in objs
+                   if getattr(o, prop_name)
+                   and re.search(regex, getattr(o, prop_name))]
+    else:
+        return objs
+    return results
 
 
 def generic_find(filter_names, kwargs, objs):

+ 18 - 18
cloudbridge/cloud/interfaces/resources.py

@@ -64,31 +64,31 @@ class CloudResource(object):
         pass
 
     @abstractproperty
-    def display_id(self):
+    def name(self):
         """
         Get the displayable id for the resource.
 
-        The display_id property is typically a user-friendly id value for the
-        resource. The display_id is different from the id property in the
+        The name property is typically a user-friendly id value for the
+        resource. The name is different from the id property in the
         following ways:
-        1. The display_id property is often a more user-friendly value to
+        1. The name property is often a more user-friendly value to
            display to the user than the id property.
-        2. The display_id may sometimes be the same as the id, but should never
+        2. The name may sometimes be the same as the id, but should never
            be used in place of the id.
         3. The id is what will uniquely identify a resource, and will be used
            internally by cloudbridge for all get operations etc.
-        4. All resources have a display_id.
-        5. The display_id is read-only.
-        6. However, the display_id may not necessarily be unique, which is the
+        4. All resources have a name.
+        5. The name is read-only.
+        6. However, the name may not necessarily be unique, which is the
            reason why it should not be used for uniquely identifying a
            resource.
         Example:
-        The AWS machine image name is a display_id. It is not editable and is
+        The AWS machine image name is a name. It is not editable and is
         a user friendly name such as 'Ubuntu 14.04' and corresponds to the
         ami-name. It is distinct from the ami-id, which corresponds to
         cloudbridge's id property. The ami-name cannot be edited, and is set
         at creation time. It is not necessarily unique.
-        In Azure, the machine image's display_id corresponds to the name
+        In Azure, the machine image's name corresponds to the name
         property. In Azure, it also happens to be the same as the id property.
         """
         pass
@@ -488,9 +488,9 @@ class Instance(ObjectLifeCycleMixin, CloudResource):
 
     __metaclass__ = ABCMeta
 
-    @CloudResource.name.setter
+    @CloudResource.label.setter
     @abstractmethod
-    def name(self, value):
+    def label(self, value):
         """
         Set the instance name.
 
@@ -934,7 +934,7 @@ class Network(ObjectLifeCycleMixin, CloudResource):
         pass
 
     @abstractmethod
-    def create_subnet(self, name, cidr_block, zone=None):
+    def create_subnet(self, cidr_block, label=None, zone=None):
         """
         Create a new network subnet and associate it with this Network.
 
@@ -1294,7 +1294,7 @@ class GatewayContainer(PageableObjectMixin):
     __metaclass__ = ABCMeta
 
     @abstractmethod
-    def get_or_create_inet_gateway(self, name=None):
+    def get_or_create_inet_gateway(self, label=None):
         """
         Creates new or returns an existing internet gateway for a network.
 
@@ -1438,9 +1438,9 @@ class Volume(ObjectLifeCycleMixin, CloudResource):
 
     __metaclass__ = ABCMeta
 
-    @CloudResource.name.setter
+    @CloudResource.label.setter
     @abstractmethod
-    def name(self, value):
+    def label(self, value):
         """
         Set the volume name.
 
@@ -1623,9 +1623,9 @@ class Snapshot(ObjectLifeCycleMixin, CloudResource):
 
     __metaclass__ = ABCMeta
 
-    @CloudResource.name.setter
+    @CloudResource.label.setter
     @abstractmethod
-    def name(self, value):
+    def label(self, value):
         """
         Set the snapshot name.
 

+ 32 - 3
cloudbridge/cloud/providers/azure/azure_client.py

@@ -2,6 +2,7 @@ import datetime
 import logging
 from io import BytesIO
 
+from azure.common import AzureConflictHttpError
 from azure.common.credentials import ServicePrincipalCredentials
 from azure.cosmosdb.table.tableservice import TableService
 from azure.mgmt.compute import ComputeManagementClient
@@ -19,7 +20,8 @@ from msrestazure.azure_exceptions import CloudError
 import tenacity
 
 from cloudbridge.cloud.interfaces.exceptions import \
-    InvalidNameException, ProviderConnectionException, WaitStateException
+    DuplicateResourceException, InvalidNameException, \
+    ProviderConnectionException, WaitStateException
 
 from . import helpers as azure_helpers
 
@@ -43,6 +45,10 @@ PUBLIC_IP_RESOURCE_ID = ['/subscriptions/{subscriptionId}/resourceGroups'
                          '/{resourceGroupName}/providers/Microsoft.Network'
                          '/publicIPAddresses/{publicIpAddressName}',
                          '{publicIpAddressName}']
+ROUTER_RESOURCE_ID = ['/subscriptions/{subscriptionId}'
+                      '/resourceGroups/{resourceGroupName}'
+                      '/providers/Microsoft.Network/routeTables/{routerName}',
+                      '{routerName}']
 SNAPSHOT_RESOURCE_ID = ['/subscriptions/{subscriptionId}/resourceGroups/'
                         '{resourceGroupName}/providers/Microsoft.Compute/'
                         'snapshots/{snapshotName}',
@@ -78,6 +84,7 @@ IMAGE_NAME = 'imageName'
 NETWORK_NAME = 'virtualNetworkName'
 NETWORK_INTERFACE_NAME = 'networkInterfaceName'
 PUBLIC_IP_NAME = 'publicIpAddressName'
+ROUTER_NAME = 'routerName'
 SNAPSHOT_NAME = 'snapshotName'
 SUBNET_NAME = 'subnetName'
 VM_NAME = 'vmName'
@@ -405,7 +412,18 @@ class AzureClient(object):
         return self.blob_service.list_containers(prefix=prefix)
 
     def create_container(self, container_name):
-        self.blob_service.create_container(container_name)
+        try:
+            self.blob_service.create_container(container_name,
+                                               fail_on_exist=True)
+        except AzureConflictHttpError as cloud_error:
+            if cloud_error.error_code == "ContainerAlreadyExists":
+                msg = "The given Bucket name '%s' already exists. Please " \
+                      "use the `get` or `find` method to get a reference to " \
+                      "an existing Bucket, or specify a new Bucket name to " \
+                      "create.\nNote that in Azure, Buckets are contained " \
+                      "in Storage Accounts." % container_name
+                raise DuplicateResourceException(msg)
+
         return self.blob_service.get_container_properties(container_name)
 
     def get_container(self, container_name):
@@ -694,6 +712,14 @@ class AzureClient(object):
             public_ip_addresses.delete(self.resource_group,
                                        public_ip_name).wait()
 
+    def update_fip_tags(self, fip_id, tags):
+        url_params = azure_helpers.parse_url(PUBLIC_IP_RESOURCE_ID,
+                                             fip_id)
+        fip_name = url_params.get(PUBLIC_IP_NAME)
+        self.network_management_client.public_ip_addresses. \
+            create_or_update(self.resource_group,
+                             fip_name, tags).result()
+
     def list_floating_ips(self):
         return self.network_management_client.public_ip_addresses.list(
             self.resource_group)
@@ -891,8 +917,11 @@ class AzureClient(object):
             route_tables.list(self.resource_group)
 
     def get_route_table(self, router_id):
+        url_params = azure_helpers.parse_url(ROUTER_RESOURCE_ID,
+                                             router_id)
+        router_name = url_params.get(ROUTER_NAME)
         return self.network_management_client. \
-            route_tables.get(self.resource_group, router_id)
+            route_tables.get(self.resource_group, router_name)
 
     def create_route_table(self, route_table_name, params):
         return self.network_management_client. \

+ 188 - 119
cloudbridge/cloud/providers/azure/resources.py

@@ -52,12 +52,16 @@ class AzureVMFirewall(BaseVMFirewall):
 
     @property
     def name(self):
-        return self._vm_firewall.tags.get('Name', self._vm_firewall.name)
+        return self._vm_firewall.name
 
-    @name.setter
-    def name(self, value):
+    @property
+    def label(self):
+        return self._vm_firewall.tags.get('Label', None)
+
+    @label.setter
+    def label(self, value):
         self.assert_valid_resource_name(value)
-        self._vm_firewall.tags.update(Name=value)
+        self._vm_firewall.tags.update(Label=value)
         self._provider.azure_client.update_vm_firewall_tags(
             self.id, self._vm_firewall.tags)
 
@@ -141,7 +145,7 @@ class AzureVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
             cidr = '0.0.0.0/0'
 
         count = len(self.firewall._vm_firewall.security_rules) + 1
-        rule_name = "Rule - " + str(count)
+        rule_name = "cb-rule-" + str(count)
         priority = 1000 + count
         destination_port_range = str(from_port) + "-" + str(to_port)
         source_port_range = '*'
@@ -177,15 +181,19 @@ class AzureVMFirewallRule(BaseVMFirewallRule):
     def id(self):
         return self._rule.id
 
+    @property
+    def name(self):
+        return self._rule.name
+
+    @property
+    def label(self):
+        raise NotImplementedError("Azure Firewall Rules do not support labels")
+
     @property
     def direction(self):
         return (TrafficDirection.INBOUND if self._rule.direction == "Inbound"
                 else TrafficDirection.OUTBOUND)
 
-    @property
-    def name(self):
-        return self._rule.name
-
     @property
     def protocol(self):
         return self._rule.protocol
@@ -240,11 +248,12 @@ class AzureBucketObject(BaseBucketObject):
 
     @property
     def name(self):
-        """
-        Get this object's name.
-        """
         return self._key.name
 
+    @property
+    def label(self):
+        raise NotImplementedError("Azure Bucket Objects do not support labels")
+
     @property
     def size(self):
         """
@@ -267,7 +276,7 @@ class AzureBucketObject(BaseBucketObject):
         iterable.
         """
         content_stream = self._provider.azure_client. \
-            get_blob_content(self._container.name, self._key.name)
+            get_blob_content(self._container.id, self._key.name)
         if content_stream:
             content_stream.seek(0)
         return content_stream
@@ -279,7 +288,7 @@ class AzureBucketObject(BaseBucketObject):
         """
         try:
             self._provider.azure_client.create_blob_from_text(
-                self._container.name, self.name, data)
+                self._container.id, self.id, data)
             return True
         except AzureException as azureEx:
             log.exception(azureEx)
@@ -291,7 +300,7 @@ class AzureBucketObject(BaseBucketObject):
         """
         try:
             self._provider.azure_client.create_blob_from_file(
-                self._container.name, self.name, path)
+                self._container.id, self.id, path)
             return True
         except AzureException as azureEx:
             log.exception(azureEx)
@@ -304,19 +313,19 @@ class AzureBucketObject(BaseBucketObject):
         :rtype: bool
         :return: True if successful
         """
-        self._provider.azure_client.delete_blob(self._container.name,
-                                                self.name)
+        self._provider.azure_client.delete_blob(self._container.id,
+                                                self.id)
 
     def generate_url(self, expires_in):
         """
         Generate a URL to this object.
         """
         return self._provider.azure_client.get_blob_url(
-            self._container.name, self.name, expires_in)
+            self._container.id, self.id, expires_in)
 
     def refresh(self):
         self._key = self._provider.azure_client.get_blob(
-            self._container.name, self._key.name)
+            self._container.id, self._key.id)
 
 
 class AzureBucket(BaseBucket):
@@ -336,6 +345,11 @@ class AzureBucket(BaseBucket):
         """
         return self._bucket.name
 
+    @property
+    def label(self):
+        raise NotImplementedError("Azure Buckets do not support labels")
+
+
     def delete(self, delete_contents=True):
         """
         Delete this bucket.
@@ -363,7 +377,8 @@ class AzureBucketContainer(BaseBucketContainer):
         Retrieve a given object from this bucket.
         """
         try:
-            obj = self._provider.azure_client.get_blob(self.bucket.name, key)
+            obj = self._provider.azure_client.get_blob(self.bucket.name,
+                                                       key)
             return AzureBucketObject(self._provider, self.bucket, obj)
         except AzureException as azureEx:
             log.exception(azureEx)
@@ -433,28 +448,31 @@ class AzureVolume(BaseVolume):
     def resource_id(self):
         return self._volume.id
 
+    @property
+    def name(self):
+        return self._volume.name
+
     @property
     def tags(self):
         return self._volume.tags
 
     @property
-    def name(self):
+    def label(self):
         """
-        Get the volume name.
+        Get the volume label.
 
-        .. note:: an instance must have a (case sensitive) tag ``Name``
+        .. note:: an instance must have a (case sensitive) tag ``Label``
         """
-        return self._volume.tags.get('Name', self._volume.name)
+        return self._volume.tags.get('Label', None)
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
-        Set the volume name.
+        Set the volume label.
         """
-        # self._volume.name = value
         self.assert_valid_resource_name(value)
-        self._volume.tags.update(Name=value)
+        self._volume.tags.update(Label=value)
         self._provider.azure_client. \
             update_disk_tags(self.id,
                              self._volume.tags)
@@ -587,27 +605,31 @@ class AzureSnapshot(BaseSnapshot):
     def id(self):
         return self._snapshot.id
 
+    @property
+    def name(self):
+        return self._snapshot.name
+
     @property
     def resource_id(self):
         return self._snapshot.id
 
     @property
-    def name(self):
+    def label(self):
         """
-        Get the snapshot name.
+        Get the snapshot label.
 
-        .. note:: an instance must have a (case sensitive) tag ``Name``
+        .. note:: an instance must have a (case sensitive) tag ``Label``
         """
-        return self._snapshot.tags.get('Name', self._snapshot.name)
+        return self._snapshot.tags.get('Label', None)
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
-        Set the snapshot name.
+        Set the snapshot label.
         """
         self.assert_valid_resource_name(value)
-        self._snapshot.tags.update(Name=value)
+        self._snapshot.tags.update(Label=value)
         self._provider.azure_client. \
             update_snapshot_tags(self.id,
                                  self._snapshot.tags)
@@ -698,39 +720,40 @@ class AzureMachineImage(BaseMachineImage):
         :rtype: ``str``
         :return: ID for this instance as returned by the cloud middleware.
         """
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return azure_helpers.generate_urn(self._image)
         else:
             return self._image.id
 
+    @property
+    def name(self):
+        if self.is_gallery_image:
+            return azure_helpers.generate_urn(self._image)
+        else:
+            return self._image.name
+
     @property
     def resource_id(self):
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return azure_helpers.generate_urn(self._image)
         else:
             return self._image.id
 
     @property
-    def name(self):
-        """
-        Get the image name.
-
-        :rtype: ``str``
-        :return: Name for this image as returned by the cloud middleware.
-        """
-        if isinstance(self._image, GalleryImageReference):
+    def label(self):
+        if self.is_gallery_image:
             return azure_helpers.generate_urn(self._image)
         else:
-            return self._image.tags.get('Name', self._image.name)
+            return self._image.tags.get('Label', None)
 
-    @name.setter
-    def name(self, value):
+    @label.setter
+    def label(self, value):
         """
-        Set the image name.
+        Set the image label when it is a private image.
         """
-        if not isinstance(self._image, GalleryImageReference):
+        if not self.is_gallery_image:
             self.assert_valid_resource_name(value)
-            self._image.tags.update(Name=value)
+            self._image.tags.update(Label=value)
             self._provider.azure_client. \
                 update_image_tags(self.id, self._image.tags)
 
@@ -742,7 +765,7 @@ class AzureMachineImage(BaseMachineImage):
         :rtype: ``str``
         :return: Description for this image as returned by the cloud middleware
         """
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return 'Public gallery image from the Azure Marketplace: '\
                     + self.name
         else:
@@ -753,7 +776,7 @@ class AzureMachineImage(BaseMachineImage):
         """
         Set the image description.
         """
-        if not isinstance(self._image, GalleryImageReference):
+        if not self.is_gallery_image:
             self._image.tags.update(Description=value)
             self._provider.azure_client. \
                 update_image_tags(self.id, self._image.tags)
@@ -769,7 +792,7 @@ class AzureMachineImage(BaseMachineImage):
         :rtype: ``int``
         :return: The minimum disk size needed by this image
         """
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return 0
         else:
             return self._image.storage_profile.os_disk.disk_size_gb or 0
@@ -778,12 +801,12 @@ class AzureMachineImage(BaseMachineImage):
         """
         Delete this image
         """
-        if not isinstance(self._image, GalleryImageReference):
+        if not self.is_gallery_image:
             self._provider.azure_client.delete_image(self.id)
 
     @property
     def state(self):
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return MachineImageState.AVAILABLE
         else:
             return AzureMachineImage.IMAGE_STATE_MAP.get(
@@ -802,7 +825,7 @@ class AzureMachineImage(BaseMachineImage):
         Refreshes the state of this instance by re-querying the cloud provider
         for its latest state.
         """
-        if not isinstance(self._image, dict):
+        if not self.is_gallery_image:
             try:
                 self._image = self._provider.azure_client.get_image(self.id)
                 self._state = self._image.provisioning_state
@@ -823,12 +846,8 @@ class AzureGatewayContainer(BaseGatewayContainer):
         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)
+    def get_or_create_inet_gateway(self, label=None):
         gateway = AzureInternetGateway(self._provider, None, self._network)
-        if name:
-            gateway.name = name
         return gateway
 
     def list(self, limit=None, marker=None):
@@ -856,27 +875,31 @@ class AzureNetwork(BaseNetwork):
     def id(self):
         return self._network.id
 
+    @property
+    def name(self):
+        return self._network.name
+
     @property
     def resource_id(self):
         return self._network.id
 
     @property
-    def name(self):
+    def label(self):
         """
-        Get the network name.
+        Get the network label.
 
-        .. note:: the network must have a (case sensitive) tag ``Name``
+        .. note:: the network must have a (case sensitive) tag ``Label``
         """
-        return self._network.tags.get('Name', self._network.name)
+        return self._network.tags.get('Label', None)
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
-        Set the network name.
+        Set the network label.
         """
         self.assert_valid_resource_name(value)
-        self._network.tags.update(Name=value)
+        self._network.tags.update(Label=value)
         self._provider.azure_client. \
             update_network_tags(self.id, self._network)
 
@@ -930,16 +953,16 @@ class AzureNetwork(BaseNetwork):
         """
         return self._provider.networking.subnets.list(network=self.id)
 
-    def create_subnet(self, cidr_block, name=None, zone=None):
+    def create_subnet(self, cidr_block, label=None, zone=None):
         """
         Create the subnet with cidr_block
         :param cidr_block:
-        :param name:
+        :param label:
         :param zone:
         :return:
         """
         return self._provider.networking.subnets. \
-            create(network=self.id, cidr_block=cidr_block, name=name)
+            create(network=self.id, cidr_block=cidr_block, label=label)
 
     @property
     def gateways(self):
@@ -966,13 +989,19 @@ class AzureFloatingIPContainer(BaseFloatingIPContainer):
         return ClientPagedResultList(self._provider, floating_ips,
                                      limit=limit, marker=marker)
 
-    def create(self):
-        public_ip_address_name = "{0}-{1}".format(
-            'public_ip', uuid.uuid4().hex[:6])
+    def create(self, label=None):
         public_ip_parameters = {
             'location': self._provider.azure_client.region_name,
             'public_ip_allocation_method': 'Static'
         }
+
+        if label:
+            public_ip_parameters.update(tags={'Label':label})
+        else:
+            label = 'cb-ip-'
+
+        public_ip_address_name = "{0}-{1}".format(
+            label, uuid.uuid4().hex[:6])
         floating_ip = self._provider.azure_client.\
             create_floating_ip(public_ip_address_name, public_ip_parameters)
         return AzureFloatingIP(self._provider, floating_ip, self._network_id)
@@ -989,10 +1018,34 @@ class AzureFloatingIP(BaseFloatingIP):
     def id(self):
         return self._ip.id
 
+    @property
+    def name(self):
+        return self._ip.name
+
     @property
     def resource_id(self):
         return self._ip.id
 
+    @property
+    def label(self):
+        """
+        Get the floating IP label.
+
+        .. note:: an instance must have a (case sensitive) tag ``Label``
+        """
+        return self._ip.tags.get('Label', None)
+
+    @label.setter
+    # pylint:disable=arguments-differ
+    def label(self, value):
+        """
+        Set the floating IP label.
+        """
+        self.assert_valid_resource_name(value)
+        self._ip.tags.update(Label=value)
+        self._provider.azure_client. \
+            update_fip_tags(self.id, self._ip)
+
     @property
     def public_ip(self):
         return self._ip.ip_address
@@ -1072,6 +1125,11 @@ class AzurePlacementZone(BasePlacementZone):
         """
         return self._azure_region
 
+    @property
+    def label(self):
+        raise NotImplementedError("Azure Placement Zones do not support "
+                                  "labels")
+
     @property
     def region_name(self):
         """
@@ -1099,17 +1157,18 @@ class AzureSubnet(BaseSubnet):
         return self._subnet.id
 
     @property
-    def resource_id(self):
-        return self._subnet.id
+    def name(self):
+        net_name = self.network_id.split('/')[-1]
+        sn_name = self._subnet.name
+        return '{0}/{1}'.format(net_name, sn_name)
 
     @property
-    def name(self):
-        """
-        Get the subnet name.
+    def label(self):
+        raise NotImplementedError("Azure Subnets do not support labels")
 
-        .. note:: the subnet must have a (case sensitive) tag ``Name``
-        """
-        return self._subnet.name
+    @property
+    def resource_id(self):
+        return self._subnet.id
 
     @property
     def zone(self):
@@ -1203,27 +1262,34 @@ class AzureInstance(BaseInstance):
         """
         return self._vm.id
 
+    @property
+    def name(self):
+        """
+        Get the instance name.
+        """
+        return self._vm.name
+
     @property
     def resource_id(self):
         return self._vm.id
 
     @property
-    def name(self):
+    def label(self):
         """
-        Get the instance name.
+        Get the instance label.
 
-        .. note:: an instance must have a (case sensitive) tag ``Name``
+        .. note:: an instance must have a (case sensitive) tag ``Label``
         """
-        return self._vm.tags.get('Name', self._vm.name)
+        return self._vm.tags.get('Label', None)
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
-        Set the instance name.
+        Set the instance label.
         """
         self.assert_valid_resource_name(value)
-        self._vm.tags.update(Name=value)
+        self._vm.tags.update(Label=value)
         self._provider.azure_client. \
             update_vm_tags(self.id, self._vm)
 
@@ -1370,10 +1436,11 @@ class AzureInstance(BaseInstance):
             'source_virtual_machine': {
                 'id': self.resource_id
             },
-            'tags': {'Name': name}
+            'tags': {'Label': name}
         }
 
-        image = self._provider.azure_client.create_image(name, create_params)
+        image = self._provider.azure_client.create_image(name,
+                                                         create_params)
         return AzureMachineImage(self._provider, image)
 
     def _deprovision(self, private_key_path):
@@ -1525,6 +1592,10 @@ class AzureVMType(BaseVMType):
     def name(self):
         return self._vm_type.name
 
+    @property
+    def label(self):
+        raise NotImplementedError("Azure VMTypes do not support labels")
+
     @property
     def family(self):
         """
@@ -1579,6 +1650,10 @@ class AzureKeyPair(BaseKeyPair):
     def name(self):
         return self._key_pair.Name
 
+    @property
+    def label(self):
+        raise NotImplementedError("Azure Key Pairs do not support labels")
+
     def delete(self):
         self._provider.azure_client.delete_public_key(self._key_pair)
 
@@ -1592,6 +1667,10 @@ class AzureRouter(BaseRouter):
 
     @property
     def id(self):
+        return self._route_table.id
+
+    @property
+    def name(self):
         return self._route_table.name
 
     @property
@@ -1599,22 +1678,22 @@ class AzureRouter(BaseRouter):
         return self._route_table.id
 
     @property
-    def name(self):
+    def label(self):
         """
-        Get the router name.
+        Get the router label.
 
-        .. note:: the router must have a (case sensitive) tag ``Name``
+        .. note:: the router must have a (case sensitive) tag ``Label``
         """
-        return self._route_table.tags.get('Name', self._route_table.name)
+        return self._route_table.tags.get('Label', None)
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
-        Set the router name.
+        Set the router label.
         """
         self.assert_valid_resource_name(value)
-        self._route_table.tags.update(Name=value)
+        self._route_table.tags.update(Label=value)
         self._provider.azure_client. \
             update_route_table_tags(self._route_table.name,
                                     self._route_table)
@@ -1669,25 +1748,15 @@ class AzureInternetGateway(BaseInternetGateway):
 
     @property
     def id(self):
-        return self._name
+        return None
 
     @property
     def name(self):
-        """
-        Get the gateway name.
-
-        .. note:: the gateway must have a (case sensitive) tag ``Name``
-        """
-        return self._name
+        return None
 
-    @name.setter
-    # pylint:disable=arguments-differ
-    def name(self, value):
-        """
-        Set the router name.
-        """
-        self.assert_valid_resource_name(value)
-        self._name = value
+    @property
+    def label(self):
+        None
 
     def refresh(self):
         pass

+ 177 - 119
cloudbridge/cloud/providers/azure/services.py

@@ -67,15 +67,24 @@ class AzureVMFirewallService(BaseVMFirewallService):
                for fw in self.provider.azure_client.list_vm_firewall()]
         return ClientPagedResultList(self.provider, fws, limit, marker)
 
-    def create(self, name, description, network_id=None):
-        AzureVMFirewall.assert_valid_resource_name(name)
-        parameters = {"location": self.provider.region_name,
-                      'tags': {'Name': name}}
+    def create(self, label=None, description=None, network_id=None):
+        parameters = {"location": self.provider.region_name}
+        if label:
+            AzureVMFirewall.assert_valid_resource_name(label)
+            parameters.update({'tags': {'Label': label}})
+        else:
+            label = "cb-fw"
 
         if description:
-            parameters['tags'].update(Description=description)
+            tags = parameters.get('tags')
+            if tags:
+                tags.update(Description=description)
+            else:
+                parameters.update({'tags': {'Description': description}})
 
-        fw = self.provider.azure_client.create_vm_firewall(name, parameters)
+        name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
+        fw = self.provider.azure_client.create_vm_firewall(name,
+                                                           parameters)
 
         # Add default rules to negate azure default rules.
         # See: https://github.com/CloudVE/cloudbridge/issues/106
@@ -86,7 +95,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
             # only 0-4096 are allowed for custom rules
             rule.priority = rule.priority - 61440
             rule.access = "Deny"
-            self._provider.azure_client.create_vm_firewall_rule(
+            self.provider.azure_client.create_vm_firewall_rule(
                 fw.id, rule_name, rule)
 
         # Add a new custom rule allowing all outbound traffic to the internet
@@ -98,7 +107,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
                       "destination_address_prefix": "Internet",
                       "access": "Allow",
                       "direction": "Outbound"}
-        result = self._provider.azure_client.create_vm_firewall_rule(
+        result = self.provider.azure_client.create_vm_firewall_rule(
             fw.id, "cb-default-internet-outbound", parameters)
         fw.security_rules.append(result)
 
@@ -106,18 +115,18 @@ class AzureVMFirewallService(BaseVMFirewallService):
         return cb_fw
 
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['label', 'name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
-        filters = {'Name': name}
-        fws = [AzureVMFirewall(self.provider, vm_firewall)
-               for vm_firewall in azure_helpers.filter_by_tag(
-               self.provider.azure_client.list_vm_firewall(), filters)]
-        return ClientPagedResultList(self.provider, fws)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
     def delete(self, group_id):
         self.provider.azure_client.delete_vm_firewall(group_id)
@@ -153,16 +162,18 @@ class AzureKeyPairService(BaseKeyPairService):
                                      data=results)
 
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
-        key_pair = self.get(name)
         return ClientPagedResultList(self.provider,
-                                     [key_pair] if key_pair else [])
+                                     matches if matches else [])
 
     def create(self, name, public_key_material=None):
         AzureKeyPair.assert_valid_resource_name(name)
@@ -207,17 +218,18 @@ class AzureBucketService(BaseBucketService):
             return None
 
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
-        buckets = [AzureBucket(self.provider, bucket)
-                   for bucket in
-                   self.provider.azure_client.list_containers(prefix=name)]
-        return ClientPagedResultList(self.provider, buckets)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
     def list(self, limit=None, marker=None):
         """
@@ -276,18 +288,18 @@ class AzureVolumeService(BaseVolumeService):
             return None
 
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
-        filters = {'Name': name}
-        cb_vols = [AzureVolume(self.provider, volume)
-                   for volume in azure_helpers.filter_by_tag(
-                   self.provider.azure_client.list_disks(), filters)]
-        return ClientPagedResultList(self.provider, cb_vols)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
     def list(self, limit=None, marker=None):
         """
@@ -298,18 +310,29 @@ class AzureVolumeService(BaseVolumeService):
         return ClientPagedResultList(self.provider, cb_vols,
                                      limit=limit, marker=marker)
 
-    def create(self, name, size, zone=None, snapshot=None, description=None):
+    def create(self, size, label=None, description=None,
+               zone=None, snapshot=None):
         """
         Creates a new volume.
         """
-        AzureVolume.assert_valid_resource_name(name)
+        AzureVolume.assert_valid_resource_name(label)
+        if label:
+            AzureVolume.assert_valid_resource_name(label)
+            tags = {'Label': label}
+        else:
+            label = "cb-vol"
+
+        disk_name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
+
         zone_id = zone.id if isinstance(zone, PlacementZone) else zone
         snapshot = (self.provider.storage.snapshots.get(snapshot)
                     if snapshot and isinstance(snapshot, str) else snapshot)
-        disk_name = "{0}-{1}".format(name, uuid.uuid4().hex[:6])
-        tags = {'Name': name}
+
         if description:
+            if not tags:
+                tags = {}
             tags.update(Description=description)
+
         if snapshot:
             params = {
                 'location':
@@ -317,10 +340,12 @@ class AzureVolumeService(BaseVolumeService):
                 'creation_data': {
                     'create_option': DiskCreateOption.copy,
                     'source_uri': snapshot.resource_id
-                },
-                'tags': tags
+                }
             }
 
+            if tags:
+                params.update(tags=tags)
+
             disk = self.provider.azure_client.create_snapshot_disk(disk_name,
                                                                    params)
 
@@ -331,8 +356,11 @@ class AzureVolumeService(BaseVolumeService):
                 'disk_size_gb': size,
                 'creation_data': {
                     'create_option': DiskCreateOption.empty
-                },
-                'tags': tags}
+                }
+            }
+
+            if tags:
+                params.update(tags=tags)
 
             disk = self.provider.azure_client.create_empty_disk(disk_name,
                                                                 params)
@@ -360,18 +388,18 @@ class AzureSnapshotService(BaseSnapshotService):
             return None
 
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
-        filters = {'Name': name}
-        cb_snapshots = [AzureSnapshot(self.provider, snapshot)
-                        for snapshot in azure_helpers.filter_by_tag(
-                        self.provider.azure_client.list_snapshots(), filters)]
-        return ClientPagedResultList(self.provider, cb_snapshots)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
     def list(self, limit=None, marker=None):
         """
@@ -382,18 +410,24 @@ class AzureSnapshotService(BaseSnapshotService):
                  self.provider.azure_client.list_snapshots()]
         return ClientPagedResultList(self.provider, snaps, limit, marker)
 
-    def create(self, name, volume, description=None):
+    def create(self, volume, label=None, description=None):
         """
         Creates a new snapshot of a given volume.
         """
-        AzureSnapshot.assert_valid_resource_name(name)
         volume = (self.provider.storage.volumes.get(volume)
                   if isinstance(volume, str) else volume)
 
-        tags = {'Name': name}
-        snapshot_name = "{0}-{1}".format(name, uuid.uuid4().hex[:6])
+        if label:
+            AzureSnapshot.assert_valid_resource_name(label)
+            tags = {'Label': label}
+        else:
+            label = "cb-snap"
+
+        snapshot_name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
 
         if description:
+            if not tags:
+                tags = {}
             tags.update(Description=description)
 
         params = {
@@ -402,10 +436,12 @@ class AzureSnapshotService(BaseSnapshotService):
                 'create_option': DiskCreateOption.copy,
                 'source_uri': volume.resource_id
             },
-            'disk_size_gb': volume.size,
-            'tags': tags
+            'disk_size_gb': volume.size
         }
 
+        if tags:
+            params.update(tags=tags)
+
         azure_snap = self.provider.azure_client.create_snapshot(snapshot_name,
                                                                 params)
         return AzureSnapshot(self.provider, azure_snap)
@@ -440,12 +476,16 @@ class AzureInstanceService(BaseInstanceService):
     def __init__(self, provider):
         super(AzureInstanceService, self).__init__(provider)
 
-    def create(self, name, image, vm_type, subnet=None, zone=None,
+    def create(self, image, vm_type, label=None, subnet=None, zone=None,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
 
-        instance_name = name.replace("_", "-") if name \
-            else "{0} - {1}".format("cb", uuid.uuid4())
+        if label:
+            prefix = label
+        else:
+            prefix = "cb-ins"
+
+        instance_name = "{0}-{1}".format(prefix, uuid.uuid4().hex[:6])
 
         AzureInstance.assert_valid_resource_name(instance_name)
 
@@ -478,7 +518,7 @@ class AzureInstanceService(BaseInstanceService):
                                                        instance_name, zone_id)
 
         nic_params = {
-            'location': self._provider.region_name,
+            'location': self.provider.region_name,
             'ip_configurations': [{
                 'name': instance_name + '_ip_config',
                 'private_ip_allocation_method': 'Dynamic',
@@ -520,7 +560,7 @@ class AzureInstanceService(BaseInstanceService):
             temp_key_pair = key_pair
 
         params = {
-            'location': zone_id or self._provider.region_name,
+            'location': zone_id or self.provider.region_name,
             'os_profile': {
                 'admin_username': self.provider.vm_default_user_name,
                 'computer_name': instance_name,
@@ -545,9 +585,12 @@ class AzureInstanceService(BaseInstanceService):
                 }]
             },
             'storage_profile': storage_profile,
-            'tags': {'Name': name}
+            'tags': {}
         }
 
+        if label:
+            params['tags'].update(Label=label)
+
         for disk_def in storage_profile.get('data_disks', []):
             params['tags'] = dict(disk_def.get('tags', {}), **params['tags'])
 
@@ -575,7 +618,7 @@ class AzureInstanceService(BaseInstanceService):
                 temp_key_pair.delete()
         return AzureInstance(self.provider, vm)
 
-    def _resolve_launch_options(self, name, subnet=None, zone_id=None,
+    def _resolve_launch_options(self, inst_name, subnet=None, zone_id=None,
                                 vm_firewalls=None):
         if subnet:
             # subnet's zone takes precedence
@@ -595,7 +638,8 @@ class AzureInstanceService(BaseInstanceService):
 
             if len(vm_firewalls) > 1:
                 new_fw = self.provider.security.vm_firewalls.\
-                    create('{0}-fw'.format(name), 'Merge vm firewall {0}'.
+                    create(label='{0}-fw'.format(inst_name),
+                           description='Merge vm firewall {0}'.
                            format(','.join(vm_firewalls_ids)))
 
                 for fw in vm_firewalls:
@@ -735,18 +779,18 @@ class AzureInstanceService(BaseInstanceService):
             return None
 
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
-        filtr = {'Name': name}
-        instances = [AzureInstance(self.provider, inst)
-                     for inst in azure_helpers.filter_by_tag(
-                     self.provider.azure_client.list_vm(), filtr)]
-        return ClientPagedResultList(self.provider, instances)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
 
 class AzureImageService(BaseImageService):
@@ -766,23 +810,18 @@ class AzureImageService(BaseImageService):
             return None
 
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
-
-        filters = {'Name': name}
-        cb_images = [AzureMachineImage(self.provider, image)
-                     for image in azure_helpers.filter_by_tag(
-                     self.provider.azure_client.list_images(), filters)]
-        # All gallery image properties (id, resource_id, name) are the URN
-        # Improvement: wrap the filters by publisher, offer, etc...
-        cb_images.extend([AzureMachineImage(self.provider, image) for image
-                          in self.provider.azure_client.list_gallery_refs()
-                          if azure_helpers.generate_urn(image) == name])
-        return ClientPagedResultList(self.provider, cb_images)
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
+
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
     def list(self, limit=None, marker=None):
         """
@@ -859,26 +898,28 @@ class AzureNetworkService(BaseNetworkService):
                                      limit=limit, marker=marker)
 
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
-        filters = {'Name': name}
-        networks = [
-            AzureNetwork(self.provider, network) for network
-            in azure_helpers.filter_by_tag(
-                self.provider.azure_client.list_networks(), filters)]
-        return ClientPagedResultList(self.provider, networks)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
-    def create(self, name, cidr_block):
+    def create(self, cidr_block, label=None):
         # 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
-        if name:
-            network_name = "{0}-{1}".format(name, uuid.uuid4().hex[:6])
+        if label:
+            tags = {'Label': label }
+        else:
+            label = 'cb-net'
+
+        network_name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
 
         AzureNetwork.assert_valid_resource_name(network_name)
 
@@ -886,9 +927,12 @@ class AzureNetworkService(BaseNetworkService):
             'location': self.provider.azure_client.region_name,
             'address_space': {
                 'address_prefixes': [cidr_block]
-            },
-            'tags': {'Name': name or AzureNetwork.CB_DEFAULT_NETWORK_NAME}
+            }
         }
+
+        if tags:
+            params.update(tags=tags)
+
         az_network = self.provider.azure_client.create_network(network_name,
                                                                params)
         cb_network = AzureNetwork(self.provider, az_network)
@@ -982,20 +1026,30 @@ class AzureSubnetService(BaseSubnetService):
         obj_list = self._list_subnets(network)
         filters = ['name']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
-        return ClientPagedResultList(self._provider, list(matches))
 
-    def create(self, network, cidr_block, name=None, **kwargs):
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
+
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
+
+    def create(self, network, cidr_block, prefix=None, **kwargs):
         """
         Create subnet
         """
-        AzureSubnet.assert_valid_resource_name(name)
         network_id = network.id \
             if isinstance(network, Network) else network
 
-        if not name:
-            subnet_name = AzureSubnet.CB_DEFAULT_SUBNET_NAME
+        if prefix:
+            AzureSubnet.assert_valid_resource_name(prefix)
+
         else:
-            subnet_name = name
+            prefix = "cb-sn"
+
+        subnet_name = "{0}-{1}".format(prefix, uuid.uuid4().hex[:6])
 
         subnet_info = self.provider.azure_client\
             .create_subnet(
@@ -1012,22 +1066,22 @@ class AzureSubnetService(BaseSubnetService):
         default_cidr = '10.0.1.0/24'
 
         # No provider-default Subnet exists, look for a library-default one
-        matches = self.find(name=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
+        matches = self.find(label=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
         if matches:
             return matches[0]
 
         # No provider-default Subnet exists, try to create it (net + subnets)
         networks = self.provider.networking.networks.find(
-            name=AzureNetwork.CB_DEFAULT_NETWORK_NAME)
+            label=AzureNetwork.CB_DEFAULT_NETWORK_NAME)
 
         if networks:
             network = networks[0]
         else:
-            network = self.provider.networking.networks.create(
-                AzureNetwork.CB_DEFAULT_NETWORK_NAME, '10.0.0.0/16')
+            network = self.provider.networking.networks.create('10.0.0.0/16',
+                label=AzureNetwork.CB_DEFAULT_NETWORK_NAME)
 
         subnet = self.create(network, default_cidr,
-                             name=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
+                             prefix=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
         return subnet
 
     def delete(self, subnet):
@@ -1049,19 +1103,18 @@ class AzureRouterService(BaseRouterService):
             return None
 
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
-
-        filters = {'Name': name}
-        routes = [AzureRouter(self.provider, route)
-                  for route in azure_helpers.filter_by_tag(
-                  self.provider.azure_client.list_route_tables(), filters)]
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
-        return ClientPagedResultList(self.provider, routes)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
     def list(self, limit=None, marker=None):
         routes = [AzureRouter(self.provider, route)
@@ -1071,10 +1124,15 @@ class AzureRouterService(BaseRouterService):
                                      routes,
                                      limit=limit, marker=marker)
 
-    def create(self, name, network):
-        AzureRouter.assert_valid_resource_name(name)
-        parameters = {"location": self.provider.region_name,
-                      'tags': {'Name': name}}
+    def create(self, network, label=None):
+        AzureRouter.assert_valid_resource_name(label)
+        parameters = {"location": self.provider.region_name}
+        if label:
+            parameters.update(tags={'Label': label})
+        else:
+            label = 'cb-router'
+
+        router_name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
         route = self.provider.azure_client. \
-            create_route_table(name, parameters)
+            create_route_table(router_name, parameters)
         return AzureRouter(self.provider, route)

+ 22 - 12
docs/getting_started.rst

@@ -86,7 +86,7 @@ Once you have a reference to a provider, explore the cloud platform:
 
 .. code-block:: python
 
-    provider.security.security_groups.list()
+    provider.security.firewalls.list()
     provider.compute.vm_types.list()
     provider.storage.snapshots.list()
     provider.storage.buckets.list()
@@ -116,12 +116,12 @@ attaching an internet gateway to the subnet via a router.
 
 .. code-block:: python
 
-    net = provider.networking.networks.create(
-        name='my-network', cidr_block='10.0.0.0/16')
-    sn = net.create_subnet(name='my-subnet', cidr_block='10.0.0.0/28')
-    router = provider.networking.routers.create(network=net, name='my-router')
+    net = provider.networking.networks.create(cidr_block='10.0.0.0/16',
+                                              label='my-network')
+    sn = net.create_subnet(cidr_block='10.0.0.0/28', label='my-subnet')
+    router = provider.networking.routers.create(network=net, label='my-router')
     router.attach_subnet(sn)
-    gateway = net.gateways.get_or_create_inet_gateway(name='my-gateway')
+    gateway = net.gateways.get_or_create_inet_gateway(label='my-gateway')
     router.attach_gateway(gateway)
 
 
@@ -135,7 +135,8 @@ a private network.
 
     from cloudbridge.cloud.interfaces.resources import TrafficDirection
     fw = provider.security.vm_firewalls.create(
-        'cloudbridge-intro', 'A VM firewall used by CloudBridge', net.id)
+        label='cloudbridge-intro', description='A VM firewall used by
+        CloudBridge', network=net.id)
     fw.rules.create(TrafficDirection.INBOUND, 'tcp', 22, 22, '0.0.0.0/0')
 
 Launch an instance
@@ -151,7 +152,7 @@ also add the network interface as a launch argument.
                       if t.vcpus >= 2 and t.ram >= 4],
                       key=lambda x: x.vcpus*x.ram)[0]
     inst = provider.compute.instances.create(
-        name='cloudbridge-intro', image=img, vm_type=vm_type,
+        image=img, vm_type=vm_type, label='cloudbridge-intro',
         subnet=sn, key_pair=kp, vm_firewalls=[fw])
     # Wait until ready
     inst.wait_till_ready()  # This is a blocking call
@@ -190,10 +191,10 @@ From the command prompt, you can now ssh into the instance
 Get a resource
 --------------
 When a resource already exists, a reference to it can be retrieved using either
-its ID or name. It is important to note that while IDs are unique, multiple
-resources of the same type could use the same name on some providers, thus the
+its ID, name, or label. It is important to note that while IDs and names are
+unique, multiple resources of the same type could use the same label, thus the
 `find` method always returns a list, while the `get` method returns a single
-object. While the methods are similar across resources, they are explicitely
+object. While the methods are similar across resources, they are explicitly
 listed in order to help map each resource with the service that handles it.
 
 .. code-block:: python
@@ -201,24 +202,30 @@ listed in order to help map each resource with the service that handles it.
     # Key Pair
     kp = provider.security.key_pairs.get('keypair ID')
     kp_list = provider.security.key_pairs.find(name='cloudbridge_intro')
+    kp_list = provider.security.key_pairs.find(label='cloudbridge_intro')
     kp = kp_list[0]
 
     # Network
     net = provider.networking.networks.get('network ID')
     net_list = provider.networking.networks.find(name='my-network')
+    net_list = provider.networking.networks.find(label='my-network')
     net = net_list[0]
 
     # Subnet
     sn = provider.networking.subnets.get('subnet ID')
     # Unknown network
     sn_list = provider.networking.subnets.find(name='my-subnet')
+    sn_list = provider.networking.subnets.find(label='my-subnet')
     # Known network
     sn_list = provider.networking.subnets.find(network=net.id, name='my-subnet')
+    sn_list = provider.networking.subnets.find(network=net.id,
+                                               label='my-subnet')
     sn = sn_list(0)
 
     # Router
     router = provider.networking.routers.get('router ID')
     router_list = provider.networking.routers.find(name='my-router')
+    router_list = provider.networking.routers.find(label='my-router')
     router = router_list[0]
 
     # Gateway
@@ -228,18 +235,21 @@ listed in order to help map each resource with the service that handles it.
     fip = gateway.floating_ips.get('FloatingIP ID')
     # Find using public IP address
     fip_list = gateway.floating_ips.find(public_ip='IP address')
-    # Find using tagged name
+    # Find using name or tag
     fip_list = net.gateways.floating_ips.find(name='my-fip')
+    fip_list = net.gateways.floating_ips.find(label='my-fip')
     fip = fip_list[0]
 
     # Firewall
     fw = provider.security.vm_firewalls.get('firewall ID')
     fw_list = provider.security.vm_firewalls.find(name='cloudbridge-intro')
+    fw_list = provider.security.vm_firewalls.find(label='cloudbridge-intro')
     fw = fw_list[0]
 
     # Instance
     inst = provider.compute.instances.get('instance ID')
     inst_list = provider.compute.instances.list(name='cloudbridge-intro')
+    inst_list = provider.compute.instances.list(label='cloudbridge-intro')
     inst = inst_list[0]