Преглед изворни кода

Merge pull request #162 from CloudVE/consistency

Making CloudBridge more robust and consistent
Nuwan Goonasekera пре 7 година
родитељ
комит
2214bb8276

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

@@ -4,6 +4,10 @@ Base implementation for services available through a provider
 import logging
 import logging
 
 
 import cloudbridge.cloud.base.helpers as cb_helpers
 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.resources import Router
 from cloudbridge.cloud.interfaces.services import BucketService
 from cloudbridge.cloud.interfaces.services import BucketService
 from cloudbridge.cloud.interfaces.services import CloudService
 from cloudbridge.cloud.interfaces.services import CloudService
@@ -39,24 +43,42 @@ class BaseCloudService(CloudService):
         return self._provider
         return self._provider
 
 
 
 
-class BaseComputeService(ComputeService, BaseCloudService):
+class BaseSecurityService(SecurityService, BaseCloudService):
 
 
     def __init__(self, provider):
     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):
     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):
     def __init__(self, provider):
-        super(BaseSnapshotService, self).__init__(provider)
+        super(BaseVMFirewallService, self).__init__(provider)
 
 
 
 
 class BaseStorageService(StorageService, BaseCloudService):
 class BaseStorageService(StorageService, BaseCloudService):
@@ -65,56 +87,45 @@ class BaseStorageService(StorageService, BaseCloudService):
         super(BaseStorageService, self).__init__(provider)
         super(BaseStorageService, self).__init__(provider)
 
 
 
 
-class BaseImageService(
-        BasePageableObjectMixin, ImageService, BaseCloudService):
+class BaseVolumeService(
+        BasePageableObjectMixin, VolumeService, BaseCloudService):
 
 
     def __init__(self, provider):
     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):
     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):
     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):
     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):
     def __init__(self, provider):
-        super(BaseVMFirewallService, self).__init__(provider)
+        super(BaseInstanceService, self).__init__(provider)
 
 
 
 
 class BaseVMTypeService(
 class BaseVMTypeService(
@@ -134,13 +145,6 @@ class BaseVMTypeService(
         return ClientPagedResultList(self._provider, list(matches))
         return ClientPagedResultList(self._provider, list(matches))
 
 
 
 
-class BaseInstanceService(
-        BasePageableObjectMixin, InstanceService, BaseCloudService):
-
-    def __init__(self, provider):
-        super(BaseInstanceService, self).__init__(provider)
-
-
 class BaseRegionService(
 class BaseRegionService(
         BasePageableObjectMixin, RegionService, BaseCloudService):
         BasePageableObjectMixin, RegionService, BaseCloudService):
 
 
@@ -177,6 +181,18 @@ class BaseNetworkService(
             log.info("Deleting network %s", network_id)
             log.info("Deleting network %s", network_id)
             network.delete()
             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(
 class BaseSubnetService(
         BasePageableObjectMixin, SubnetService, BaseCloudService):
         BasePageableObjectMixin, SubnetService, BaseCloudService):
@@ -190,6 +206,20 @@ class BaseSubnetService(
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         return ClientPagedResultList(self._provider, list(matches))
         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(
 class BaseRouterService(
         BasePageableObjectMixin, RouterService, BaseCloudService):
         BasePageableObjectMixin, RouterService, BaseCloudService):
@@ -207,3 +237,14 @@ class BaseRouterService(
             if router:
             if router:
                 log.info("Router %s successful deleted.", router)
                 log.info("Router %s successful deleted.", router)
                 router.delete()
                 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
         pass
 
 
     @abstractmethod
     @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.
         Create a new subnet within the supplied network.
 
 
@@ -1119,15 +1119,15 @@ class VMFirewallService(PageableObjectMixin, CloudService):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create(self, label, network_id, description=None):
+    def create(self, label, network, description=None):
         """
         """
         Create a new VMFirewall.
         Create a new VMFirewall.
 
 
         :type label: str
         :type label: str
         :param label: The label for the new VM firewall.
         :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
         :type description: str
         :param description: The description of the new VM firewall.
         :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)
         return self.svc.list(limit=limit, marker=marker)
 
 
     @cb_helpers.deprecated_alias(network_id='network')
     @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 "
         log.debug("Creating Firewall Service with the parameters "
                   "[label: %s id: %s description: %s]", label, network,
                   "[label: %s id: %s description: %s]", label, network,
                   description)
                   description)
@@ -374,6 +374,32 @@ class AWSBucketService(BaseBucketService):
                     raise
                     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):
 class AWSImageService(BaseImageService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):
@@ -388,7 +414,6 @@ class AWSImageService(BaseImageService):
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
         # Filter by name or label
         # Filter by name or label
-        name = kwargs.get('name', None)
         label = kwargs.get('label', None)
         label = kwargs.get('label', None)
         # Popped here, not used in the generic find
         # Popped here, not used in the generic find
         owner = kwargs.pop('owners', None)
         owner = kwargs.pop('owners', None)
@@ -397,16 +422,23 @@ class AWSImageService(BaseImageService):
             extra_args.update(Owners=owner)
             extra_args.update(Owners=owner)
 
 
         obj_list = []
         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:
         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',
             obj_list.extend(self.svc.find(filter_name='tag:Name',
                                           filter_value=label, **extra_args))
                                           filter_value=label, **extra_args))
-        if not name and not label:
+
+        if not label:
             obj_list = self
             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']
         filters = ['label', 'name']
         return cb_helpers.generic_find(filters, kwargs, obj_list)
         return cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
@@ -416,32 +448,6 @@ class AWSImageService(BaseImageService):
                              limit=limit, marker=marker)
                              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):
 class AWSInstanceService(BaseInstanceService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):
