Explorar el Código

Merge pull request #162 from CloudVE/consistency

Making CloudBridge more robust and consistent
Nuwan Goonasekera hace 7 años
padre
commit
2214bb8276

+ 85 - 44
cloudbridge/cloud/base/services.py

@@ -4,6 +4,10 @@ Base implementation for services available through a provider
 import logging
 
 import cloudbridge.cloud.base.helpers as cb_helpers
+from cloudbridge.cloud.base.resources import BaseNetwork
+from cloudbridge.cloud.base.resources import BaseRouter
+from cloudbridge.cloud.base.resources import BaseSubnet
+from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import Router
 from cloudbridge.cloud.interfaces.services import BucketService
 from cloudbridge.cloud.interfaces.services import CloudService
@@ -39,24 +43,42 @@ class BaseCloudService(CloudService):
         return self._provider
 
 
-class BaseComputeService(ComputeService, BaseCloudService):
+class BaseSecurityService(SecurityService, BaseCloudService):
 
     def __init__(self, provider):
-        super(BaseComputeService, self).__init__(provider)
+        super(BaseSecurityService, self).__init__(provider)
 
 
-class BaseVolumeService(
-        BasePageableObjectMixin, VolumeService, BaseCloudService):
+class BaseKeyPairService(
+        BasePageableObjectMixin, KeyPairService, BaseCloudService):
 
     def __init__(self, provider):
-        super(BaseVolumeService, self).__init__(provider)
+        super(BaseKeyPairService, self).__init__(provider)
+
+    def delete(self, key_pair_id):
+        """
+        Delete an existing key pair.
 
+        :type key_pair_id: str
+        :param key_pair_id: The id of the key pair to be deleted.
 
-class BaseSnapshotService(
-        BasePageableObjectMixin, SnapshotService, BaseCloudService):
+        :rtype: ``bool``
+        :return:  ``True`` if the key does not exist. Note that this implies
+                  that the key may not have been deleted by this method but
+                  instead has not existed in the first place.
+        """
+        log.info("Deleting the existing key pair %s", key_pair_id)
+        kp = self.get(key_pair_id)
+        if kp:
+            kp.delete()
+        return True
+
+
+class BaseVMFirewallService(
+        BasePageableObjectMixin, VMFirewallService, BaseCloudService):
 
     def __init__(self, provider):
-        super(BaseSnapshotService, self).__init__(provider)
+        super(BaseVMFirewallService, self).__init__(provider)
 
 
 class BaseStorageService(StorageService, BaseCloudService):
@@ -65,56 +87,45 @@ class BaseStorageService(StorageService, BaseCloudService):
         super(BaseStorageService, self).__init__(provider)
 
 
-class BaseImageService(
-        BasePageableObjectMixin, ImageService, BaseCloudService):
+class BaseVolumeService(
+        BasePageableObjectMixin, VolumeService, BaseCloudService):
 
     def __init__(self, provider):
-        super(BaseImageService, self).__init__(provider)
+        super(BaseVolumeService, self).__init__(provider)
 
 
-class BaseBucketService(
-        BasePageableObjectMixin, BucketService, BaseCloudService):
+class BaseSnapshotService(
+        BasePageableObjectMixin, SnapshotService, BaseCloudService):
 
     def __init__(self, provider):
-        super(BaseBucketService, self).__init__(provider)
+        super(BaseSnapshotService, self).__init__(provider)
 
 
-class BaseSecurityService(SecurityService, BaseCloudService):
+class BaseBucketService(
+        BasePageableObjectMixin, BucketService, BaseCloudService):
 
     def __init__(self, provider):
-        super(BaseSecurityService, self).__init__(provider)
+        super(BaseBucketService, self).__init__(provider)
 
 
-class BaseKeyPairService(
-        BasePageableObjectMixin, KeyPairService, BaseCloudService):
+class BaseComputeService(ComputeService, BaseCloudService):
 
     def __init__(self, provider):
-        super(BaseKeyPairService, self).__init__(provider)
+        super(BaseComputeService, self).__init__(provider)
 
-    def delete(self, key_pair_id):
-        """
-        Delete an existing key pair.
 
-        :type key_pair_id: str
-        :param key_pair_id: The id of the key pair to be deleted.
+class BaseImageService(
+        BasePageableObjectMixin, ImageService, BaseCloudService):
 
-        :rtype: ``bool``
-        :return:  ``True`` if the key does not exist. Note that this implies
-                  that the key may not have been deleted by this method but
-                  instead has not existed in the first place.
-        """
-        log.info("Deleting the existing key pair %s", key_pair_id)
-        kp = self.get(key_pair_id)
-        if kp:
-            kp.delete()
-        return True
+    def __init__(self, provider):
+        super(BaseImageService, self).__init__(provider)
 
 
-class BaseVMFirewallService(
-        BasePageableObjectMixin, VMFirewallService, BaseCloudService):
+class BaseInstanceService(
+        BasePageableObjectMixin, InstanceService, BaseCloudService):
 
     def __init__(self, provider):