@@ -724,6 +730,25 @@ class AWSNetworkService(BaseNetworkService):
             cb_net.label = label
             cb_net.label = label
         return cb_net
         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):
 class AWSSubnetService(BaseSubnetService):
 
 
@@ -777,51 +802,48 @@ class AWSSubnetService(BaseSubnetService):
         return subnet
         return subnet
 
 
     def get_or_create_default(self, zone):
     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
         # Get/create an internet gateway for the default network and a
         # corresponding router if it does not already exist.
         # 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
         # Create a subnet in each of the region's zones
         region = self.provider.compute.regions.get(self.provider.region_name)
         region = self.provider.compute.regions.get(self.provider.region_name)
         default_sn = None
         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,
             sn_label = "{0}-{1}".format(AWSSubnet.CB_DEFAULT_SUBNET_LABEL,
                                         z.id[-1])
                                         z.id[-1])
             log.info("Creating default CloudBridge subnet %s", sn_label)
             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
             # Create a route table entry between the SN and the inet gateway
             # See note above about why this is commented
             # See note above about why this is commented
             # default_router.attach_subnet(sn)
             # default_router.attach_subnet(sn)
-            if zone and zone == z.name:
+            if zone and zone_name == z.name:
                 default_sn = sn
                 default_sn = sn
         # No specific zone was supplied; return the last created subnet
         # 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:
         if not default_sn:
             default_sn = sn
             default_sn = sn
         return default_sn
         return default_sn

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

@@ -40,7 +40,7 @@ class AzureVMFirewall(BaseVMFirewall):
 
 
     @property
     @property
     def network_id(self):
     def network_id(self):
-        return None
+        return self._vm_firewall.tags.get('network_id', None)
 
 
     @property
     @property
     def resource_id(self):
     def resource_id(self):
@@ -1317,10 +1317,10 @@ class AzureInstance(BaseInstance):
         """
         """
         self._provider.azure_client.deallocate_vm(self.id)
         self._provider.azure_client.deallocate_vm(self.id)
         self._provider.azure_client.delete_vm(self.id)
         self._provider.azure_client.delete_vm(self.id)
-        for public_ip_id in self._public_ip_ids:
-            self._provider.azure_client.delete_floating_ip(public_ip_id)
         for nic_id in self._nic_ids:
         for nic_id in self._nic_ids:
             self._provider.azure_client.delete_nic(nic_id)
             self._provider.azure_client.delete_nic(nic_id)
+        for public_ip_id in self._public_ip_ids:
+            self._provider.azure_client.delete_floating_ip(public_ip_id)
         for data_disk in self._vm.storage_profile.data_disks:
         for data_disk in self._vm.storage_profile.data_disks:
             if data_disk.managed_disk:
             if data_disk.managed_disk:
                 if self._vm.tags.get('delete_on_terminate',
                 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)
         return ClientPagedResultList(self.provider, fws, limit, marker)
 
 
     @cb_helpers.deprecated_alias(network_id='network')
     @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)
         AzureVMFirewall.assert_valid_resource_label(label)
         name = AzureVMFirewall._generate_name_from_label(label, "cb-fw")
         name = AzureVMFirewall._generate_name_from_label(label, "cb-fw")
+        net = network.id if isinstance(network, Network) else network
         parameters = {"location": self.provider.region_name,
         parameters = {"location": self.provider.region_name,
-                      "tags": {'Label': label}}
+                      "tags": {'Label': label,
+                               'network_id': net}}
 
 
         if description:
         if description:
             parameters['tags'].update(Description=description)
             parameters['tags'].update(Description=description)