-        super(BaseVMFirewallService, self).__init__(provider)
+        super(BaseInstanceService, self).__init__(provider)
 
 
 class BaseVMTypeService(
@@ -134,13 +145,6 @@ class BaseVMTypeService(
         return ClientPagedResultList(self._provider, list(matches))
 
 
-class BaseInstanceService(
-        BasePageableObjectMixin, InstanceService, BaseCloudService):
-
-    def __init__(self, provider):
-        super(BaseInstanceService, self).__init__(provider)
-
-
 class BaseRegionService(
         BasePageableObjectMixin, RegionService, BaseCloudService):
 
@@ -177,6 +181,18 @@ class BaseNetworkService(
             log.info("Deleting network %s", network_id)
             network.delete()
 
+    def get_or_create_default(self):
+        networks = self.provider.networking.networks.find(
+            label=BaseNetwork.CB_DEFAULT_NETWORK_LABEL)
+
+        if networks:
+            return networks[0]
+        else:
+            log.info("Creating a CloudBridge-default network labeled %s",
+                     BaseNetwork.CB_DEFAULT_NETWORK_LABEL)
+            return self.provider.networking.networks.create(
+                BaseNetwork.CB_DEFAULT_NETWORK_LABEL, '10.0.0.0/16')
+
 
 class BaseSubnetService(
         BasePageableObjectMixin, SubnetService, BaseCloudService):
@@ -190,6 +206,20 @@ class BaseSubnetService(
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         return ClientPagedResultList(self._provider, list(matches))
 
+    def get_or_create_default(self, zone):
+        default_cidr = '10.0.0.0/24'
+
+        # Look for a CB-default subnet
+        matches = self.find(label=BaseSubnet.CB_DEFAULT_SUBNET_LABEL)
+        if matches:
+            return matches[0]
+
+        # No provider-default Subnet exists, try to create it (net + subnets)
+        network = self.provider.networking.networks.get_or_create_default()
+        subnet = self.create(BaseSubnet.CB_DEFAULT_SUBNET_LABEL, network,
+                             default_cidr, zone)
+        return subnet
+
 
 class BaseRouterService(
         BasePageableObjectMixin, RouterService, BaseCloudService):
@@ -207,3 +237,14 @@ class BaseRouterService(
             if router:
                 log.info("Router %s successful deleted.", router)
                 router.delete()
+
+    def get_or_create_default(self, network):
+        net_id = network.id if isinstance(network, Network) else network
+        routers = self.provider.networking.routers.find(
+            label=BaseRouter.CB_DEFAULT_ROUTER_LABEL)
+        for router in routers:
+            if router.network_id == net_id:
+                return router
+        else:
+            return self.provider.networking.routers.create(
+                network=net_id, label=BaseRouter.CB_DEFAULT_ROUTER_LABEL)

+ 4 - 4
cloudbridge/cloud/interfaces/services.py

@@ -730,7 +730,7 @@ class SubnetService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def create(self, label, network_id, cidr_block, zone):
+    def create(self, label, network, cidr_block, zone):
         """
         Create a new subnet within the supplied network.
 
@@ -1119,15 +1119,15 @@ class VMFirewallService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def create(self, label, network_id, description=None):
+    def create(self, label, network, description=None):
         """
         Create a new VMFirewall.
 
         :type label: str
         :param label: The label for the new VM firewall.
 
-        :type  network_id: ``str``
-        :param network_id: Network ID under which to create the VM firewall.
+        :type  network: ``str``
+        :param network: Network ID under which to create the VM firewall.
 
         :type description: str
         :param description: The description of the new VM firewall.

+ 102 - 79
cloudbridge/cloud/providers/aws/services.py

@@ -136,7 +136,7 @@ class AWSVMFirewallService(BaseVMFirewallService):
         return self.svc.list(limit=limit, marker=marker)
 
     @cb_helpers.deprecated_alias(network_id='network')
-    def create(self, label, network=None, description=None):
+    def create(self, label, network, description=None):
         log.debug("Creating Firewall Service with the parameters "
                   "[label: %s id: %s description: %s]", label, network,
                   description)
@@ -374,6 +374,32 @@ class AWSBucketService(BaseBucketService):
                     raise
 
 
+class AWSComputeService(BaseComputeService):
+
+    def __init__(self, provider):
+        super(AWSComputeService, self).__init__(provider)
+        self._vm_type_svc = AWSVMTypeService(self.provider)
+        self._instance_svc = AWSInstanceService(self.provider)
+        self._region_svc = AWSRegionService(self.provider)
+        self._images_svc = AWSImageService(self.provider)
+
+    @property
+    def images(self):
+        return self._images_svc
+
+    @property
+    def vm_types(self):
+        return self._vm_type_svc
+
+    @property
+    def instances(self):
+        return self._instance_svc
+
+    @property
+    def regions(self):
+        return self._region_svc
+
+
 class AWSImageService(BaseImageService):
 
     def __init__(self, provider):
@@ -388,7 +414,6 @@ class AWSImageService(BaseImageService):
 
     def find(self, **kwargs):
         # Filter by name or label
-        name = kwargs.get('name', None)
         label = kwargs.get('label', None)
         # Popped here, not used in the generic find
         owner = kwargs.pop('owners', None)
@@ -397,16 +422,23 @@ class AWSImageService(BaseImageService):
             extra_args.update(Owners=owner)
 
         obj_list = []
-        if name:
-            log.debug("Searching for AWS Image Service %s", name)
-            obj_list.extend(self.svc.find(filter_name='name',
-                                          filter_value=name, **extra_args))
+
+        # The original list is made by combining both searches by "tag:Name"
+        # and "AMI name" to allow for searches of public images
         if label:
+            log.debug("Searching for AWS Image Service %s", label)
+            obj_list.extend(self.svc.find(filter_name='name',
+                                          filter_value=label, **extra_args))
             obj_list.extend(self.svc.find(filter_name='tag:Name',
                                           filter_value=label, **extra_args))
-        if not name and not label:
+
+        if not label:
             obj_list = self
 
+        # Add name filter for the generic find method, to allow searching
+        # through AMI names for a match (public images will likely have an
+        # AMI name and no tag:Name)
+        kwargs.update({'name': label})
         filters = ['label', 'name']
         return cb_helpers.generic_find(filters, kwargs, obj_list)
 
@@ -416,32 +448,6 @@ class AWSImageService(BaseImageService):
                              limit=limit, marker=marker)
 
 
-class AWSComputeService(BaseComputeService):
-
-    def __init__(self, provider):
-        super(AWSComputeService, self).__init__(provider)
-        self._vm_type_svc = AWSVMTypeService(self.provider)
-        self._instance_svc = AWSInstanceService(self.provider)
-        self._region_svc = AWSRegionService(self.provider)
-        self._images_svc = AWSImageService(self.provider)
-
-    @property
-    def images(self):
-        return self._images_svc
-
-    @property
-    def vm_types(self):
-        return self._vm_type_svc
-
-    @property
-    def instances(self):
-        return self._instance_svc
-
-    @property
-    def regions(self):
-        return self._region_svc
-
-
 class AWSInstanceService(BaseInstanceService):
 
     def __init__(self, provider):
@@ -724,6 +730,25 @@ class AWSNetworkService(BaseNetworkService):
             cb_net.label = label
         return cb_net
 
+    def get_or_create_default(self):
+        # # Look for provided default network
+        # for net in self.provider.networking.networks:
+        #     if net._vpc.is_default:
+        #         return net
+
+        # No provider-default, try CB-default instead
+        default_nets = self.provider.networking.networks.find(
+            label=AWSNetwork.CB_DEFAULT_NETWORK_LABEL)
+        if default_nets:
+            return default_nets[0]
+
+        else:
+            log.info("Creating a CloudBridge-default network labeled %s",
+                     AWSNetwork.CB_DEFAULT_NETWORK_LABEL)
+            return self.provider.networking.networks.create(
+                label=AWSNetwork.CB_DEFAULT_NETWORK_LABEL,
+                cidr_block='10.0.0.0/16')
+
 
 class AWSSubnetService(BaseSubnetService):
 
@@ -777,51 +802,48 @@ class AWSSubnetService(BaseSubnetService):
         return subnet
 
     def get_or_create_default(self, zone):
-        zone_name = zone.name if isinstance(
-            zone, AWSPlacementZone) else zone
-        snl = self.svc.find('availabilityZone', zone_name)
-        # Find first available default subnet by sorted order
-        # of availability zone. Prefer zone us-east-1a over 1e,
-        # because newer zones tend to have less compatibility
-        # with different instance types (e.g. c5.large not available
-        # on us-east-1e as of 14 Dec. 2017).
-        # pylint:disable=protected-access
-        snl.sort(key=lambda sn: sn._subnet.availability_zone)
-        for sn in snl:
-            # pylint:disable=protected-access
-            if sn._subnet.default_for_az:
-                return sn
-
-        # Refresh the list for the default label
-        snl = self.find(label=AWSSubnet.CB_DEFAULT_SUBNET_LABEL)
+        zone_name = zone.name if isinstance(zone, AWSPlacementZone) else zone
 
-        if len(snl) > 0:
-            return snl[0]
-
-        """
-        No provider-default Subnet exists, try to create a CloudBridge-specific
-        network. This involves creating the network, subnets, internet gateway,
-        and connecting it all together so that the network has Internet
-        connectivity.
-        """
-        # Check if a default net already exists
-        default_nets = self.provider.networking.networks.find(
-            label=AWSNetwork.CB_DEFAULT_NETWORK_LABEL)
-        if len(default_nets) > 0:
-            default_net = default_nets[0]
-            for sn in default_net.subnets:
-                if zone and zone == sn.zone.name:
-                    return sn
-            if len(default_net.subnets) == 0:
-                pass  # No subnets exist within the default net so continue
-            else:
-                return default_net.subnets[0]  # Pick a (first) subnet
-        else:
-            log.info("Creating a CloudBridge-default network labeled %s",
-                     AWSNetwork.CB_DEFAULT_NETWORK_LABEL)
-            default_net = self.provider.networking.networks.create(
-                label=AWSNetwork.CB_DEFAULT_NETWORK_LABEL,
-                cidr_block='10.0.0.0/16')
+        # # Look for provider default subnet in current zone
+        # if zone_name:
+        #     snl = self.svc.find('availabilityZone', zone_name)
+        #
+        # else:
+        #     snl = self.svc.list()
+        #     # Find first available default subnet by sorted order
+        #     # of availability zone. Prefer zone us-east-1a over 1e,
+        #     # because newer zones tend to have less compatibility
+        #     # with different instance types (e.g. c5.large not available
+        #     # on us-east-1e as of 14 Dec. 2017).
+        #     # pylint:disable=protected-access
+        #     snl.sort(key=lambda sn: sn._subnet.availability_zone)
+        #
+        # for sn in snl:
+        #     # pylint:disable=protected-access
+        #     if sn._subnet.default_for_az:
+        #         return sn
+
+        # If no provider-default subnet has been found, look for
+        # cloudbridge-default by label. We suffix labels by availability zone,
+        # thus we add the wildcard for the regular expression to find the
+        # subnet
+        snl = self.find(label=AWSSubnet.CB_DEFAULT_SUBNET_LABEL + "*")
+
+        if snl:
+            snl.sort(key=lambda sn: sn._subnet.availability_zone)
+            if not zone_name:
+                return snl[0]
+            for subnet in snl:
+                if subnet.zone.name == zone_name:
+                    return subnet
+
+        # No default Subnet exists, try to create a CloudBridge-specific
+        # subnet. This involves creating the network, subnets, internet
+        # gateway, and connecting it all together so that the network has
+        # Internet connectivity.
+
+        # Check if a default net already exists and get it or create on
+        default_net = self.provider.networking.networks.get_or_create_default()
 
         # Get/create an internet gateway for the default network and a
         # corresponding router if it does not already exist.
@@ -845,7 +867,7 @@ class AWSSubnetService(BaseSubnetService):
         # Create a subnet in each of the region's zones
         region = self.provider.compute.regions.get(self.provider.region_name)
         default_sn = None
-        for i, z in enumerate(region.zones):
+        for i, z in reversed(list(enumerate(region.zones))):
             sn_label = "{0}-{1}".format(AWSSubnet.CB_DEFAULT_SUBNET_LABEL,
                                         z.id[-1])
             log.info("Creating default CloudBridge subnet %s", sn_label)
@@ -854,9 +876,10 @@ class AWSSubnetService(BaseSubnetService):
             # Create a route table entry between the SN and the inet gateway
             # See note above about why this is commented
             # default_router.attach_subnet(sn)
-            if zone and zone == z.name:
+            if zone and zone_name == z.name:
                 default_sn = sn
         # No specific zone was supplied; return the last created subnet
+        # The list was originally reversed to have the last subnet be in zone a
         if not default_sn:
             default_sn = sn
         return default_sn

+ 3 - 3
cloudbridge/cloud/providers/azure/resources.py

@@ -40,7 +40,7 @@ class AzureVMFirewall(BaseVMFirewall):
 
     @property
     def network_id(self):
-        return None
+        return self._vm_firewall.tags.get('network_id', None)
 
     @property
     def resource_id(self):
@@ -1317,10 +1317,10 @@ class AzureInstance(BaseInstance):
         """
         self._provider.azure_client.deallocate_vm(self.id)
         self._provider.azure_client.delete_vm(self.id)
-        for public_ip_id in self._public_ip_ids:
-            self._provider.azure_client.delete_floating_ip(public_ip_id)
         for nic_id in self._nic_ids:
             self._provider.azure_client.delete_nic(nic_id)
+        for public_ip_id in self._public_ip_ids:
+            self._provider.azure_client.delete_floating_ip(public_ip_id)
         for data_disk in self._vm.storage_profile.data_disks:
             if data_disk.managed_disk:
                 if self._vm.tags.get('delete_on_terminate',

+ 126 - 151
cloudbridge/cloud/providers/azure/services.py

@@ -67,11 +67,13 @@ class AzureVMFirewallService(BaseVMFirewallService):
         return ClientPagedResultList(self.provider, fws, limit, marker)
 
     @cb_helpers.deprecated_alias(network_id='network')
-    def create(self, label, network=None, description=None):
+    def create(self, label, network, description=None):
         AzureVMFirewall.assert_valid_resource_label(label)
         name = AzureVMFirewall._generate_name_from_label(label, "cb-fw")
+        net = network.id if isinstance(network, Network) else network
         parameters = {"location": self.provider.region_name,
-                      "tags": {'Label': label}}
+                      "tags": {'Label': label,
+                               'network_id': net}}
 
         if description:
             parameters['tags'].update(Description=description)
@@ -140,7 +142,8 @@ class AzureKeyPairService(BaseKeyPairService):
                 return AzureKeyPair(self.provider, key_pair)
             return None
         except AzureException as error:
-            log.exception(error)
+            log.debug("KeyPair %s was not found.", key_pair_id)
+            log.debug(error)
             return None
 
     def list(self, limit=None, marker=None):
@@ -194,59 +197,6 @@ class AzureKeyPairService(BaseKeyPairService):
         return key_pair
 
 
-class AzureBucketService(BaseBucketService):
-    def __init__(self, provider):
-        super(AzureBucketService, self).__init__(provider)
-
-    def get(self, bucket_id):
-        """
-        Returns a bucket given its ID. Returns ``None`` if the bucket
-        does not exist.
-        """
-        try:
-            bucket = self.provider.azure_client.get_container(bucket_id)
-            return AzureBucket(self.provider, bucket)
-        except AzureException as error:
-            log.exception(error)
-            return None
-
-    def find(self, **kwargs):
-        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,
-                                                           ", ".join(filters)))
-
-        return ClientPagedResultList(self.provider,
-                                     matches if matches else [])
-
-    def list(self, limit=None, marker=None):
-        """
-        List all containers.
-        """
-        buckets, resume_marker = self.provider.azure_client.list_containers(
-            limit=limit or self.provider.config.default_result_limit,
-            marker=marker)
-        results = [AzureBucket(self.provider, bucket)
-                   for bucket in buckets]
-        return ServerPagedResultList(is_truncated=resume_marker,
-                                     marker=resume_marker,
-                                     supports_total=False,
-                                     data=results)
-
-    def create(self, name, location=None):
-        """
-        Create a new bucket.
-        """
-        AzureBucket.assert_valid_resource_name(name)
-        bucket = self.provider.azure_client.create_container(name)
-        return AzureBucket(self.provider, bucket)
-
-
 class AzureStorageService(BaseStorageService):
     def __init__(self, provider):
         super(AzureStorageService, self).__init__(provider)
@@ -308,8 +258,7 @@ class AzureVolumeService(BaseVolumeService):
         return ClientPagedResultList(self.provider, cb_vols,
                                      limit=limit, marker=marker)
 
-    def create(self, label, size, zone=None, description=None,
-               snapshot=None):
+    def create(self, label, size, zone, description=None, snapshot=None):
         """
         Creates a new volume.
         """
@@ -376,7 +325,7 @@ class AzureSnapshotService(BaseSnapshotService):
 
     def find(self, **kwargs):
         obj_list = self
-        filters = ['name', 'label']
+        filters = ['label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
         # All kwargs should have been popped at this time.
@@ -426,6 +375,54 @@ class AzureSnapshotService(BaseSnapshotService):
         return AzureSnapshot(self.provider, azure_snap)
 
 
+class AzureBucketService(BaseBucketService):
+    def __init__(self, provider):
+        super(AzureBucketService, self).__init__(provider)
+
+    def get(self, bucket_id):
+        """
+        Returns a bucket given its ID. Returns ``None`` if the bucket
+        does not exist.
+        """
+        try:
+            bucket = self.provider.azure_client.get_container(bucket_id)
+            return AzureBucket(self.provider, bucket)
+        except AzureException as error:
+            log.exception(error)
+            return None
+
+    def find(self, **kwargs):
+        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,
+                                                           ", ".join(filters)))
+
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
+
+    def list(self, limit=None, marker=None):
+        """
+        List all containers.
+        """
+        buckets = [AzureBucket(self.provider, bucket)
+                   for bucket in self.provider.azure_client.list_containers()]
+        return ClientPagedResultList(self.provider, buckets,
+                                     limit=limit, marker=marker)
+
+    def create(self, name, location=None):
+        """
+        Create a new bucket.
+        """
+        AzureBucket.assert_valid_resource_name(name)
+        bucket = self.provider.azure_client.create_container(name)
+        return AzureBucket(self.provider, bucket)
+
+
 class AzureComputeService(BaseComputeService):
     def __init__(self, provider):
         super(AzureComputeService, self).__init__(provider)
@@ -451,11 +448,54 @@ class AzureComputeService(BaseComputeService):
         return self._region_svc
 
 
+class AzureImageService(BaseImageService):
+    def __init__(self, provider):
+        super(AzureImageService, self).__init__(provider)
+
+    def get(self, image_id):
+        """
+        Returns an Image given its id
+        """
+        try:
+            image = self.provider.azure_client.get_image(image_id)
+            return AzureMachineImage(self.provider, image)
+        except (CloudError, InvalidValueException) as cloud_error:
+            # Azure raises the cloud error if the resource not available
+            log.exception(cloud_error)
+            return None
+
+    def find(self, **kwargs):
+        obj_list = self
+        filters = ['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,
+                                                           ", ".join(filters)))
+
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
+
+    def list(self, filter_by_owner=True, limit=None, marker=None):
+        """
+        List all images.
+        """
+        azure_images = self.provider.azure_client.list_images()
+        azure_gallery_refs = self.provider.azure_client.list_gallery_refs() \
+            if not filter_by_owner else []
+        cb_images = [AzureMachineImage(self.provider, img)
+                     for img in azure_images + azure_gallery_refs]
+        return ClientPagedResultList(self.provider, cb_images,
+                                     limit=limit, marker=marker)
+
+
 class AzureInstanceService(BaseInstanceService):
     def __init__(self, provider):
         super(AzureInstanceService, self).__init__(provider)
 
-    def create(self, label, image, vm_type, subnet=None, zone=None,
+    def create(self, label, image, vm_type, subnet, zone,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
 
@@ -750,37 +790,6 @@ class AzureInstanceService(BaseInstanceService):
             log.exception(cloud_error)
             return None
 
-    def find(self, **kwargs):
-        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,
-                                                           ", ".join(filters)))
-
-        return ClientPagedResultList(self.provider,
-                                     matches if matches else [])
-
-
-class AzureImageService(BaseImageService):
-    def __init__(self, provider):
-        super(AzureImageService, self).__init__(provider)
-
-    def get(self, image_id):
-        """
-        Returns an Image given its id
-        """
-        try:
-            image = self.provider.azure_client.get_image(image_id)
-            return AzureMachineImage(self.provider, image)
-        except (CloudError, InvalidValueException) as cloud_error:
-            # Azure raises the cloud error if the resource not available
-            log.exception(cloud_error)
-            return None
-
     def find(self, **kwargs):
         obj_list = self
         filters = ['label']
@@ -795,18 +804,6 @@ class AzureImageService(BaseImageService):
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
-    def list(self, filter_by_owner=True, limit=None, marker=None):
-        """
-        List all images.
-        """
-        azure_images = self.provider.azure_client.list_images()
-        azure_gallery_refs = self.provider.azure_client.list_gallery_refs() \
-            if not filter_by_owner else []
-        cb_images = [AzureMachineImage(self.provider, img)
-                     for img in azure_images + azure_gallery_refs]
-        return ClientPagedResultList(self.provider, cb_images,
-                                     limit=limit, marker=marker)
-
 
 class AzureVMTypeService(BaseVMTypeService):
 
@@ -828,6 +825,29 @@ class AzureVMTypeService(BaseVMTypeService):
                                      limit=limit, marker=marker)
 
 
+class AzureRegionService(BaseRegionService):
+    def __init__(self, provider):
+        super(AzureRegionService, self).__init__(provider)
+
+    def get(self, region_id):
+        region = None
+        for azureRegion in self.provider.azure_client.list_locations():
+            if azureRegion.name == region_id:
+                region = AzureRegion(self.provider, azureRegion)
+                break
+        return region
+
+    def list(self, limit=None, marker=None):
+        regions = [AzureRegion(self.provider, region)
+                   for region in self.provider.azure_client.list_locations()]
+        return ClientPagedResultList(self.provider, regions,
+                                     limit=limit, marker=marker)
+
+    @property
+    def current(self):
+        return self.get(self.provider.region_name)
+
+
 class AzureNetworkingService(BaseNetworkingService):
     def __init__(self, provider):
         super(AzureNetworkingService, self).__init__(provider)
@@ -908,29 +928,6 @@ class AzureNetworkService(BaseNetworkService):
         self.provider.azure_client.delete_network(network_id)
 
 
-class AzureRegionService(BaseRegionService):
-    def __init__(self, provider):
-        super(AzureRegionService, self).__init__(provider)
-
-    def get(self, region_id):
-        region = None
-        for azureRegion in self.provider.azure_client.list_locations():
-            if azureRegion.name == region_id:
-                region = AzureRegion(self.provider, azureRegion)
-                break
-        return region
-
-    def list(self, limit=None, marker=None):
-        regions = [AzureRegion(self.provider, region)
-                   for region in self.provider.azure_client.list_locations()]
-        return ClientPagedResultList(self.provider, regions,
-                                     limit=limit, marker=marker)
-
-    @property
-    def current(self):
-        return self.get(self.provider.region_name)
-
-
 class AzureSubnetService(BaseSubnetService):
 
     def __init__(self, provider):
@@ -993,12 +990,12 @@ class AzureSubnetService(BaseSubnetService):
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
 
-    def create(self, label, network, cidr_block, **kwargs):
+    def create(self, label, network, cidr_block, zone):
         """
         Create subnet
         """
-        # Although Subnet doesn't support labels, we use the parent Network's
-        # tags to track the subnet's labels
+        # Although Subnet doesn't support tags in Azure, we use the parent
+        # Network's tags to track its subnets' labels
         AzureSubnet.assert_valid_resource_label(label)
         subnet_name = AzureSubnet._generate_name_from_label(label, "cb-sn")
 
@@ -1018,28 +1015,6 @@ class AzureSubnetService(BaseSubnetService):
         subnet.label = label
         return subnet
 
-    def get_or_create_default(self, zone):
-        default_cidr = '10.0.1.0/24'
-
-        # No provider-default Subnet exists, look for a library-default one
-        matches = self.find(label=AzureSubnet.CB_DEFAULT_SUBNET_LABEL)
-        if matches:
-            return matches[0]
-
-        # No provider-default Subnet exists, try to create it (net + subnets)
-        networks = self.provider.networking.networks.find(
-            label=AzureNetwork.CB_DEFAULT_NETWORK_LABEL)
-
-        if networks:
-            network = networks[0]
-        else:
-            network = self.provider.networking.networks.create(
-                AzureNetwork.CB_DEFAULT_NETWORK_LABEL, '10.0.0.0/16')
-
-        subnet = self.create(AzureSubnet.CB_DEFAULT_SUBNET_LABEL, network,
-                             default_cidr)
-        return subnet
-
     def delete(self, subnet):
         subnet_id = subnet.id if isinstance(subnet, Subnet) else subnet
         self.provider.azure_client.delete_subnet(subnet_id)

+ 21 - 1
cloudbridge/cloud/providers/openstack/resources.py

@@ -1203,6 +1203,7 @@ class OpenStackKeyPair(BaseKeyPair):
 
 
 class OpenStackVMFirewall(BaseVMFirewall):
+    _network_id_tag = "CB-AUTO-associated-network-id: "
 
     def __init__(self, provider, vm_firewall):
         super(OpenStackVMFirewall, self).__init__(provider, vm_firewall)
@@ -1215,7 +1216,26 @@ class OpenStackVMFirewall(BaseVMFirewall):
 
         :return: Always return ``None``.
         """
-        return None
+        # Best way would be to use regex, but using this hacky way to avoid
+        # importing the re package
+        net_id = self._description\
+                     .split(" [{}".format(self._network_id_tag))[-1]\
+                     .split(']')[0]
+        return net_id
+
+    @property
+    def _description(self):
+        return self._vm_firewall.description or ""
+
+    @property
+    def description(self):
+        desc_fragment = " [{}{}]".format(self._network_id_tag,
+                                         self.network_id)
+        desc = self._description
+        if desc:
+            return desc.replace(desc_fragment, "")
+        else:
+            return None
 
     @property
     def name(self):

+ 109 - 105
cloudbridge/cloud/providers/openstack/services.py

@@ -37,6 +37,7 @@ from cloudbridge.cloud.interfaces.exceptions \
     import DuplicateResourceException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
+from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import Snapshot
 from cloudbridge.cloud.interfaces.resources import Subnet
@@ -216,6 +217,11 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
         log.debug("Creating OpenStack VM Firewall with the params: "
                   "[label: %s network id: %s description: %s]", label,
                   network, description)
+        net = network.id if isinstance(network, Network) else network
+        if not description:
+            description = ""
+        description += "[{}{}]".format(OpenStackVMFirewall._network_id_tag,
+                                       net)
         sg = self.provider.os_conn.network.create_security_group(
             name=label, description=description or label)
         if sg:
@@ -244,61 +250,6 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
         return True
 
 
-class OpenStackImageService(BaseImageService):
-
-    def __init__(self, provider):
-        super(OpenStackImageService, self).__init__(provider)
-
-    def get(self, image_id):
-        """
-        Returns an Image given its id
-        """
-        log.debug("Getting OpenStack Image with the id: %s", image_id)
-        try:
-            return OpenStackMachineImage(
-                self.provider, self.provider.os_conn.image.get_image(image_id))
-        except (NotFoundException, ResourceNotFound):
-            log.debug("Image %s not found", image_id)
-            return None
-
-    def find(self, **kwargs):
-        filters = ['name', 'label']
-        obj_list = self
-        return cb_helpers.generic_find(filters, kwargs, obj_list)
-
-    def list(self, filter_by_owner=True, limit=None, marker=None):
-        """
-        List all images.
-        """
-        project_id = None
-        if filter_by_owner:
-            project_id = self.provider.os_conn.session.get_project_id()
-        os_images = self.provider.os_conn.image.images(
-            owner=project_id,
-            limit=oshelpers.os_result_limit(self.provider, limit),
-            marker=marker)
-
-        cb_images = [
-            OpenStackMachineImage(self.provider, img)
-            for img in os_images]
-        return oshelpers.to_server_paged_list(self.provider, cb_images, limit)
-
-
-class OpenStackVMTypeService(BaseVMTypeService):
-
-    def __init__(self, provider):
-        super(OpenStackVMTypeService, self).__init__(provider)
-
-    def list(self, limit=None, marker=None):
-        cb_itypes = [
-            OpenStackVMType(self.provider, obj)
-            for obj in self.provider.nova.flavors.list(
-                limit=oshelpers.os_result_limit(self.provider, limit),
-                marker=marker)]
-
-        return oshelpers.to_server_paged_list(self.provider, cb_itypes, limit)
-
-
 class OpenStackStorageService(BaseStorageService):
 
     def __init__(self, provider):
@@ -519,44 +470,6 @@ class OpenStackBucketService(BaseBucketService):
             return self.get(name)
 
 
-class OpenStackRegionService(BaseRegionService):
-
-    def __init__(self, provider):
-        super(OpenStackRegionService, self).__init__(provider)
-
-    def get(self, region_id):
-        log.debug("Getting OpenStack Region with the id: %s", region_id)
-        region = (r for r in self if r.id == region_id)
-        return next(region, None)
-
-    def list(self, limit=None, marker=None):
-        # pylint:disable=protected-access
-        if self.provider._keystone_version == 3:
-            os_regions = [OpenStackRegion(self.provider, region)
-                          for region in self.provider.keystone.regions.list()]
-            return ClientPagedResultList(self.provider, os_regions,
-                                         limit=limit, marker=marker)
-        else:
-            # Keystone v3 onwards supports directly listing regions
-            # but for v2, this convoluted method is necessary.
-            regions = (
-                endpoint.get('region') or endpoint.get('region_id')
-                for svc in self.provider.keystone.service_catalog.get_data()
-                for endpoint in svc.get('endpoints', [])
-            )
-            regions = set(region for region in regions if region)
-            os_regions = [OpenStackRegion(self.provider, region)
-                          for region in regions]
-
-            return ClientPagedResultList(self.provider, os_regions,
-                                         limit=limit, marker=marker)
-
-    @property
-    def current(self):
-        nova_region = self.provider.nova.client.region_name
-        return self.get(nova_region) if nova_region else None
-
-
 class OpenStackComputeService(BaseComputeService):
 
     def __init__(self, provider):
@@ -583,6 +496,46 @@ class OpenStackComputeService(BaseComputeService):
         return self._region_svc
 
 
+class OpenStackImageService(BaseImageService):
+
+    def __init__(self, provider):
+        super(OpenStackImageService, self).__init__(provider)
+
+    def get(self, image_id):
+        """
+        Returns an Image given its id
+        """
+        log.debug("Getting OpenStack Image with the id: %s", image_id)
+        try:
+            return OpenStackMachineImage(
+                self.provider, self.provider.os_conn.image.get_image(image_id))
+        except (NotFoundException, ResourceNotFound):
+            log.debug("Image %s not found", image_id)
+            return None
+
+    def find(self, **kwargs):
+        filters = ['label']
+        obj_list = self
+        return cb_helpers.generic_find(filters, kwargs, obj_list)
+
+    def list(self, filter_by_owner=True, limit=None, marker=None):
+        """
+        List all images.
+        """
+        project_id = None
+        if filter_by_owner:
+            project_id = self.provider.os_conn.session.get_project_id()
+        os_images = self.provider.os_conn.image.images(
+            owner=project_id,
+            limit=oshelpers.os_result_limit(self.provider, limit),
+            marker=marker)
+
+        cb_images = [
+            OpenStackMachineImage(self.provider, img)
+            for img in os_images]
+        return oshelpers.to_server_paged_list(self.provider, cb_images, limit)
+
+
 class OpenStackInstanceService(BaseInstanceService):
 
     def __init__(self, provider):
@@ -590,8 +543,7 @@ class OpenStackInstanceService(BaseInstanceService):
 
     def create(self, label, image, vm_type, subnet, zone,
                key_pair=None, vm_firewalls=None, user_data=None,
-               launch_config=None,
-               **kwargs):
+               launch_config=None, **kwargs):
         """Create a new virtual machine instance."""
         OpenStackInstance.assert_valid_resource_label(label)
 
@@ -765,6 +717,59 @@ class OpenStackInstanceService(BaseInstanceService):
             return None
 
 
+class OpenStackVMTypeService(BaseVMTypeService):
+
+    def __init__(self, provider):
+        super(OpenStackVMTypeService, self).__init__(provider)
+
+    def list(self, limit=None, marker=None):
+        cb_itypes = [
+            OpenStackVMType(self.provider, obj)
+            for obj in self.provider.nova.flavors.list(
+                limit=oshelpers.os_result_limit(self.provider, limit),
+                marker=marker)]
+
+        return oshelpers.to_server_paged_list(self.provider, cb_itypes, limit)
+
+
+class OpenStackRegionService(BaseRegionService):
+
+    def __init__(self, provider):
+        super(OpenStackRegionService, self).__init__(provider)
+
+    def get(self, region_id):
+        log.debug("Getting OpenStack Region with the id: %s", region_id)
+        region = (r for r in self if r.id == region_id)
+        return next(region, None)
+
+    def list(self, limit=None, marker=None):
+        # pylint:disable=protected-access
+        if self.provider._keystone_version == 3:
+            os_regions = [OpenStackRegion(self.provider, region)
+                          for region in self.provider.keystone.regions.list()]
+            return ClientPagedResultList(self.provider, os_regions,
+                                         limit=limit, marker=marker)
+        else:
+            # Keystone v3 onwards supports directly listing regions
+            # but for v2, this convoluted method is necessary.
+            regions = (
+                endpoint.get('region') or endpoint.get('region_id')
+                for svc in self.provider.keystone.service_catalog.get_data()
+                for endpoint in svc.get('endpoints', [])
+            )
+            regions = set(region for region in regions if region)
+            os_regions = [OpenStackRegion(self.provider, region)
+                          for region in regions]
+
+            return ClientPagedResultList(self.provider, os_regions,
+                                         limit=limit, marker=marker)
+
+    @property
+    def current(self):
+        nova_region = self.provider.nova.client.region_name
+        return self.get(nova_region) if nova_region else None
+
+
 class OpenStackNetworkingService(BaseNetworkingService):
 
     def __init__(self, provider):
@@ -875,18 +880,17 @@ class OpenStackSubnetService(BaseSubnetService):
             sn = self.find(label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL)
             if sn:
                 return sn[0]
-            # No default; create one
-            net = self.provider.networking.networks.create(
-                label=OpenStackNetwork.CB_DEFAULT_NETWORK_LABEL,
-                cidr_block='10.0.0.0/16')
-            sn = net.create_subnet(
+            # No default subnet look for default network, then create subnet
+            net = self.provider.networking.networks.get_or_create_default()
+            sn = self.provider.networking.subnets.create(
                 label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL,
-                cidr_block='10.0.0.0/24')
-            router = self.provider.networking.routers.create(
-                network=net, label=OpenStackRouter.CB_DEFAULT_ROUTER_LABEL)
+                cidr_block='10.0.0.0/24',
+                network=net)
+            router = self.provider.networking.routers.get_or_create_default(
+                net)
             router.attach_subnet(sn)
-            gteway = net.gateways.get_or_create_inet_gateway()
-            router.attach_gateway(gteway)
+            gateway = net.gateways.get_or_create_inet_gateway()
+            router.attach_gateway(gateway)
             return sn
         except NeutronClientException:
             return None

+ 13 - 15
docs/getting_started.rst

@@ -197,6 +197,10 @@ 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 explicitly
 listed in order to help map each resource with the service that handles it.
+Note that labeled resources allow to find by label, while unlabeled
+resources find by name or their special properties (eg: public_ip for
+floating IPs). For more detailed information on the types of resources and
+their provider mappings, see :doc:`topics/resource_types_and_mappings`.
 
 .. code-block:: python
 
@@ -205,50 +209,44 @@ listed in order to help map each resource with the service that handles it.
     kp_list = provider.security.key_pairs.find(name='cloudbridge-intro')
     kp = kp_list[0]
 
+    # Floating IPs
+    fip = gateway.floating_ips.get('FloatingIP ID')
+    # Find using public IP address
+    fip_list = gateway.floating_ips.find(public_ip='IP address')
+    # Find using name (the behavior of the `name` property can be 
+    # cloud-dependent). More details can be found `here <topics/resource_types_and_mapping.html>`
+    fip_list = net.gateways.floating_ips.find(name='my-fip')
+    fip = fip_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
     gateway = net.gateways.get_or_create_inet_gateway()
 
-    # Floating IPs
-    fip = gateway.floating_ips.get('FloatingIP ID')
-    # Find using public IP address
-    fip_list = gateway.floating_ips.find(public_ip='IP address')
-    # 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]
 

+ 169 - 0
docs/topics/aws_mapping.rst

@@ -0,0 +1,169 @@
+AWS Dashboard
+-------------
+AWS has a particular dashboard as resources are found within different
+services. The following table lists the dashboard location of each resource,
+and the below screenshot shows how the switch between the various services.
+
++------------------------+-----+
+| Instance               | EC2 |
++------------------------+-----+
+| MachineImage (Private) | EC2 |
++------------------------+-----+
+| Volume                 | EC2 |
++------------------------+-----+
+| Snapshot               | EC2 |
++------------------------+-----+
+| VMFirewall             | EC2 |
++------------------------+-----+
+| FloatingIP             | EC2 |
++------------------------+-----+
+| KeyPair                | EC2 |
++------------------------+-----+
+| VMFirewallRule         | EC2 |
++------------------------+-----+
+| Network                | VPC |
++------------------------+-----+
+| Subnet                 | VPC |
++------------------------+-----+
+| Router                 | VPC |
++------------------------+-----+
+| InternetGateway        | VPC |
++------------------------+-----+
+| Bucket                 | S2  |
++------------------------+-----+
+| BucketObject           | S2  |
++------------------------+-----+
+
+.. figure:: captures/aws-services-dash.png
+   :scale: 50 %
+   :alt: EC2, VPC, and S3
+
+   Resources in AWS are separated into three dashboards depending on the
+   type of service handling the resources
+
+
+AWS - Labeled Resources
+-----------------------
++------------------------+-------------------+----------------+----------------+----------+
+| Labeled Resource       | AWS Resource Type | CB ID          | CB Name        | CB Label |
++------------------------+-------------------+----------------+----------------+----------+
+| AWSInstance            | Instance          | Instance ID    | Instance ID    | tag:Name |
++------------------------+-------------------+----------------+----------------+----------+
+| AWSMachineImage        | AMI               | AMI ID         | AMI Name       | tag:Name |
++------------------------+-------------------+----------------+----------------+----------+
+| AWSNetwork             | VPC               | VPC ID         | VPC ID         | tag:Name |
++------------------------+-------------------+----------------+----------------+----------+
+| AWSSubnet              | Subnet            | Subnet ID      | Subnet ID      | tag:Name |
++------------------------+-------------------+----------------+----------------+----------+
+| AWSRouter              | Route Table       | Route Table ID | Route Table ID | tag:Name |
++------------------------+-------------------+----------------+----------------+----------+
+| AWSVolume              | Volume            | Volume ID      | Volume ID      | tag:Name |
++------------------------+-------------------+----------------+----------------+----------+
+| AWSSnapshot            | Snapshot          | Snapshot ID    | Snapshot ID    | tag:Name |
++------------------------+-------------------+----------------+----------------+----------+
+| AWSVMFirewall          | Security Group    | Group ID       | Group Name     | tag:Name |
++------------------------+-------------------+----------------+----------------+----------+
+
+The resources listed above are labeled, they thus have both the `name` and
+`label` properties in CloudBridge. These resources require a mandatory `label`
+parameter at creation. For all labeled resources, the `label` property in AWS
+maps to the tag with `key:Name`. However, unlike in Azure where all resources
+have names, only some AWS resources have an unchangeable name by which to
+identify them. Thus, for most AWS resources, the `name` property maps to the
+ID, in order to preserve the concept of names being a unique identifier,
+even if they are not easily readable in this context. For resources that do
+support naming in AWS, the `name` will be generated from the `label` given at
+creation, consisting of up to 55 characters from the label, followed by a UUID.
+The label property can subsequently be changed, but the name property will
+be set at creation and remain unchanged. Finally, labeled resources support
+a `label` parameter for the `find` method in their corresponding services.
+The below screenshots will help map these properties to AWS objects in the
+web portal.
+
+.. figure:: captures/aws-instance-dash.png
+   :scale: 50 %
+   :alt: name, ID, and label properties for AWS EC2 Instances
+
+   The CloudBridge `name` and `ID` properties map to the unchangeable
+   resource ID in AWS when the resource does not allow for an unchangeable
+   name. The `label` property maps to the tag with key 'Name' for all
+   resources in AWS. By default, this label will appear in the first
+   column.
+
+.. figure:: captures/az-ami-dash.png
+   :scale: 50 %
+   :alt: name, ID, and label properties for AWS EC2 AMIs
+
+   When an AWS resource allows for an unchangeable name, the CloudBridge
+   `ID` property maps to the Resource ID, while the `Name` property maps to
+   the Resource Name. The `label` property maps to the tag with key 'Name'
+   for all resources in AWS. By default, this label will appear in the first
+   column.
+
+
+AWS - Unlabeled Resources
+---------------------------
++-----------------------+--------------------+-------+---------+----------+
+| Unlabeled Resource    | AWS Resource Type  | CB ID | CB Name | CB Label |
++-----------------------+--------------------+-------+---------+----------+
+| AWSKeyPair            | Key Pair           | Name  | Name    | -        |
++-----------------------+--------------------+-------+---------+----------+
+| AWSBucket             | Bucket             | Name  | Name    | -        |
++-----------------------+--------------------+-------+---------+----------+
+| AWSBucketObject       | Bucket Object      | Key   | Key     | -        |
++-----------------------+--------------------+-------+---------+----------+
+
+The resources listed above are unlabeled. They thus only have the `name`
+property in CloudBridge. These resources require a mandatory `name`
+parameter at creation, which will directly map to the unchangeable `name`
+property. Additionally, for these resources, the `ID` property also maps to
+the `name` in AWS, as these resources don't have an `ID` in the
+traditional sense and can be located by name. Finally, unlabeled resources
+support a `name` parameter for the `find` method in their corresponding
+services.
+
+.. figure:: captures/aws-bucket.png
+   :scale: 50 %
+   :alt: list of buckets on AWS dashboard
+
+   Buckets can be found in the Amazon S3 portal. BucketObjects are contained
+   within each Bucket.
+
+
+AWS - Special Unlabeled Resources
+-----------------------------------
++--------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+| Unlabeled Resource | AWS Resource Type      | CB ID | CB Name                                                                | CB Label |
++--------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+| AWSFloatingIP      | Elastic IP             | ID    | [public_ip]                                                            | -        |
++--------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+| AWSInternetGateway | Internet Gateway       | ID    | tag:Name                                                               | -        |
++--------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+| AWSVMFirewallRule  | Network Security Rules | ID    | Generated: [direction]-[protocol]-[from_port]-[to_port]-[cidr]-[fw_id] | -        |
++--------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+
+While these resources are similarly unlabeled, they do not follow the same
+general rules as the ones listed above. Firstly, they differ by the fact
+that they take neither a `name` nor a `label` parameter at creation.
+Moreover, each of them has other special properties.
+
+The FloatingIP resource has a traditional resource ID, but instead of a
+traditional name, its `name` property maps to its Public IP.
+Moreover, the corresponding `find` method for Floating IPs can thus help
+find a resource by `Public IP Address`.
+
+In terms of the gateway, given that gateways are not their own objects in
+other providers, we do not treat them like labeled resources in AWS although
+they could support labels. Thus, the internet gateway create method does not
+take a name parameter, and the `name` property is set automatically to a
+default value. Note that since this value is stored in the tag with key Name,
+the AWS dashboard does allow for its modification, although that is not
+encouraged as the default name is expected for the
+`get_or_create_inet_gateway` method.
+
+Finally, Firewall Rules in AWS differ from traditional unlabeled resources
+by the fact that they do not take a `name` parameter at creation, and the
+`name` property is automatically generated from the rule's properties, as
+shown above. These rules can be found within each Firewall (i.e. Security
+Group) in the AWS EC2 portal, and will not have any name in the AWS dashboard
+

+ 223 - 0
docs/topics/azure_mapping.rst

@@ -0,0 +1,223 @@
+Azure - Labeled Resources
+-------------------------
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+| Labeled CloudBridge Resource          | Azure Resource Type    | CB ID | CB Name                | CB Label                           |
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+| AzureInstance                         | Virtual Machine        | ID    | Name                   | tag:Label                          |
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+| AzureMachineImage (Private)           | Image                  | ID    | Name                   | tag:Label                          |
+| AzureMachineImage (Marketplace Image) | VirtualMachineImage    | ID    | URN                    | URN                                |
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+| AzureNetwork                          | Virtual Network        | ID    | Name                   | tag:Label                          |
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+| AzureSubnet                           | Subnet                 | ID    | NetworkName/SubnetName | Network:tag:SubnetLabel_SubnetName |
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+| AzureRouter                           | Route Table            | ID    | Name                   | tag:Label                          |
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+| AzureVolume                           | Disk                   | ID    | Name                   | tag:Label                          |
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+| AzureSnapshot                         | Snapshot               | ID    | Name                   | tag:Label                          |
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+| AzureVMFirewall                       | Network security group | ID    | Name                   | tag:Label                          |
++---------------------------------------+------------------------+-------+------------------------+------------------------------------+
+
+The resources listed above are labeled, they thus have both the `name` and
+`label` properties in CloudBridge. These resources require a mandatory `label`
+parameter at creation. The `label` will then be used to create the `name`,
+which will consist of up to 55 characters from the label, followed by a UUID.
+The label property can subsequently be changed, but the name property will
+remain unchanged, as it is part of the ID. Finally, labeled resources support
+a `label` parameter for the `find` method in their corresponding services.
+The below screenshots will help map these properties to Azure objects in the
+web portal.
+Additionally, although Azure Security Groups are not associated with a
+specific network, such an association is done in CloudBridge, due to its
+necessity in AWS. As such, the VMFirewall creation method requires a
+`network` parameter and the association is accomplished in OpenStack through
+a tag with the key `network_id`.
+
+.. figure:: captures/az-label-dash.png
+   :scale: 50 %
+   :alt: name and label properties in Azure portal
+
+   The CloudBridge `name` property always maps to the unchangeable resource
+   name in Azure. The `label` property maps to the tag with key 'Label' for
+   most resources in Azure. By default, this label will appear in the tags
+   column, but can also be made into its own column, using the feature
+   pointed out in the screenshot above.
+
+.. figure:: captures/az-net-id.png
+   :scale: 50 %
+   :alt: network id in Azure portal
+
+   The CloudBridge `ID` property most often maps to the Resource ID in Azure,
+   which can be found under the properties tab within a resource. The above
+   screenshot shows where to find a resource's ID in Azure's web portal.
+
+.. figure:: captures/az-net-label.png
+   :scale: 50 %
+   :alt: network label in Azure portal
+
+   The CloudBridge `label` property most often maps to the tag with key
+   'Label' in Azure, which can be found under the tags tab within a resource.
+   The above screenshot shows where to find a resource's label in Azure's
+   web portal.
+
+Two labeled resources are exceptions to the general trends presented above,
+namely public images (i.e. Azure Marketplace Images) and subnets.
+
+These public images can be found in the Azure Marketplace, and cannot be
+found on a user's dashboard. A Marketplace Image can be passed either by URN,
+or by public ID, and does not need to be linked to a user. While all
+Marketplace images will not be be listed by the find or list methods at the
+moment, a pre-set list of popular images is built into CloudBridge for that
+purpose. However, one can choose to list all Marketplace Images using the
+`list_marketplace_images` function in the azure client. Specifically,
+this can be done as follows:
+
+.. code-block:: python
+
+    # List all images
+    # Note that in September 2018, around 10 minutes of wall time were required
+    # to fetch the entire list
+    provider.azure_client.list_marketplace_images()
+    # List all images published by Canonical
+    provider.azure_client.list_marketplace_images(publisher='Canonical')
+    # List all Ubuntu images
+    provider.azure_client.list_marketplace_images(publisher='Canonical',
+                                                  offer='UbuntuServer')
+    # List all Ubuntu 16.04 images
+    provider.azure_client.list_marketplace_images(publisher='Canonical',
+                                                  offer='UbuntuServer',
+                                                  sku='16.04.0-LTS')
+    # The ID of the listed object can then be used to retrieve an instance
+    img = provider.compute.images.get
+            ('/Subscriptions/{subscriptionID}/Providers/Microsoft.Compute/\
+            Locations/{regionName}/Publishers/Canonical/ArtifactTypes/VMImage\
+            /Offers/UbuntuServer/Skus/16.04.0-LTS/Versions/16.04.201808140')
+    # The URN can also be used instead if it is already known
+    # When the latest version is desired, it can be retrieved with the
+    # keyword 'latest' in the URN without specifying a version
+    img = provider.compute.images.get(
+          'Canonical:UbuntuServer:16.04.0-LTS:latest')
+
+
+Given that these resources are not owned by the user, they can only be
+referenced and all setters will silently pass. CloudBridge properties `name`
+and `label` will map to the URN, while the `ID` will map to the public `ID`.
+It is also important to note that some of these resources are paid and
+required a plan to use, while others are free but likewise require accepting
+certain terms before being used. These plans and terms are passed and
+accepted silently by CloudBridge in order to keep the code cloud-independent.
+We therefore encourage using the `marketplace website<https://azuremarketplace.microsoft.com/en-us>`_
+to view image and plan details before using them in CloudBridge.
+
+Additionally, Subnets are a particular resource in Azure because they are
+not simply found in the Resource Group like most resources, but are rather
+nested within a network. Moreover, Subnets do not support tags in Azure.
+However, they remain a labeled resource in CloudBridge, which was
+accomplished by creating Network tags holding Subnet labels in Azure. The
+below screenshots will show how to find Subnets and their labels in the
+Azure web portal.
+
+.. figure:: captures/az-subnet-name.png
+   :scale: 50 %
+   :alt: subnet name in Azure portal
+
+   The CloudBridge `name` property for Subnets corresponds to the
+   unchangeable Resource Name in Azure. However, unlike other resources
+   where the Azure Name maps directly to the `name` property alone, a Subnet's
+   `name` property returns the Network's name and the Subnet's name,
+   separated by a slash, thus having the format [networkName]/[subnetName].
+   Subnets are additionally not found in the default resource list, but are
+   rather nested within a Network, in the Subnets tab as shown above.
+
+.. figure:: captures/az-subnet-label.png
+   :scale: 50 %
+   :alt: subnet label in Azure portal
+
+   The CloudBridge `label` property most often maps to the tag with key
+   'Label' in Azure, which can be found under the tags tab within a resource.
+   However, given that Subnets can't hold tags themselves, we set their tags
+   in the Network with which they are associated. The tag name 'Label' thus
+   corresponds to the Network's label, while each contained Subnet will have
+   a corresponding tag with the name 'SubnetLabel_[subnetName]'.
+
+
+Azure - Unlabeled Resources
+---------------------------
++--------------------+----------------------------------------+-------+---------+----------+
+| Unlabeled Resource | Azure Resource Type                    | CB ID | CB Name | CB Label |
++--------------------+----------------------------------------+-------+---------+----------+
+| AzureKeyPair       | StorageAccount:Table                   | Name  | Name    | -        |
++--------------------+----------------------------------------+-------+---------+----------+
+| AzureBucket        | StorageAccount:BlobContainer           | Name  | Name    | -        |
++--------------------+----------------------------------------+-------+---------+----------+
+| AzureBucketObject  | StorageAccount:BlobContainer:BlockBlob | Name  | Name    | -        |
++--------------------+----------------------------------------+-------+---------+----------+
+
+The resources listed above are unlabeled. They thus only have the `name`
+property in CloudBridge. These resources require a mandatory `name`
+parameter at creation, which will directly map to the unchangeable `name`
+property. Additionally, for these resources, the `ID` property also maps to
+the `name` in Azure, as these resources don't have an `ID` in the
+traditional sense and can be located simply by name. Finally, unlabeled
+resources support a `name` parameter for the `find` method in their
+corresponding services.
+
+.. figure:: captures/az-storacc.png
+   :scale: 50 %
+   :alt: storage account in Azure portal
+
+   Bucket and Key Pair objects are different than other resources in Azure,
+   as they are not resources simply residing in a resource group, but are
+   rather found in a storage account. As a result of this difference, these
+   resources do not support labels, and cannot be seen on the default
+   dashboard. In order to find these resources in the Azure web portal, one
+   must head to the storage account containing them, and look in the `Blobs`
+   and `Tables` services respectively for `Buckets` and `KeyPairs`.
+
+
+Azure - Special Unlabeled Resources
+-----------------------------------
++-------------------------+------------------------+--------------------+--------------------+----------+
+| Unlabeled Resource      | Azure Resource Type    | CB ID              | CB Name            | CB Label |
++-------------------------+------------------------+--------------------+--------------------+----------+
+| AzureFloatingIP         | Public IP Address      | ID                 | [public_ip]        | -        |
++-------------------------+------------------------+--------------------+--------------------+----------+
+| AzureInternetGateway    | None                   | cb-gateway-wrapper | cb-gateway-wrapper | -        |
++-------------------------+------------------------+--------------------+--------------------+----------+
+| AzureVMFirewallRule     | Network Security Rules | ID                 | name               | -        |
++-------------------------+------------------------+--------------------+--------------------+----------+
+
+While these resources are similarly unlabeled, they do not follow the same
+general rules as the ones listed above. Firstly, they differ by the fact
+that they take neither a `name` nor a `label` parameter at creation.
+Moreover, each of them has other special properties.
+
+The FloatingIP resource has a traditional resource ID, but instead of a
+traditional name, its `name` property maps to its Public IP. Thus, the name
+seen in the Azure web portal will not map to the CloudBridge name, but will
+rather be auto-generated, while the Azure `IP Address` will map to CloudBridge
+name. Moreover, the corresponding `find` method for Floating IPs can thus help
+find a resource by `Public IP Address`, and the get method also accepts a
+'Public IP' instead of an 'ID'.
+
+In terms of the gateway, one of the major discrepancies in Azure is the
+non-existence of an InternetGateway. In fact, Azure resources are exposed
+with no need for an Internet gateway. However, in order to keep resources
+consistent across providers, the CloudBridge Gateway resource exists
+regardless of provider. For Azure, the gateway object created through
+CloudBridge will not appear on the dashboard, but will rather be a cached
+CloudBridge-level wrapper object.
+For a succinct comparison between AWS Gateways and Azure, see `this answer
+<https://social.msdn.microsoft.com/Forums/en-US/
+814ccee0-9fbb-4c04-8135-49d0aaea5f38/
+equivalent-of-aws-internet-gateways-in-azure?
+forum=WAVirtualMachinesVirtualNetwork>`_.
+
+Finally, Firewall Rules in Azure differ from traditional unlabeled
+resources by the fact that they do not take a `name` parameter at creation.
+These rules can be found within each Firewall (i.e. Security Group) in the
+Azure web portal, and will have an automatically generated `name` of the form
+'cb-rule-[int]'.

BIN
docs/topics/captures/aws-ami-dash.png


BIN
docs/topics/captures/aws-bucket.png


BIN
docs/topics/captures/aws-instance-dash.png


BIN
docs/topics/captures/aws-services-dash.png


BIN
docs/topics/captures/az-storacc.png


BIN
docs/topics/captures/az-subnet-label.png


BIN
docs/topics/captures/az-subnet-name.png


BIN
docs/topics/captures/os-instance-dash.png


BIN
docs/topics/captures/os-kp-dash.png


+ 0 - 121
docs/topics/dashboard.rst

@@ -1,121 +0,0 @@
-Dashboard Mapping
-=================
-
-Cross-Platform Concepts
------------------------
-
-Given cloudbridge's goal to work uniformly across cloud providers, some
-compromises were necessary in order to bridge the many differences between
-providers' resources and features. Notably, in order to create a robust and
-conceptually consistent cross-cloud library, resources were given three main
-properties: ID, name, and label.
-The `ID` corresponds to a unique identifier that can be reliably used to
-reference a resource. Users can safely use an ID knowing that it will always
-point to the same resource.
-The `name` property corresponds to an unchangeable and unique designation for
-a particular resource. This property is meant to be, in some ways, a more
-human-readable identifier. However, when no conceptually comparable property
-exists for a given resource in a particular provider, the ID is returned
-instead, as is the case for OpenStack resources. When the name can be
-determined by a user at resource creation, either the name parameter will be
-used for resources that support it, or the label will be used, when provided
-as a prefix, with an appended uuid to ensure that the name remains unique.
-The `label` property, conversely, is a changeable value that does not need
-to be unique. Unlike the name property, it is not used to identify a
-particular resource, but rather label a resource for easier distinction. It
-is however important to note that not all resources support labels. When
-supported, labels given at creation will also be used as a prefix to the name.
-
-Properties per Resource per Provider
-------------------------------------
-The sections below will present a summary table detailing the cloudbridge
-properties implemented for each resource, and their corresponding value in
-the provider's dashboard.
-
-Azure
------
-+-----------------------------------+-------+---------------+---------------+
-| CloudServiceType                 	| CB_ID	| CB_Name      	| CB_Label  	|
-+===================================+=======+===============+===============+
-| Instance                         	| ID   	| Name       	| Tags:Label 	|
-+-----------------------------------+-------+---------------+---------------+
-| MachineImage (Private)           	| ID   	| Name       	| Tags:Label 	|
-| MachineImage (Gallery Reference) 	| URN  	| URN        	| URN        	|
-+-----------------------------------+-------+---------------+---------------+
-| Network                          	| ID   	| Name       	| Tags:Label 	|
-+-----------------------------------+-------+---------------+---------------+
-| Subnet                           	| ID   	| Name       	| Tags:Label 	|
-+-----------------------------------+-------+---------------+---------------+
-| FloatingIP                       	| ID   	| Name       	| Tags:Label 	|
-+-----------------------------------+-------+---------------+---------------+
-| Router                           	| ID   	| Name       	| Tags:Label 	|
-+-----------------------------------+-------+---------------+---------------+
-| InternetGateway                  	| None 	| None       	| -          	|
-+-----------------------------------+-------+---------------+---------------+
-| Volume                           	| ID   	| Name       	| Tags:Label 	|
-+-----------------------------------+-------+---------------+---------------+
-| Snapshot                         	| ID   	| Name       	| Tags:Label 	|
-+-----------------------------------+-------+---------------+---------------+
-| KeyPair                          	| Name 	| Name       	| -          	|
-+-----------------------------------+-------+---------------+---------------+
-| VMFirewall                       	| ID   	| Name       	| Tags:Label 	|
-+-----------------------------------+-------+---------------+---------------+
-| VMFirewallRule                   	| ID   	| Name       	| -          	|
-+-----------------------------------+-------+---------------+---------------+
-| Bucket                           	| Name 	| Name       	| -          	|
-+-----------------------------------+-------+---------------+---------------+
-| BucketObject                     	| Name 	| Name       	| -          	|
-+-----------------------------------+-------+---------------+---------------+
-
-One of the major discrepancies in Azure is the non-existence of an Internet
-Gateway. In fact, Azure resources are automatically exposed to the internet,
-and thus an internet gateway object is not necessary for this purpose. Thus,
-a gateway object created through cloudbridge in Azure will not appear on the
-dashboard, as a cloudbridge-level wrapper object is returned when trying to
-create or get a gateway, but no object corresponds to that concept in Azure.
-For a succinct comparison between AWS Gateways and Azure, see:
-https://social.msdn.microsoft.com/Forums/en-US/
-814ccee0-9fbb-4c04-8135-49d0aaea5f38/
-equivalent-of-aws-internet-gateways-in-azure?
-forum=WAVirtualMachinesVirtualNetwork
-
-
-.. figure:: captures/az-label-dash.png
-   :scale: 50 %
-   :alt: name and label properties in Azure portal
-
-   The cloudbridge `name` property always maps to the unchangeable resource
-   name in Azure. The `label` property maps to the tag with key 'Label' in
-   Azure. By default, this label will appear in the tags column, but can also
-   be made into its own column, following the button indicated in the
-   screenshot above.
-
-.. figure:: captures/az-net-id.png
-   :scale: 50 %
-   :alt: network id in Azure portal
-
-   The cloudbridge `ID` property most often maps to the Resource ID in Azure,
-   which can be found under the properties tab within a resource. The above
-   screenshot shows where to find a resource's label in Azure's web portal
-
-.. figure:: captures/az-net-label.png
-   :scale: 50 %
-   :alt: network label in Azure portal
-
-   The cloudbridge `label` property most often maps to the tag with key
-   'Label' in Azure, which can be found under the tags tab within a resource.
-   The above screenshot shows where to find a resource's label in Azure's
-   web portal.
-
-.. figure:: captures/az-storacc.png
-   :scale: 50 %
-   :alt: storage account in Azure portal
-
-   Bucket and Key Pair objects are different than other resources in Azure,
-   as they are not resources residing in a resource group, but rather reside
-   in a storage account. As a result of this difference, these resources do
-   not support labels, and cannot be seen on the default dashboard. In order
-   to find these resources in the Azure web portal, one must head to the
-   storage account containing them, and look in the `Blobs` and `Tables`
-   services respectively for `Buckets` and `KeyPairs`.
-

+ 110 - 0
docs/topics/os_mapping.rst

@@ -0,0 +1,110 @@
+OpenStack - Labeled Resources
+-----------------------
++------------------------+------------------------+-----------+----------------+----------+
+| Labeled Resource       | OS Resource Type       | CB ID     | CB Name        | CB Label |
++------------------------+------------------------+-----------+----------------+----------+
+| OpenStackInstance      | Instance               | ID        | ID             | Name     |
++------------------------+------------------------+-----------+----------------+----------+
+| OpenStackMachineImage  | Image                  | ID        | ID             | Name     |
++------------------------+------------------------+-----------+----------------+----------+
+| OpenStackNetwork       | Network                | ID        | ID             | Name     |
++------------------------+------------------------+-----------+----------------+----------+
+| OpenStackSubnet        | Subnet                 | ID        | ID             | Name     |
++------------------------+------------------------+-----------+----------------+----------+
+| OpenStackRouter        | Router                 | ID        | ID             | Name     |
++------------------------+------------------------+-----------+----------------+----------+
+| OpenStackVolume        | Volume                 | ID        | ID             | Name     |
++------------------------+------------------------+-----------+----------------+----------+
+| OpenStackSnapshot      | Snapshot               | ID        | ID             | Name     |
++------------------------+------------------------+-----------+----------------+----------+
+| OpenStackVMFirewall    | Security Group         | ID        | ID             | Name     |
++------------------------+------------------------+-----------+----------------+----------+
+
+The resources listed above are labeled, they thus have both the `name` and
+`label` properties in CloudBridge. These resources require a mandatory `label`
+parameter at creation. For all labeled resources, the `label` property in OpenStack
+maps to the Name attribute. However, unlike in Azure or AWS, no resource has
+an unchangeable name by which to identify it in our OpenStack implementation.
+The `name` property will therefore map to the ID, preserving its role as an unchangeable 
+identifier even though not easily readable in this context. Finally, labeled resources
+support a `label` parameter for the `find` method in their corresponding services.
+The below screenshots will help map these properties to OpenStack objects in the
+web portal.
+Additionally, although OpenStack Security Groups are not associated with a
+specific network, such an association is done in CloudBridge, due to its
+necessity in AWS. As such, the VMFirewall creation method requires a
+`network` parameter and the association is accomplished in OpenStack through
+the description, by appending the following string to the user-provided description
+(if any) at creation: "[CB-AUTO-associated-network-id: associated_net_id]"
+
+.. figure:: captures/os-instance-dash.png
+   :scale: 50 %
+   :alt: name, ID, and label properties for OS Instances
+
+   The CloudBridge `name` and `ID` properties map to the unchangeable
+   resource ID in OpenStack as resources do not allow for an unchangeable
+   name. The `label` property maps to the 'Name' for all resources in 
+   OpenStack. By default, this label will appear in the first column.
+
+
+OpenStack - Unlabeled Resources
+---------------------------
++-----------------------+------------------------+-------+---------+----------+
+| Unlabeled Resource    | OS Resource Type       | CB ID | CB Name | CB Label |
++-----------------------+------------------------+-------+---------+----------+
+| OpenStackKeyPair      | Key Pair               | Name  | Name    | -        |
++-----------------------+------------------------+-------+---------+----------+
+| OpenStackBucket       | Object Store Container | Name  | Name    | -        |
++-----------------------+------------------------+-------+---------+----------+
+| OpenStackBucketObject | Object                 | Name  | Name    | -        |
++-----------------------+------------------------+-------+---------+----------+
+
+The resources listed above are unlabeled. They thus only have the `name`
+property in CloudBridge. These resources require a mandatory `name`
+parameter at creation, which will directly map to the unchangeable `name`
+property. Additionally, for these resources, the `ID` property also maps to
+the `name` in OpenStack, as these resources don't have an `ID` in the
+traditional sense and can be identified by name. Finally, unlabeled resources
+support a `name` parameter for the `find` method in their corresponding
+services.
+
+.. figure:: captures/os-kp-dash.png
+   :scale: 50 %
+   :alt: KeyPair details on OS dashboard
+
+   KeyPairs and other unlabeled resources in OpenStack have `name` that is
+   unique and unmodifiable. The `ID` will thus map to the `name` property when
+   no other `ID` exists for that OpenStack resource.
+
+
+OpenStack - Special Unlabeled Resources
+-----------------------------------
++--------------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+| Unlabeled Resource       | OS Resource Type       | CB ID | CB Name                                                                | CB Label |
++--------------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+| OpenStackFloatingIP      | Floating IP            | ID    | [public_ip]                                                            | -        |
++--------------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+| OpenStackInternetGateway | Network `public`       | ID    | 'public'                                                               | -        |
++--------------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+| OpenStackVMFirewallRule  | Security Group Rule    | ID    | Generated: [direction]-[protocol]-[from_port]-[to_port]-[cidr]-[fw_id] | -        |
++--------------------------+------------------------+-------+------------------------------------------------------------------------+----------+
+
+While these resources are similarly unlabeled, they do not follow the same
+general rules as the ones listed before. Firstly, they differ by the fact
+that they take neither a `name` nor a `label` parameter at creation.
+Moreover, each of them has other special properties.
+
+The FloatingIP resource has a traditional resource ID, but instead of a
+traditional name, its `name` property maps to its Public IP.
+Moreover, the corresponding `find` method for Floating IPs can thus help
+find a resource by `Public IP Address`.
+
+In terms of the gateway in OpenStack, it maps to the network named 'public.'
+Thus, the internet gateway create method does not take a name parameter, and
+the `name` property will be 'public'.
+
+Finally, Firewall Rules in OpenStack differ from traditional unlabeled resources
+by the fact that they do not take a `name` parameter at creation, and the
+`name` property is automatically generated from the rule's properties, as
+shown above. These rules can be found within each Firewall (i.e. Security
+Group) in the web portal, and will not have any name in the OpenStack dashboard.

+ 65 - 0
docs/topics/resource_types_and_mapping.rst

@@ -0,0 +1,65 @@
+Resource Types and Dashboard Mapping
+====================================
+
+Cross-Platform Concepts
+-----------------------
+
+Given CloudBridge's goal to work uniformly across cloud providers, some
+compromises were necessary in order to bridge the many differences between
+providers' resources and features. Notably, in order to create a robust and
+conceptually consistent cross-cloud library, resources were separated into
+`labeled` and `unlabeled resources,` and were given three main properties:
+`ID`, `name`, and `label`.
+The `ID` corresponds to a unique identifier that can be reliably used to
+reference a resource. Users can safely use an ID knowing that it will always
+point to the same resource. All resources have an `ID` property, thus making
+it the recommended oproperty for reliably identifying a resource.
+The `label` property, conversely, is a modifiable value that does not need
+to be unique. Unlike the name property, it is not used to identify a
+particular resource, but rather label a resource for easier distinction.
+Only labeled resources have the label property, and these resources require
+a `label` parameter be set at creation time.
+The `name` property corresponds to an unchangeable and unique designation for
+a particular resource. This property is meant to be, in some ways, a more
+human-readable identifier. Thus, when no conceptually comparable property
+exists for a given resource in a particular provider, the ID is returned
+instead, as is the case for all OpenStack and some AWS resources. Given the 
+discrepancy between providers, using the `name` property is not advisable 
+for cross-cloud usage of the library. Labeled resources will use the label
+given at creation as a prefix to the set name, when this property is separable
+from the ID as is the case in Azure and some AWS resources. Finally, unlabeled
+resources will always support a `name`, and some unlabeled resources will require
+a name parameter at creation. Below is a list of all resources classified by
+whether they support a `label` property.
+
++-------------------+---------------------+
+| Labeled Resources | Unlabeled Resources | 
++-------------------+---------------------+
+| Instance          | Key Pair            |
++-------------------+---------------------+
+| MachineImage      | Bucket              |
++-------------------+---------------------+
+| Network           | Bucket Object       |
++-------------------+---------------------+
+| Subnet            | FloatingIP          |
++-------------------+---------------------+
+| Router            | Internet Gateway    |
++-------------------+---------------------+
+| Volume            | VMFirewall Rule     |
++-------------------+---------------------+
+| Snapshot          |                     |
++-------------------+---------------------+
+| VMFirewall        |                     |
++-------------------+---------------------+
+
+
+Properties per Resource per Provider
+------------------------------------
+For each provider, we documented the mapping of CloudBridge resources and
+properties to provider objects, as well as some useful dashboard navigation.
+These sections will thus present summary tables delineating the different types of
+CloudBridge resources, as well as present some design decisions made to
+preserve consistency across providers:
+-`Detailed Azure Mappings <azure_mapping.html>`
+-`Detailed AWS Mappings <aws_mapping.html>`
+-`Detailed OpenStack Mappings <os_mapping.html>`

+ 7 - 2
test/test_security_service.py

@@ -66,14 +66,19 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
 
         def create_fw(label):
             return self.provider.security.vm_firewalls.create(
-                label=label, description=label, network_id=net.id)
+                label=label, description=label, network=net.id)
 
         def cleanup_fw(fw):
             if fw:
                 fw.delete()
 
+        def network_id_test(fw):
+            # Checking that the network ID is returned correctly
+            self.assertEqual(fw.network_id, net.id)
+
         sit.check_crud(self, self.provider.security.vm_firewalls,
-                       VMFirewall, "cb-crudfw", create_fw, cleanup_fw)
+                       VMFirewall, "cb-crudfw", create_fw, cleanup_fw,
+                       extra_test_func=network_id_test)
 
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_vm_firewall_properties(self):