@@ -140,7 +142,8 @@ class AzureKeyPairService(BaseKeyPairService):
                 return AzureKeyPair(self.provider, key_pair)
                 return AzureKeyPair(self.provider, key_pair)
             return None
             return None
         except AzureException as error:
         except AzureException as error:
-            log.exception(error)
+            log.debug("KeyPair %s was not found.", key_pair_id)
+            log.debug(error)
             return None
             return None
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
@@ -194,59 +197,6 @@ class AzureKeyPairService(BaseKeyPairService):
         return key_pair
         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):
 class AzureStorageService(BaseStorageService):
     def __init__(self, provider):
     def __init__(self, provider):
         super(AzureStorageService, self).__init__(provider)
         super(AzureStorageService, self).__init__(provider)
@@ -308,8 +258,7 @@ class AzureVolumeService(BaseVolumeService):
         return ClientPagedResultList(self.provider, cb_vols,
         return ClientPagedResultList(self.provider, cb_vols,
                                      limit=limit, marker=marker)
                                      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.
         Creates a new volume.
         """
         """
@@ -376,7 +325,7 @@ class AzureSnapshotService(BaseSnapshotService):
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
         obj_list = self
         obj_list = self
-        filters = ['name', 'label']
+        filters = ['label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
@@ -426,6 +375,54 @@ class AzureSnapshotService(BaseSnapshotService):
         return AzureSnapshot(self.provider, azure_snap)
         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):
 class AzureComputeService(BaseComputeService):
     def __init__(self, provider):
     def __init__(self, provider):
         super(AzureComputeService, self).__init__(provider)
         super(AzureComputeService, self).__init__(provider)
@@ -451,11 +448,54 @@ class AzureComputeService(BaseComputeService):
         return self._region_svc
         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):
 class AzureInstanceService(BaseInstanceService):
     def __init__(self, provider):
     def __init__(self, provider):
         super(AzureInstanceService, self).__init__(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,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
                launch_config=None, **kwargs):
 
 
@@ -750,37 +790,6 @@ class AzureInstanceService(BaseInstanceService):
             log.exception(cloud_error)
             log.exception(cloud_error)
             return None
             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):
     def find(self, **kwargs):
         obj_list = self
         obj_list = self
         filters = ['label']
         filters = ['label']
@@ -795,18 +804,6 @@ class AzureImageService(BaseImageService):
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      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):
 class AzureVMTypeService(BaseVMTypeService):
 
 
@@ -828,6 +825,29 @@ class AzureVMTypeService(BaseVMTypeService):
                                      limit=limit, marker=marker)
                                      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):
 class AzureNetworkingService(BaseNetworkingService):
     def __init__(self, provider):
     def __init__(self, provider):
         super(AzureNetworkingService, self).__init__(provider)
         super(AzureNetworkingService, self).__init__(provider)
@@ -908,29 +928,6 @@ class AzureNetworkService(BaseNetworkService):
         self.provider.azure_client.delete_network(network_id)
         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):
 class AzureSubnetService(BaseSubnetService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):
@@ -993,12 +990,12 @@ class AzureSubnetService(BaseSubnetService):
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
                                      matches if matches else [])
                                      matches if matches else [])
 
 
-    def create(self, label, network, cidr_block, **kwargs):
+    def create(self, label, network, cidr_block, zone):
         """
         """
         Create subnet
         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)
         AzureSubnet.assert_valid_resource_label(label)
         subnet_name = AzureSubnet._generate_name_from_label(label, "cb-sn")
         subnet_name = AzureSubnet._generate_name_from_label(label, "cb-sn")
 
 
@@ -1018,28 +1015,6 @@ class AzureSubnetService(BaseSubnetService):
         subnet.label = label
         subnet.label = label
         return subnet
         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):
     def delete(self, subnet):
         subnet_id = subnet.id if isinstance(subnet, Subnet) else subnet
         subnet_id = subnet.id if isinstance(subnet, Subnet) else subnet
         self.provider.azure_client.delete_subnet(subnet_id)
         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):
 class OpenStackVMFirewall(BaseVMFirewall):
+    _network_id_tag = "CB-AUTO-associated-network-id: "
 
 
     def __init__(self, provider, vm_firewall):
     def __init__(self, provider, vm_firewall):
         super(OpenStackVMFirewall, self).__init__(provider, vm_firewall)
         super(OpenStackVMFirewall, self).__init__(provider, vm_firewall)
@@ -1215,7 +1216,26 @@ class OpenStackVMFirewall(BaseVMFirewall):
 
 
         :return: Always return ``None``.
         :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
     @property
     def name(self):
     def name(self):

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

@@ -37,6 +37,7 @@ from cloudbridge.cloud.interfaces.exceptions \
     import DuplicateResourceException
     import DuplicateResourceException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import MachineImage
+from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import Snapshot
 from cloudbridge.cloud.interfaces.resources import Snapshot
 from cloudbridge.cloud.interfaces.resources import Subnet
 from cloudbridge.cloud.interfaces.resources import Subnet
@@ -216,6 +217,11 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
         log.debug("Creating OpenStack VM Firewall with the params: "
         log.debug("Creating OpenStack VM Firewall with the params: "
                   "[label: %s network id: %s description: %s]", label,
                   "[label: %s network id: %s description: %s]", label,
                   network, description)
                   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(
         sg = self.provider.os_conn.network.create_security_group(
             name=label, description=description or label)
             name=label, description=description or label)
         if sg:
         if sg:
@@ -244,61 +250,6 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
         return True
         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):
 class OpenStackStorageService(BaseStorageService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):
@@ -519,44 +470,6 @@ class OpenStackBucketService(BaseBucketService):
             return self.get(name)
             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):
 class OpenStackComputeService(BaseComputeService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):
@@ -583,6 +496,46 @@ class OpenStackComputeService(BaseComputeService):
         return self._region_svc
         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):
 class OpenStackInstanceService(BaseInstanceService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):
@@ -590,8 +543,7 @@ class OpenStackInstanceService(BaseInstanceService):
 
 
     def create(self, label, image, vm_type, subnet, zone,
     def create(self, label, image, vm_type, subnet, zone,
                key_pair=None, vm_firewalls=None, user_data=None,
                key_pair=None, vm_firewalls=None, user_data=None,
-               launch_config=None,
-               **kwargs):
+               launch_config=None, **kwargs):
         """Create a new virtual machine instance."""
         """Create a new virtual machine instance."""
         OpenStackInstance.assert_valid_resource_label(label)
         OpenStackInstance.assert_valid_resource_label(label)
 
 
@@ -765,6 +717,59 @@ class OpenStackInstanceService(BaseInstanceService):
             return None
             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):
 class OpenStackNetworkingService(BaseNetworkingService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):
@@ -875,18 +880,17 @@ class OpenStackSubnetService(BaseSubnetService):
             sn = self.find(label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL)
             sn = self.find(label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL)
             if sn:
             if sn:
                 return sn[0]
                 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,
                 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)
             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
             return sn
         except NeutronClientException:
         except NeutronClientException:
             return None
             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
 `find` method always returns a list, while the `get` method returns a single
 object. While the methods are similar across resources, they are explicitly
 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.
 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
 .. 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_list = provider.security.key_pairs.find(name='cloudbridge-intro')
     kp = kp_list[0]
     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
     # Network
     net = provider.networking.networks.get('network ID')
     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_list = provider.networking.networks.find(label='my-network')
     net = net_list[0]
     net = net_list[0]
 
 
     # Subnet
     # Subnet
     sn = provider.networking.subnets.get('subnet ID')
     sn = provider.networking.subnets.get('subnet ID')
     # Unknown network
     # Unknown network
-    sn_list = provider.networking.subnets.find(name='my-subnet')
     sn_list = provider.networking.subnets.find(label='my-subnet')
     sn_list = provider.networking.subnets.find(label='my-subnet')
     # Known network
     # Known network
-    sn_list = provider.networking.subnets.find(network=net.id, name='my-subnet')
     sn_list = provider.networking.subnets.find(network=net.id,
     sn_list = provider.networking.subnets.find(network=net.id,
                                                label='my-subnet')
                                                label='my-subnet')
     sn = sn_list(0)
     sn = sn_list(0)
 
 
     # Router
     # Router
     router = provider.networking.routers.get('router ID')
     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_list = provider.networking.routers.find(label='my-router')
     router = router_list[0]
     router = router_list[0]
 
 
     # Gateway
     # Gateway
     gateway = net.gateways.get_or_create_inet_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
     # Firewall
     fw = provider.security.vm_firewalls.get('firewall ID')
     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_list = provider.security.vm_firewalls.find(label='cloudbridge-intro')
     fw = fw_list[0]
     fw = fw_list[0]
 
 
     # Instance
     # Instance
     inst = provider.compute.instances.get('instance ID')
     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_list = provider.compute.instances.list(label='cloudbridge-intro')
     inst = inst_list[0]
     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):
         def create_fw(label):
             return self.provider.security.vm_firewalls.create(
             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):
         def cleanup_fw(fw):
             if fw:
             if fw:
                 fw.delete()
                 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,
         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'])
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_vm_firewall_properties(self):
     def test_vm_firewall_properties(self):