소스 검색

Merge pull request #198 from CloudVE/move_zone_to_provider

Move zone to provider
Nuwan Goonasekera 7 년 전
부모
커밋
3f1381c420
33개의 변경된 파일361개의 추가작업 그리고 350개의 파일을 삭제
  1. 16 1
      cloudbridge/cloud/base/provider.py
  2. 2 2
      cloudbridge/cloud/base/services.py
  3. 2 2
      cloudbridge/cloud/base/subservices.py
  4. 23 0
      cloudbridge/cloud/interfaces/provider.py
  5. 2 5
      cloudbridge/cloud/interfaces/resources.py
  6. 5 24
      cloudbridge/cloud/interfaces/services.py
  7. 1 5
      cloudbridge/cloud/interfaces/subservices.py
  8. 7 10
      cloudbridge/cloud/providers/aws/helpers.py
  9. 7 6
      cloudbridge/cloud/providers/aws/provider.py
  10. 1 2
      cloudbridge/cloud/providers/aws/resources.py
  11. 51 46
      cloudbridge/cloud/providers/aws/services.py
  12. 8 6
      cloudbridge/cloud/providers/azure/provider.py
  13. 2 3
      cloudbridge/cloud/providers/azure/resources.py
  14. 17 26
      cloudbridge/cloud/providers/azure/services.py
  15. 4 5
      cloudbridge/cloud/providers/gcp/provider.py
  16. 2 4
      cloudbridge/cloud/providers/gcp/resources.py
  17. 24 68
      cloudbridge/cloud/providers/gcp/services.py
  18. 11 9
      cloudbridge/cloud/providers/openstack/provider.py
  19. 2 2
      cloudbridge/cloud/providers/openstack/resources.py
  20. 60 39
      cloudbridge/cloud/providers/openstack/services.py
  21. 2 2
      docs/getting_started.rst
  22. 1 1
      docs/topics/block_storage.rst
  23. 12 1
      docs/topics/design_decisions.rst
  24. 8 7
      docs/topics/launch.rst
  25. 3 4
      docs/topics/networking.rst
  26. 20 18
      docs/topics/setup.rst
  27. 25 15
      test/helpers/__init__.py
  28. 7 13
      test/test_block_store_service.py
  29. 2 7
      test/test_compute_service.py
  30. 1 3
      test/test_image_service.py
  31. 28 0
      test/test_interface.py
  32. 4 12
      test/test_network_service.py
  33. 1 2
      test/test_object_life_cycle.py

+ 16 - 1
cloudbridge/cloud/base/provider.py

@@ -89,6 +89,21 @@ class BaseCloudProvider(CloudProvider):
         self._config_parser.read(CloudBridgeConfigLocations)
         self._middleware = SimpleMiddlewareManager()
         self.add_required_middleware()
+        self._region_name = None
+        self._zone_name = None
+
+    @property
+    def region_name(self):
+        return self._region_name
+
+    @property
+    def zone_name(self):
+        if not self._zone_name:
+            region = self.compute.regions.get(self.region_name)
+            # TODO: Default zone
+            zone = next(iter(region.zones))
+            self._zone_name = zone.name if zone else None
+        return self._zone_name
 
     @property
     def config(self):
@@ -152,7 +167,7 @@ class BaseCloudProvider(CloudProvider):
                  service_type)
         return False
 
-    def _get_config_value(self, key, default_value):
+    def _get_config_value(self, key, default_value=None):
         """
         A convenience method to extract a configuration value.
 

+ 2 - 2
cloudbridge/cloud/base/services.py

@@ -307,7 +307,7 @@ class BaseSubnetService(
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         return ClientPagedResultList(self._provider, list(matches))
 
-    def get_or_create_default(self, zone):
+    def get_or_create_default(self):
         # Look for a CB-default subnet
         matches = self.find(label=BaseSubnet.CB_DEFAULT_SUBNET_LABEL)
         if matches:
@@ -316,7 +316,7 @@ class BaseSubnetService(
         # 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,
-                             BaseSubnet.CB_DEFAULT_SUBNET_IPV4RANGE, zone)
+                             BaseSubnet.CB_DEFAULT_SUBNET_IPV4RANGE)
         return subnet
 
 

+ 2 - 2
cloudbridge/cloud/base/subservices.py

@@ -159,10 +159,10 @@ class BaseSubnetSubService(SubnetSubService, BasePageableObjectMixin):
         return self._provider.networking.subnets.find(network=self.network,
                                                       **kwargs)
 
-    def create(self, label, cidr_block, zone):
+    def create(self, label, cidr_block):
         return self._provider.networking.subnets.create(label,
                                                         self.network,
-                                                        cidr_block, zone)
+                                                        cidr_block)
 
     def delete(self, subnet):
         return self._provider.networking.subnets.delete(subnet)

+ 23 - 0
cloudbridge/cloud/interfaces/provider.py

@@ -119,6 +119,29 @@ class CloudProvider(object):
         """
         pass
 
+    @abstractproperty
+    def region_name(self):
+        """
+        Returns the region that this provider is connected to.
+        All provider operations will take place within this region.
+
+        :rtype: ``str``
+        :return:  a zone id
+        """
+        pass
+
+    @abstractproperty
+    def zone_name(self):
+        """
+        Returns the placement zone that this provider is connected to.
+        All provider operations will take place within this zone. Placement
+        zone must be within the provider default region.
+
+        :rtype: ``str``
+        :return:  a zone id
+        """
+        pass
+
 #     @abstractproperty
 #     def account(self):
 #         """

+ 2 - 5
cloudbridge/cloud/interfaces/resources.py

@@ -740,7 +740,7 @@ class LaunchConfig(object):
         lc.add_block_device(...)
 
         inst = provider.compute.instances.create(
-            'MyVM', image, vm_type, subnet, zone, launch_config=lc)
+            'MyVM', image, vm_type, subnet, launch_config=lc)
     """
 
     @abstractmethod
@@ -1623,13 +1623,10 @@ class Snapshot(ObjectLifeCycleMixin, LabeledCloudResource):
         pass
 
     @abstractmethod
-    def create_volume(self, placement, size=None, volume_type=None, iops=None):
+    def create_volume(self, size=None, volume_type=None, iops=None):
         """
         Create a new Volume from this Snapshot.
 
-        :type placement: ``str``
-        :param placement: The availability zone where to create the Volume.
-
         :type size: ``int``
         :param size: The size of the new volume, in GiB (optional). Defaults to
                      the size of the snapshot.

+ 5 - 24
cloudbridge/cloud/interfaces/services.py

@@ -215,9 +215,8 @@ class InstanceService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def create(self, label, image, vm_type, subnet, zone=None,
-               key_pair=None, vm_firewalls=None, user_data=None,
-               launch_config=None,
+    def create(self, label, image, vm_type, subnet, key_pair=None,
+               vm_firewalls=None, user_data=None, launch_config=None,
                **kwargs):
         """
         Creates a new virtual machine instance.
@@ -245,14 +244,6 @@ class InstanceService(PageableObjectMixin, CloudService):
                        work. Some providers (e.g. OpenStack) support a null
                        value but the behaviour is implementation specific.
 
-        :type  zone: ``Zone`` or ``str``
-        :param zone: The Zone or its id, where the instance should be placed.
-                     This parameter is provided for legacy compatibility (with
-                     classic networks).
-
-                     The subnet's placement zone will take precedence over this
-                     parameter, but in its absence, this value will be used.
-
         :type  key_pair: ``KeyPair`` or ``str``
         :param key_pair: The KeyPair object or its id, to set for the
                          instance.
@@ -335,7 +326,7 @@ class VolumeService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def create(self, label, size, zone, snapshot=None, description=None):
+    def create(self, label, size, snapshot=None, description=None):
         """
         Creates a new volume.
 
@@ -345,9 +336,6 @@ class VolumeService(PageableObjectMixin, CloudService):
         :type  size: ``int``
         :param size: The size of the volume (in GB).
 
-        :type  zone: ``str`` or :class:`.PlacementZone` object
-        :param zone: The availability zone in which the Volume will be created.
-
         :type  snapshot: ``str`` or :class:`.Snapshot` object
         :param snapshot: An optional reference to a snapshot from which this
                          volume should be created.
@@ -772,7 +760,7 @@ class SubnetService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def create(self, label, network, cidr_block, zone):
+    def create(self, label, network, cidr_block):
         """
         Create a new subnet within the supplied network.
 
@@ -786,17 +774,13 @@ class SubnetService(PageableObjectMixin, CloudService):
         :param cidr_block: CIDR block within the Network to assign to the
                            subnet.
 
-        :type zone: ``str``
-        :param zone: A placement zone for the subnet. Some providers
-                     may not support this, in which case the value is ignored.
-
         :rtype: ``object`` of :class:`.Subnet`
         :return:  A Subnet object
         """
         pass
 
     @abstractmethod
-    def get_or_create_default(self, zone):
+    def get_or_create_default(self):
         """
         Return a default subnet for the account or create one if not found.
         This provides a convenience method for obtaining a network if you
@@ -805,9 +789,6 @@ class SubnetService(PageableObjectMixin, CloudService):
         A default network is one marked as such by the provider or matches the
         default label used by this library (e.g., cloudbridge-net).
 
-        :type zone: :class:`.PlacementZone` object ``str``
-        :param zone: Placement zone where to look for the subnet.
-
         :rtype: ``object`` of :class:`.Subnet`
         :return: A Subnet object
         """

+ 1 - 5
cloudbridge/cloud/interfaces/subservices.py

@@ -370,7 +370,7 @@ class SubnetSubService(PageableObjectMixin):
         pass
 
     @abstractmethod
-    def create(self, label, cidr_block, zone):
+    def create(self, label, cidr_block):
         """
         Create a new subnet within the network holding this subservice.
 
@@ -381,10 +381,6 @@ class SubnetSubService(PageableObjectMixin):
         :param cidr_block: CIDR block within the Network to assign to the
                            subnet.
 
-        :type zone: ``str``
-        :param zone: A placement zone for the subnet. Some providers
-                     may not support this, in which case the value is ignored.
-
         :rtype: ``object`` of :class:`.Subnet`
         :return:  A Subnet object
         """

+ 7 - 10
cloudbridge/cloud/providers/aws/helpers.py

@@ -241,22 +241,19 @@ class BotoGenericService(object):
             return ClientPagedResultList(self.provider, results,
                                          limit=limit, marker=marker)
 
-    def find(self, filter_name, filter_value, limit=None, marker=None,
+    def find(self, filters, limit=None, marker=None,
              **kwargs):
         """
         Return a list of resources by filter.
 
-        :type filter_name: ``str``
-        :param filter_name: Name of the filter to use
-
-        :type filter_value: ``str``
-        :param filter_value: Value to filter with
+        :type filters: A ``dict`` of filters
+        :param filters: A list of filters, where the dict key is the filter
+            name and the value is the value to filter by.
         """
+        boto_filters = [{'Name': key, 'Values': [value]}
+                        for key, value in filters.items()]
         collection = self.boto_collection
-        collection = collection.filter(Filters=[{
-            'Name': filter_name,
-            'Values': [filter_value]
-            }])
+        collection = collection.filter(Filters=boto_filters)
         if kwargs:
             collection = collection.filter(**kwargs)
         return self.list(limit=limit, marker=marker, collection=collection)

+ 7 - 6
cloudbridge/cloud/providers/aws/provider.py

@@ -22,25 +22,26 @@ class AWSCloudProvider(BaseCloudProvider):
 
         # Initialize cloud connection fields
         # These are passed as-is to Boto
-        self.region_name = self._get_config_value('aws_region_name',
-                                                  'us-east-1')
+        self._region_name = self._get_config_value('aws_region_name',
+                                                   'us-east-1')
+        self._zone_name = self._get_config_value('aws_zone_name')
         self.session_cfg = {
             'aws_access_key_id': self._get_config_value(
-                'aws_access_key', get_env('AWS_ACCESS_KEY', None)),
+                'aws_access_key', get_env('AWS_ACCESS_KEY')),
             'aws_secret_access_key': self._get_config_value(
-                'aws_secret_key', get_env('AWS_SECRET_KEY', None)),
+                'aws_secret_key', get_env('AWS_SECRET_KEY')),
             'aws_session_token': self._get_config_value(
                 'aws_session_token', None)
         }
         self.ec2_cfg = {
             'use_ssl': self._get_config_value('ec2_is_secure', True),
             'verify': self._get_config_value('ec2_validate_certs', True),
-            'endpoint_url': self._get_config_value('ec2_endpoint_url', None)
+            'endpoint_url': self._get_config_value('ec2_endpoint_url')
         }
         self.s3_cfg = {
             'use_ssl': self._get_config_value('s3_is_secure', True),
             'verify': self._get_config_value('s3_validate_certs', True),
-            'endpoint_url': self._get_config_value('s3_endpoint_url', None)
+            'endpoint_url': self._get_config_value('s3_endpoint_url')
         }
 
         # service connections, lazily initialized

+ 1 - 2
cloudbridge/cloud/providers/aws/resources.py

@@ -590,12 +590,11 @@ class AWSSnapshot(BaseSnapshot):
             # set the status to unknown
             self._unknown_state = True
 
-    def create_volume(self, placement, size=None, volume_type=None, iops=None):
+    def create_volume(self, size=None, volume_type=None, iops=None):
         label = "from-snap-{0}".format(self.label or self.id)
         cb_vol = self._provider.storage.volumes.create(
             label=label,
             size=size,
-            zone=placement,
             snapshot=self.id)
         cb_vol.wait_till_ready()
         return cb_vol

+ 51 - 46
cloudbridge/cloud/providers/aws/services.py

@@ -40,7 +40,6 @@ from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import Network
-from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import Snapshot
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
@@ -59,7 +58,6 @@ from .resources import AWSKeyPair
 from .resources import AWSLaunchConfig
 from .resources import AWSMachineImage
 from .resources import AWSNetwork
-from .resources import AWSPlacementZone
 from .resources import AWSRegion
 from .resources import AWSRouter
 from .resources import AWSSnapshot
@@ -126,7 +124,7 @@ class AWSKeyPairService(BaseKeyPairService):
                 "attributes: %s" % (kwargs, 'name'))
 
         log.debug("Searching for Key Pair %s", name)
-        return self.svc.find(filter_name='key-name', filter_value=name)
+        return self.svc.find(filters={'key-name': name})
 
     @dispatch(event="provider.security.key_pairs.create",
               priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
@@ -201,8 +199,7 @@ class AWSVMFirewallService(BaseVMFirewallService):
             raise InvalidParamException(
                 "Unrecognised parameters for search: %s. Supported "
                 "attributes: %s" % (kwargs, 'label'))
-        return self.svc.find(filter_name='tag:Name',
-                             filter_value=label)
+        return self.svc.find(filters={'tag:Name': label})
 
     @dispatch(event="provider.security.vm_firewalls.delete",
               priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
@@ -343,23 +340,27 @@ class AWSVolumeService(BaseVolumeService):
                 "attributes: %s" % (kwargs, 'label'))
 
         log.debug("Searching for AWS Volume Service %s", label)
-        return self.svc.find(filter_name='tag:Name', filter_value=label)
+        return self.svc.find(
+            filters={'tag:Name': label,
+                     'availability-zone': self.provider.zone_name})
 
     @dispatch(event="provider.storage.volumes.list",
               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
-        return self.svc.list(limit=limit, marker=marker)
+        return self.svc.find(
+            filters={'availability-zone': self.provider.zone_name},
+            limit=limit, marker=marker)
 
     @dispatch(event="provider.storage.volumes.create",
               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, size, zone, snapshot=None, description=None):
+    def create(self, label, size, snapshot=None, description=None):
         AWSVolume.assert_valid_resource_label(label)
-        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
+        zone_name = self.provider.zone_name
         snapshot_id = snapshot.id if isinstance(
             snapshot, AWSSnapshot) and snapshot else snapshot
 
         cb_vol = self.svc.create('create_volume', Size=size,
-                                 AvailabilityZone=zone_id,
+                                 AvailabilityZone=zone_name,
                                  SnapshotId=snapshot_id)
         # Wait until ready to tag instance
         cb_vol.wait_till_ready()
@@ -399,8 +400,7 @@ class AWSSnapshotService(BaseSnapshotService):
         obj_list = []
         if label:
             log.debug("Searching for AWS Snapshot with label %s", label)
-            obj_list.extend(self.svc.find(filter_name='tag:Name',
-                                          filter_value=label,
+            obj_list.extend(self.svc.find(filters={'tag:Name': label},
                                           OwnerIds=['self']))
         else:
             obj_list = list(self)
@@ -624,10 +624,10 @@ class AWSImageService(BaseImageService):
         if label:
             log.debug("Searching for AWS Image Service %s", label)
             obj_list = []
-            obj_list.extend(self.svc.find(filter_name='name',
-                                          filter_value=label, **extra_args))
-            obj_list.extend(self.svc.find(filter_name='tag:Name',
-                                          filter_value=label, **extra_args))
+            obj_list.extend(
+                self.svc.find(filters={'name': label}, **extra_args))
+            obj_list.extend(
+                self.svc.find(filters={'tag:Name': label}, **extra_args))
             return obj_list
         else:
             return []
@@ -671,12 +671,12 @@ class AWSInstanceService(BaseInstanceService):
         if subnet:
             # subnet's zone takes precedence
             zone_id = subnet.zone.id
-        if isinstance(vm_firewalls, list) and isinstance(
+        if vm_firewalls and isinstance(vm_firewalls, list) and isinstance(
                 vm_firewalls[0], VMFirewall):
             vm_firewall_ids = [fw.id for fw in vm_firewalls]
         else:
             vm_firewall_ids = vm_firewalls
-        return subnet.id, zone_id, vm_firewall_ids
+        return subnet.id if subnet else None, zone_id, vm_firewall_ids
 
     def _process_block_device_mappings(self, launch_config):
         """
@@ -733,7 +733,7 @@ class AWSInstanceService(BaseInstanceService):
 
     @dispatch(event="provider.compute.instances.create",
               priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, image, vm_type, subnet, zone,
+    def create(self, label, image, vm_type, subnet,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
         AWSInstance.assert_valid_resource_label(label)
@@ -742,7 +742,7 @@ class AWSInstanceService(BaseInstanceService):
             isinstance(vm_type, VMType) else vm_type
         subnet = (self.provider.networking.subnets.get(subnet)
                   if isinstance(subnet, str) else subnet)
-        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
+        zone_name = self.provider.zone_name
         key_pair_name = key_pair.name if isinstance(
             key_pair,
             KeyPair) else key_pair
@@ -752,7 +752,7 @@ class AWSInstanceService(BaseInstanceService):
             bdm = None
 
         subnet_id, zone_id, vm_firewall_ids = \
-            self._resolve_launch_options(subnet, zone_id, vm_firewalls)
+            self._resolve_launch_options(subnet, zone_name, vm_firewalls)
 
         placement = {'AvailabilityZone': zone_id} if zone_id else None
         inst = self.svc.create('create_instances',
@@ -793,12 +793,16 @@ class AWSInstanceService(BaseInstanceService):
                 "Unrecognised parameters for search: %s. Supported "
                 "attributes: %s" % (kwargs, 'label'))
 
-        return self.svc.find(filter_name='tag:Name', filter_value=label)
+        return self.svc.find(
+            filters={'tag:Name': label,
+                     'availability-zone': self.provider.zone_name})
 
     @dispatch(event="provider.compute.instances.list",
               priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
-        return self.svc.list(limit=limit, marker=marker)
+        return self.svc.find(
+            filters={'availability-zone': self.provider.zone_name},
+            limit=limit, marker=marker)
 
     @dispatch(event="provider.compute.instances.delete",
               priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
@@ -939,7 +943,7 @@ class AWSNetworkService(BaseNetworkService):
                 "attributes: %s" % (kwargs, 'label'))
 
         log.debug("Searching for AWS Network Service %s", label)
-        return self.svc.find(filter_name='tag:Name', filter_value=label)
+        return self.svc.find(filters={'tag:Name': label})
 
     @dispatch(event="provider.networking.networks.create",
               priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
@@ -1002,10 +1006,13 @@ class AWSSubnetService(BaseSubnetService):
         network_id = network.id if isinstance(network, AWSNetwork) else network
         if network_id:
             return self.svc.find(
-                filter_name='vpc-id', filter_value=network_id,
+                filters={'vpc-id': network_id,
+                         'availability-zone': self.provider.zone_name},
                 limit=limit, marker=marker)
         else:
-            return self.svc.list(limit=limit, marker=marker)
+            return self.svc.find(
+                filters={'availability-zone': self.provider.zone_name},
+                limit=limit, marker=marker)
 
     @dispatch(event="provider.networking.subnets.find",
               priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
@@ -1019,14 +1026,15 @@ class AWSSubnetService(BaseSubnetService):
                 "attributes: %s" % (kwargs, 'label'))
 
         log.debug("Searching for AWS Subnet Service %s", label)
-        return self.svc.find(filter_name='tag:Name', filter_value=label)
+        return self.svc.find(
+            filters={'tag:Name': label,
+                     'availability-zone': self.provider.zone_name})
 
     @dispatch(event="provider.networking.subnets.create",
               priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, network, cidr_block, zone):
+    def create(self, label, network, cidr_block):
         AWSSubnet.assert_valid_resource_label(label)
-        zone_name = zone.name if isinstance(
-            zone, AWSPlacementZone) else zone
+        zone_name = self.provider.zone_name
 
         network_id = network.id if isinstance(network, AWSNetwork) else network
 
@@ -1046,8 +1054,8 @@ class AWSSubnetService(BaseSubnetService):
             # pylint:disable=protected-access
             sn._subnet.delete()
 
-    def get_or_create_default(self, zone):
-        zone_name = zone.name if isinstance(zone, AWSPlacementZone) else zone
+    def get_or_create_default(self):
+        zone_name = self.provider.zone_name
 
         # # Look for provider default subnet in current zone
         # if zone_name:
@@ -1077,8 +1085,6 @@ class AWSSubnetService(BaseSubnetService):
         if snl:
             # pylint:disable=protected-access
             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
@@ -1133,15 +1139,15 @@ class AWSSubnetService(BaseSubnetService):
         subnets = list(ip_net.subnets(prefixlen_diff=prefixlen_diff))
 
         for i, z in reversed(list(enumerate(region.zones))):
-            sn_label = "{0}-{1}".format(AWSSubnet.CB_DEFAULT_SUBNET_LABEL,
-                                        z.id[-1])
-            log.info("Creating a default CloudBridge subnet %s: %s" %
-                     (sn_label, str(subnets[i])))
-            sn = self.create(sn_label, default_net, str(subnets[i]), z)
-            # Create a route table entry between the SN and the inet gateway
-            # See note above about why this is commented
-            # default_router.attach_subnet(sn)
-            if zone and zone_name == z.name:
+            if zone_name == z.name:
+                sn_label = "{0}-{1}".format(AWSSubnet.CB_DEFAULT_SUBNET_LABEL,
+                                            z.id[-1])
+                log.info("Creating a default CloudBridge subnet %s: %s" %
+                         (sn_label, str(subnets[i])))
+                sn = self.create(sn_label, default_net, str(subnets[i]))
+                # Create a route table entry between the SN and the inet
+                # gateway. See note above about why this is commented
+                # default_router.attach_subnet(sn)
                 default_sn = sn
         # No specific zone was supplied; return the last created subnet
         # The list was originally reversed to have the last subnet be in zone a
@@ -1176,7 +1182,7 @@ class AWSRouterService(BaseRouterService):
                 "attributes: %s" % (kwargs, 'label'))
 
         log.debug("Searching for AWS Router Service %s", label)
-        return self.svc.find(filter_name='tag:Name', filter_value=label)
+        return self.svc.find(filters={'tag:Name': label})
 
     @dispatch(event="provider.networking.routers.list",
               priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
@@ -1218,8 +1224,7 @@ class AWSGatewayService(BaseGatewayService):
         # Don't filter by label because it may conflict with at least the
         # default VPC that most accounts have but that network is typically
         # without a name.
-        gtw = self.svc.find(filter_name='attachment.vpc-id',
-                            filter_value=network_id)
+        gtw = self.svc.find(filters={'attachment.vpc-id': network_id})
         if gtw:
             return gtw[0]  # There can be only one gtw attached to a VPC
         # Gateway does not exist so create one and attach to the supplied net

+ 8 - 6
cloudbridge/cloud/providers/azure/provider.py

@@ -30,17 +30,19 @@ class AzureCloudProvider(BaseCloudProvider):
         self.subscription_id = self._get_config_value(
             'azure_subscription_id', get_env('AZURE_SUBSCRIPTION_ID'))
         self.client_id = self._get_config_value(
-            'azure_client_id', get_env('AZURE_CLIENT_ID', None))
+            'azure_client_id', get_env('AZURE_CLIENT_ID'))
         self.secret = self._get_config_value(
-            'azure_secret', get_env('AZURE_SECRET', None))
+            'azure_secret', get_env('AZURE_SECRET'))
         self.tenant = self._get_config_value(
-            'azure_tenant', get_env('AZURE_TENANT', None))
+            'azure_tenant', get_env('AZURE_TENANT'))
 
         # optional config values
         self.access_token = self._get_config_value(
-            'azure_access_token', get_env('AZURE_ACCESS_TOKEN', None))
-        self.region_name = self._get_config_value(
+            'azure_access_token', get_env('AZURE_ACCESS_TOKEN'))
+        self._region_name = self._get_config_value(
             'azure_region_name', get_env('AZURE_REGION_NAME', 'eastus'))
+        self._zone_name = self._get_config_value(
+            'azure_zone_name', get_env('AZURE_ZONE_NAME'))
         self.resource_group = self._get_config_value(
             'azure_resource_group', get_env('AZURE_RESOURCE_GROUP',
                                             'cloudbridge'))
@@ -59,7 +61,7 @@ class AzureCloudProvider(BaseCloudProvider):
 
         self.vm_default_user_name = self._get_config_value(
                 'azure_vm_default_username', get_env(
-                    'AZURE_VM_DEFAULT_USERNAME', None)) \
+                    'AZURE_VM_DEFAULT_USERNAME')) \
             or self.__get_deprecated_username('cbuser')
 
         self.public_key_storage_table_name = self._get_config_value(

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

@@ -554,13 +554,12 @@ class AzureSnapshot(BaseSnapshot):
             # set the state to unknown
             self._state = 'unknown'
 
-    def create_volume(self, placement=None,
-                      size=None, volume_type=None, iops=None):
+    def create_volume(self, size=None, volume_type=None, iops=None):
         """
         Create a new Volume from this Snapshot.
         """
         return self._provider.storage.volumes. \
-            create(self.name, self.size, zone=placement, snapshot=self)
+            create(self.name, self.size, snapshot=self)
 
 
 class AzureMachineImage(BaseMachineImage):

+ 17 - 26
cloudbridge/cloud/providers/azure/services.py

@@ -36,7 +36,6 @@ from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
 from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import Network
-from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import Snapshot
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
@@ -397,12 +396,12 @@ class AzureVolumeService(BaseVolumeService):
 
     @dispatch(event="provider.storage.volumes.create",
               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, size, zone, snapshot=None, description=None):
+    def create(self, label, size, snapshot=None, description=None):
         AzureVolume.assert_valid_resource_label(label)
         disk_name = AzureVolume._generate_name_from_label(label, "cb-vol")
         tags = {'Label': label}
 
-        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
+        zone_name = self.provider.zone_name
         snapshot = (self.provider.storage.snapshots.get(snapshot)
                     if snapshot and isinstance(snapshot, str) else snapshot)
 
@@ -411,8 +410,7 @@ class AzureVolumeService(BaseVolumeService):
 
         if snapshot:
             params = {
-                'location':
-                    zone_id or self.provider.azure_client.region_name,
+                'location': zone_name,
                 'creation_data': {
                     'create_option': DiskCreateOption.copy,
                     'source_uri': snapshot.resource_id
@@ -425,8 +423,7 @@ class AzureVolumeService(BaseVolumeService):
 
         else:
             params = {
-                'location':
-                    zone_id or self.provider.region_name,
+                'location': zone_name,
                 'disk_size_gb': size,
                 'creation_data': {
                     'create_option': DiskCreateOption.empty
@@ -718,8 +715,7 @@ class AzureInstanceService(BaseInstanceService):
 
         return subnet.resource_id, zone_id, vm_firewall_id
 
-    def _create_storage_profile(self, image, launch_config, instance_name,
-                                zone_id):
+    def _create_storage_profile(self, image, launch_config, instance_name):
 
         if image.is_gallery_image:
             # pylint:disable=protected-access
@@ -745,7 +741,7 @@ class AzureInstanceService(BaseInstanceService):
 
         if launch_config:
             data_disks, root_disk_size = self._process_block_device_mappings(
-                launch_config, instance_name, zone_id)
+                launch_config)
             if data_disks:
                 storage_profile['data_disks'] = data_disks
             if root_disk_size:
@@ -753,8 +749,7 @@ class AzureInstanceService(BaseInstanceService):
 
         return storage_profile
 
-    def _process_block_device_mappings(self, launch_config,
-                                       vm_name, zone=None):
+    def _process_block_device_mappings(self, launch_config):
         """
         Processes block device mapping information
         and returns a Data disk dictionary list. If new volumes
@@ -828,7 +823,7 @@ class AzureInstanceService(BaseInstanceService):
 
     @dispatch(event="provider.compute.instances.create",
               priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, image, vm_type, subnet, zone,
+    def create(self, label, image, vm_type, subnet,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
         AzureInstance.assert_valid_resource_label(label)
@@ -844,24 +839,20 @@ class AzureInstanceService(BaseInstanceService):
         instance_size = vm_type.id if \
             isinstance(vm_type, VMType) else vm_type
 
-        if not subnet:
-            # Azure has only a single zone per region; use the current one
-            zone = self.provider.compute.regions.get(
-                self.provider.region_name).zones[0]
-            subnet = self.provider.networking.subnets.get_or_create_default(
-                zone)
+        if subnet:
+            subnet = (subnet if isinstance(subnet, AzureSubnet) else
+                      self.provider.networking.subnets.get(subnet))
         else:
-            subnet = (self.provider.networking.subnets.get(subnet)
-                      if isinstance(subnet, str) else subnet)
+            subnet = self.provider.networking.subnets.get_or_create_default()
 
-        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
+        zone_name = self.provider.zone_name
 
         subnet_id, zone_id, vm_firewall_id = \
             self._resolve_launch_options(instance_name,
-                                         subnet, zone_id, vm_firewalls)
+                                         subnet, zone_name, vm_firewalls)
 
         storage_profile = self._create_storage_profile(image, launch_config,
-                                                       instance_name, zone_id)
+                                                       instance_name)
 
         nic_params = {
             'location': self.provider.region_name,
@@ -906,7 +897,7 @@ class AzureInstanceService(BaseInstanceService):
             temp_key_pair = key_pair
 
         params = {
-            'location': zone_id or self.provider.region_name,
+            'location': zone_id,
             'os_profile': {
                 'admin_username': self.provider.vm_default_user_name,
                 'computer_name': instance_name,
@@ -1233,7 +1224,7 @@ class AzureSubnetService(BaseSubnetService):
 
     @dispatch(event="provider.networking.subnets.create",
               priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, network, cidr_block, zone):
+    def create(self, label, network, cidr_block):
         AzureSubnet.assert_valid_resource_label(label)
         # Although Subnet doesn't support tags in Azure, we use the parent
         # Network's tags to track its subnets' labels

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

@@ -221,12 +221,11 @@ class GCPCloudProvider(BaseCloudProvider):
         if self.credentials_file and not self.credentials_dict:
             with open(self.credentials_file) as creds_file:
                 self.credentials_dict = json.load(creds_file)
-        self.default_zone = self._get_config_value(
-            'gcp_default_zone',
-            os.environ.get('GCP_DEFAULT_ZONE') or 'us-central1-a')
-        self.region_name = self._get_config_value(
+        self._region_name = self._get_config_value(
             'gcp_region_name',
             os.environ.get('GCP_DEFAULT_REGION') or 'us-central1')
+        self._zone_name = self._get_config_value(
+            'gcp_zone_name', os.environ.get('GCP_ZONE_NAME'))
 
         if self.credentials_dict and 'project_id' in self.credentials_dict:
             self.project_name = self.credentials_dict['project_id']
@@ -281,7 +280,7 @@ class GCPCloudProvider(BaseCloudProvider):
                     self.gcp_compute,
                     project=self.project_name,
                     region=self.region_name,
-                    zone=self.default_zone)
+                    zone=self.zone_name)
         return self._compute_resources_cache
 
     @property

+ 2 - 4
cloudbridge/cloud/providers/gcp/resources.py

@@ -1870,7 +1870,7 @@ class GCPSnapshot(BaseSnapshot):
             # snapshot no longer exists
             self._snapshot['status'] = SnapshotState.UNKNOWN
 
-    def create_volume(self, placement, size=None, volume_type=None, iops=None):
+    def create_volume(self, size=None, volume_type=None, iops=None):
         """
         Create a new Volume from this Snapshot.
 
@@ -1882,9 +1882,7 @@ class GCPSnapshot(BaseSnapshot):
                 'pd-ssd'.
             iops: Not supported by GCP.
         """
-        zone_name = placement
-        if isinstance(placement, GCPPlacementZone):
-            zone_name = placement.name
+        zone_name = self._provider.zone_name
         vol_type = 'zones/{0}/diskTypes/{1}'.format(
             zone_name,
             'pd-standard' if (volume_type != 'pd-standard' or

+ 24 - 68
cloudbridge/cloud/providers/gcp/services.py

@@ -47,7 +47,6 @@ from .resources import GCPKeyPair
 from .resources import GCPLaunchConfig
 from .resources import GCPMachineImage
 from .resources import GCPNetwork
-from .resources import GCPPlacementZone
 from .resources import GCPRegion
 from .resources import GCPRouter
 from .resources import GCPSnapshot
@@ -320,7 +319,7 @@ class GCPVMTypeService(BaseVMTypeService):
                         .gcp_compute
                         .machineTypes()
                         .list(project=self.provider.project_name,
-                              zone=self.provider.default_zone)
+                              zone=self.provider.zone_name)
                         .execute())
         return response['items']
 
@@ -467,20 +466,14 @@ class GCPInstanceService(BaseInstanceService):
 
     @dispatch(event="provider.compute.instances.create",
               priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, image, vm_type, subnet, zone=None,
+    def create(self, label, image, vm_type, subnet,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
         """
         Creates a new virtual machine instance.
         """
         GCPInstance.assert_valid_resource_name(label)
-        zone_name = self.provider.default_zone
-        if zone:
-            if not isinstance(zone, GCPPlacementZone):
-                zone = GCPPlacementZone(
-                    self.provider,
-                    self.provider.get_resource('zones', zone))
-            zone_name = zone.name
+        zone_name = self.provider.zone_name
         if not isinstance(vm_type, GCPVMType):
             vm_type = self.provider.compute.vm_types.get(vm_type)
 
@@ -500,7 +493,7 @@ class GCPInstanceService(BaseInstanceService):
                     volume_name = 'disk-{0}'.format(uuid.uuid4())
                     volume_size = disk.size if disk.size else 1
                     volume = self.provider.storage.volumes.create(
-                        volume_name, volume_size, zone)
+                        volume_name, volume_size)
                     volume.wait_till_ready()
                     source_field = 'source'
                     source_value = volume.id
@@ -516,7 +509,7 @@ class GCPInstanceService(BaseInstanceService):
                     source_field = 'source'
                     source_value = disk.source.id
                 elif isinstance(disk.source, GCPSnapshot):
-                    volume = disk.source.create_volume(zone, size=disk.size)
+                    volume = disk.source.create_volume(size=disk.size)
                     volume.wait_till_ready()
                     source_field = 'source'
                     source_value = volume.id
@@ -662,7 +655,7 @@ class GCPInstanceService(BaseInstanceService):
                         .gcp_compute
                         .instances()
                         .list(project=self.provider.project_name,
-                              zone=self.provider.default_zone,
+                              zone=self.provider.zone_name,
                               maxResults=max_result,
                               pageToken=marker)
                         .execute())
@@ -962,19 +955,13 @@ class GCPSubnetService(BaseSubnetService):
 
     @dispatch(event="provider.networking.subnets.list",
               priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
-    def list(self, network=None, zone=None, limit=None, marker=None):
-        """
-        If the zone is not given, we list all subnets in the default region.
-        """
+    def list(self, network=None, limit=None, marker=None):
         filter = None
         if network is not None:
             network = (network if isinstance(network, GCPNetwork)
                        else self.provider.networking.networks.get(network))
             filter = 'network eq %s' % network.resource_url
-        if zone:
-            region_name = self._zone_to_region(zone)
-        else:
-            region_name = self.provider.region_name
+        region_name = self.provider.region_name
         subnets = []
         response = (self.provider
                         .gcp_compute
@@ -990,19 +977,14 @@ class GCPSubnetService(BaseSubnetService):
 
     @dispatch(event="provider.networking.subnets.create",
               priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, network, cidr_block, zone):
+    def create(self, label, network, cidr_block):
         """
-        GCP subnets are regional. The region is inferred from the zone;
-        otherwise, the default region, as set in the
+        GCP subnets are regional. The default region, as set in the
         provider, is used.
-
-        If a subnet with overlapping IP range exists already, we return that
-        instead of creating a new subnet. In this case, other parameters, i.e.
-        the name and the zone, are ignored.
         """
         GCPSubnet.assert_valid_resource_label(label)
         name = GCPSubnet._generate_name_from_label(label, 'cbsubnet')
-        region_name = self._zone_to_region(zone)
+        region_name = self.provider.region_name
 #         for subnet in self.iter(network=network):
 #            if BaseNetwork.cidr_blocks_overlap(subnet.cidr_block, cidr_block):
 #                 if subnet.region_name != region_name:
@@ -1052,22 +1034,19 @@ class GCPSubnetService(BaseSubnetService):
             log.warning('No label was found associated with this subnet '
                         '"{}" when deleted.'.format(sn.name))
 
-    def get_or_create_default(self, zone):
+    def get_or_create_default(self):
         """
-        Return an existing or create a new subnet for the supplied zone.
+        Return an existing or create a new subnet in the provider default zone.
 
         In GCP, subnets are a regional resource so a single subnet can services
-        an entire region. The supplied zone parameter is used to derive the
-        parent region under which the default subnet then exists.
+        an entire region.
         """
-        # In case the supplied zone param is `None`, resort to the default one
-        region = self._zone_to_region(zone or self.provider.default_zone,
-                                      return_name_only=False)
+        region_name = self.provider.region_name
         # Check if a default subnet already exists for the given region/zone
         for sn in self.find(label=GCPSubnet.CB_DEFAULT_SUBNET_LABEL):
-            if sn.region == region.id:
+            if sn.region_name == region_name:
                 return sn
-        # No default subnet in the supplied zone. Look for a default network,
+        # No default subnet in the current zone. Look for a default network,
         # then create a subnet whose address space does not overlap with any
         # other existing subnets. If there are existing subnets, this process
         # largely assumes the subnet address spaces are contiguous when it
@@ -1089,32 +1068,13 @@ class GCPSubnetService(BaseSubnetService):
             cidr_block = "{}/{}".format(next_sn_address, max_sn_ipa.prefixlen)
         sn = self.provider.networking.subnets.create(
                 label=GCPSubnet.CB_DEFAULT_SUBNET_LABEL,
-                cidr_block=cidr_block, network=net, zone=zone)
+                cidr_block=cidr_block, network=net)
         router = self.provider.networking.routers.get_or_create_default(net)
         router.attach_subnet(sn)
         gateway = net.gateways.get_or_create()
         router.attach_gateway(gateway)
         return sn
 
-    def _zone_to_region(self, zone, return_name_only=True):
-        """
-        Given a GCP zone, return parent region.
-
-        Supplied `zone` param can be a `str` or `GCPPlacementZone`.
-
-        If ``return_name_only`` is set, return the region name as a string;
-        otherwise, return a GCPRegion object.
-        """
-        region_name = self.provider.region_name
-        if zone:
-            if isinstance(zone, GCPPlacementZone):
-                region_name = zone.region_name
-            else:
-                region_name = zone[:-2]
-        if return_name_only:
-            return region_name
-        return self.provider.compute.regions.get(region_name)
-
 
 class GCPStorageService(BaseStorageService):
 
@@ -1175,7 +1135,7 @@ class GCPVolumeService(BaseVolumeService):
                         .gcp_compute
                         .disks()
                         .list(project=self.provider.project_name,
-                              zone=self.provider.default_zone,
+                              zone=self.provider.zone_name,
                               filter=filtr,
                               maxResults=max_result,
                               pageToken=marker)
@@ -1206,7 +1166,7 @@ class GCPVolumeService(BaseVolumeService):
                         .gcp_compute
                         .disks()
                         .list(project=self.provider.project_name,
-                              zone=self.provider.default_zone,
+                              zone=self.provider.zone_name,
                               maxResults=max_result,
                               pageToken=marker)
                         .execute())
@@ -1221,14 +1181,10 @@ class GCPVolumeService(BaseVolumeService):
 
     @dispatch(event="provider.storage.volumes.create",
               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, size, zone, snapshot=None, description=None):
+    def create(self, label, size, snapshot=None, description=None):
         GCPVolume.assert_valid_resource_label(label)
         name = GCPVolume._generate_name_from_label(label, 'cb-vol')
-        if not isinstance(zone, GCPPlacementZone):
-            zone = GCPPlacementZone(
-                self.provider,
-                self.provider.get_resource('zones', zone))
-        zone_name = zone.name
+        zone_name = self.provider.zone_name
         snapshot_id = snapshot.id if isinstance(
             snapshot, GCPSnapshot) and snapshot else snapshot
         labels = {'cblabel': label}
@@ -1344,13 +1300,13 @@ class GCPSnapshotService(BaseSnapshotService):
                          .disks()
                          .createSnapshot(
                              project=self.provider.project_name,
-                             zone=self.provider.default_zone,
+                             zone=self.provider.zone_name,
                              disk=volume_name, body=snapshot_body)
                          .execute())
         if 'zone' not in operation:
             return None
         self.provider.wait_for_operation(operation,
-                                         zone=self.provider.default_zone)
+                                         zone=self.provider.zone_name)
         cb_snap = self.get(name)
         return cb_snap
 

+ 11 - 9
cloudbridge/cloud/providers/openstack/provider.py

@@ -36,22 +36,24 @@ class OpenStackCloudProvider(BaseCloudProvider):
 
         # Initialize cloud connection fields
         self.username = self._get_config_value(
-            'os_username', get_env('OS_USERNAME', None))
+            'os_username', get_env('OS_USERNAME'))
         self.password = self._get_config_value(
-            'os_password', get_env('OS_PASSWORD', None))
+            'os_password', get_env('OS_PASSWORD'))
         self.project_name = self._get_config_value(
-            'os_project_name', get_env('OS_PROJECT_NAME', None)
-            or get_env('OS_TENANT_NAME', None))
+            'os_project_name', get_env('OS_PROJECT_NAME')
+            or get_env('OS_TENANT_NAME'))
         self.auth_url = self._get_config_value(
-            'os_auth_url', get_env('OS_AUTH_URL', None))
-        self.region_name = self._get_config_value(
-            'os_region_name', get_env('OS_REGION_NAME', None))
+            'os_auth_url', get_env('OS_AUTH_URL'))
+        self._region_name = self._get_config_value(
+            'os_region_name', get_env('OS_REGION_NAME'))
+        self._zone_name = self._get_config_value(
+            'os_zone_name', get_env('OS_ZONE_NAME'))
         self.project_domain_name = self._get_config_value(
             'os_project_domain_name',
-            get_env('OS_PROJECT_DOMAIN_NAME', None))
+            get_env('OS_PROJECT_DOMAIN_NAME'))
         self.user_domain_name = self._get_config_value(
             'os_user_domain_name',
-            get_env('OS_USER_DOMAIN_NAME', None))
+            get_env('OS_USER_DOMAIN_NAME'))
 
         # Service connections, lazily initialized
         self._nova = None

+ 2 - 2
cloudbridge/cloud/providers/openstack/resources.py

@@ -751,7 +751,7 @@ class OpenStackSnapshot(BaseSnapshot):
             # set the status to unknown
             self._snapshot.status = 'unknown'
 
-    def create_volume(self, placement, size=None, volume_type=None, iops=None):
+    def create_volume(self, size=None, volume_type=None, iops=None):
         """
         Create a new Volume from this Snapshot.
         """
@@ -759,7 +759,7 @@ class OpenStackSnapshot(BaseSnapshot):
         self.assert_valid_resource_label(vol_label)
         size = size if size else self._snapshot.size
         os_vol = self._provider.cinder.volumes.create(
-            size, name=vol_label, availability_zone=placement,
+            size, name=vol_label, availability_zone=self._provider.zone_name,
             snapshot_id=self._snapshot.id)
         cb_vol = OpenStackVolume(self._provider, os_vol)
         return cb_vol

+ 60 - 39
cloudbridge/cloud/providers/openstack/services.py

@@ -47,7 +47,6 @@ from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import Network
-from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import Snapshot
 from cloudbridge.cloud.interfaces.resources import Subnet
 from cloudbridge.cloud.interfaces.resources import TrafficDirection
@@ -368,11 +367,19 @@ class OpenStackVolumeService(BaseVolumeService):
               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
     def get(self, volume_id):
         try:
-            return OpenStackVolume(
-                self.provider, self.provider.cinder.volumes.get(volume_id))
+            os_vol = self.provider.cinder.volumes.get(volume_id)
         except CinderNotFound:
             log.debug("Volume %s was not found.", volume_id)
             return None
+        if os_vol.availability_zone != self.provider.zone_name:
+            log.debug("Volume %s was found in availability zone '%s' while the"
+                      " OpenStack provider is in zone '%s'",
+                      volume_id,
+                      os_vol.availability_zone,
+                      self.provider.zone_name)
+            return None
+        else:
+            return OpenStackVolume(self.provider, os_vol)
 
     @dispatch(event="provider.storage.volumes.find",
               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
@@ -386,7 +393,8 @@ class OpenStackVolumeService(BaseVolumeService):
                 "attributes: %s" % (kwargs, 'label'))
 
         log.debug("Searching for an OpenStack Volume with the label %s", label)
-        search_opts = {'name': label}
+        search_opts = {'name': label,
+                       'availability_zone': self.provider.zone_name}
         cb_vols = [
             OpenStackVolume(self.provider, vol)
             for vol in self.provider.cinder.volumes.list(
@@ -399,9 +407,11 @@ class OpenStackVolumeService(BaseVolumeService):
     @dispatch(event="provider.storage.volumes.list",
               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
+        search_opts = {'availability_zone': self.provider.zone_name}
         cb_vols = [
             OpenStackVolume(self.provider, vol)
             for vol in self.provider.cinder.volumes.list(
+                search_opts=search_opts,
                 limit=oshelpers.os_result_limit(self.provider, limit),
                 marker=marker)]
 
@@ -409,15 +419,15 @@ class OpenStackVolumeService(BaseVolumeService):
 
     @dispatch(event="provider.storage.volumes.create",
               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, size, zone, snapshot=None, description=None):
+    def create(self, label, size, snapshot=None, description=None):
         OpenStackVolume.assert_valid_resource_label(label)
-        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
+        zone_name = self.provider.zone_name
         snapshot_id = snapshot.id if isinstance(
             snapshot, OpenStackSnapshot) and snapshot else snapshot
 
         os_vol = self.provider.cinder.volumes.create(
             size, name=label, description=description,
-            availability_zone=zone_id, snapshot_id=snapshot_id)
+            availability_zone=zone_name, snapshot_id=snapshot_id)
         return OpenStackVolume(self.provider, os_vol)
 
     @dispatch(event="provider.storage.volumes.delete",
@@ -749,7 +759,7 @@ class OpenStackInstanceService(BaseInstanceService):
 
     @dispatch(event="provider.compute.instances.create",
               priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, image, vm_type, subnet, zone,
+    def create(self, label, image, vm_type, subnet,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
         OpenStackInstance.assert_valid_resource_label(label)
@@ -766,7 +776,7 @@ class OpenStackInstanceService(BaseInstanceService):
             net_id = (self.provider.networking.subnets
                       .get(subnet_id).network_id
                       if subnet_id else None)
-        zone_id = zone.id if isinstance(zone, PlacementZone) else zone
+        zone_name = self.provider.zone_name
         key_pair_name = key_pair.name if \
             isinstance(key_pair, KeyPair) else key_pair
         bdm = None
@@ -822,7 +832,7 @@ class OpenStackInstanceService(BaseInstanceService):
             vm_size,
             min_count=1,
             max_count=1,
-            availability_zone=zone_id,
+            availability_zone=zone_name,
             key_name=key_pair_name,
             security_groups=sg_name_list,
             userdata=str(user_data) or None,
@@ -841,7 +851,8 @@ class OpenStackInstanceService(BaseInstanceService):
                 "Unrecognised parameters for search: %s. Supported "
                 "attributes: %s" % (kwargs, 'label'))
 
-        search_opts = {'name': label}
+        search_opts = {'name': label,
+                       'availability_zone': self.provider.zone_name}
         cb_insts = [
             OpenStackInstance(self.provider, inst)
             for inst in self.provider.nova.servers.list(
@@ -856,9 +867,11 @@ class OpenStackInstanceService(BaseInstanceService):
         """
         List all instances.
         """
+        search_opts = {'availability_zone': self.provider.zone_name}
         cb_insts = [
             OpenStackInstance(self.provider, inst)
             for inst in self.provider.nova.servers.list(
+                search_opts=search_opts,
                 limit=oshelpers.os_result_limit(self.provider, limit),
                 marker=marker)]
         return oshelpers.to_server_paged_list(self.provider, cb_insts, limit)
@@ -871,10 +884,19 @@ class OpenStackInstanceService(BaseInstanceService):
         """
         try:
             os_instance = self.provider.nova.servers.get(instance_id)
-            return OpenStackInstance(self.provider, os_instance)
         except NovaNotFound:
             log.debug("Instance %s was not found.", instance_id)
             return None
+        if (getattr(os_instance,
+                    'OS-EXT-AZ:availability_zone', "")
+                != self.provider.zone_name):
+            log.debug("Instance %s was found in availability zone '%s' while "
+                      "the OpenStack provider is in zone '%s'",
+                      instance_id,
+                      getattr(os_instance, 'OS-EXT-AZ:availability_zone', ""),
+                      self.provider.zone_name)
+            return None
+        return OpenStackInstance(self.provider, os_instance)
 
     @dispatch(event="provider.compute.instances.delete",
               priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
@@ -998,27 +1020,22 @@ class OpenStackNetworkService(BaseNetworkService):
     def list(self, limit=None, marker=None):
         networks = [OpenStackNetwork(self.provider, network)
                     for network in self.provider.neutron.list_networks()
-                    .get('networks') if network]
+                    .get('networks') if network
+                    # If there are no availability zones, keep the network
+                    # in the results list
+                    and (not network.get('availability_zones')
+                         or self.provider.zone_name
+                         in network.get('availability_zones'))]
         return ClientPagedResultList(self.provider, networks,
                                      limit=limit, marker=marker)
 
     @dispatch(event="provider.networking.networks.find",
               priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
     def find(self, **kwargs):
-        label = kwargs.pop('label', None)
-
-        # All kwargs should have been popped at this time.
-        if len(kwargs) > 0:
-            raise InvalidParamException(
-                "Unrecognised parameters for search: %s. Supported "
-                "attributes: %s" % (kwargs, 'label'))
-
-        log.debug("Searching for OpenStack Network with label: %s", label)
-        networks = [OpenStackNetwork(self.provider, network)
-                    for network in self.provider.neutron.list_networks(
-                        name=label)
-                    .get('networks') if network]
-        return ClientPagedResultList(self.provider, networks)
+        obj_list = self
+        filters = ['label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches))
 
     @dispatch(event="provider.networking.networks.create",
               priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
@@ -1027,8 +1044,6 @@ class OpenStackNetworkService(BaseNetworkService):
         net_info = {'name': label or ""}
         network = self.provider.neutron.create_network({'network': net_info})
         cb_net = OpenStackNetwork(self.provider, network.get('network'))
-        if label:
-            cb_net.label = label
         return cb_net
 
     @dispatch(event="provider.networking.networks.delete",
@@ -1080,8 +1095,7 @@ class OpenStackSubnetService(BaseSubnetService):
 
     @dispatch(event="provider.networking.subnets.create",
               priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
-    def create(self, label, network, cidr_block, zone):
-        """zone param is ignored."""
+    def create(self, label, network, cidr_block):
         OpenStackSubnet.assert_valid_resource_label(label)
         network_id = (network.id if isinstance(network, OpenStackNetwork)
                       else network)
@@ -1098,10 +1112,7 @@ class OpenStackSubnetService(BaseSubnetService):
         sn_id = subnet.id if isinstance(subnet, OpenStackSubnet) else subnet
         self.provider.neutron.delete_subnet(sn_id)
 
-    def get_or_create_default(self, zone):
-        """
-        Subnet zone is not supported by OpenStack and is thus ignored.
-        """
+    def get_or_create_default(self):
         try:
             sn = self.find(label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL)
             if sn:
@@ -1111,7 +1122,7 @@ class OpenStackSubnetService(BaseSubnetService):
             sn = self.provider.networking.subnets.create(
                 label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL,
                 cidr_block=OpenStackSubnet.CB_DEFAULT_SUBNET_IPV4RANGE,
-                network=net, zone=zone)
+                network=net)
             router = self.provider.networking.routers.get_or_create_default(
                 net)
             router.attach_subnet(sn)
@@ -1130,15 +1141,25 @@ class OpenStackRouterService(BaseRouterService):
     @dispatch(event="provider.networking.routers.get",
               priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def get(self, router_id):
-        log.debug("Getting OpenStack Router with the id: %s", router_id)
         router = self.provider.os_conn.get_router(router_id)
-        return OpenStackRouter(self.provider, router) if router else None
+        if not router:
+            log.debug("Router %s was not found.", router_id)
+            return None
+        elif self.provider.zone_name not in router.availability_zones:
+            log.debug("Router %s was found in availability zone '%s' while the"
+                      " OpenStack provider is in zone '%s'",
+                      router_id,
+                      router.availability_zones,
+                      self.provider.zone_name)
+            return None
+        return OpenStackRouter(self.provider, router)
 
     @dispatch(event="provider.networking.routers.list",
               priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
         routers = self.provider.os_conn.list_routers()
-        os_routers = [OpenStackRouter(self.provider, r) for r in routers]
+        os_routers = [OpenStackRouter(self.provider, r) for r in routers
+                      if self.provider.zone_name in r.availability_zones]
         return ClientPagedResultList(self.provider, os_routers, limit=limit,
                                      marker=marker)
 

+ 2 - 2
docs/getting_started.rst

@@ -87,8 +87,8 @@ Google Compute Cloud:
 
     config = {'gcp_project_name': 'project name',
               'gcp_service_creds_file': 'service_file.json',
-              'gcp_default_zone': 'us-east1-b',  # Use desired value
-              'gcp_region_name': 'us-east1'}  # Use desired value
+              'gcp_region_name': 'us-east1',  # Use desired value
+              'gcp_zone_name': 'us-east1-b'}  # Use desired value
     provider = CloudProviderFactory().create_provider(ProviderList.GCP, config)
     image_id = 'https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1804-bionic-v20181222'
 

+ 1 - 1
docs/topics/block_storage.rst

@@ -14,7 +14,7 @@ performed via the :class:`.VolumeService`. To start, let's create a 1GB volume.
 
 .. code-block:: python
 
-    vol = provider.storage.volumes.create('cloudbridge-vol', 1, 'us-east-1e')
+    vol = provider.storage.volumes.create('cloudbridge-vol', 1)
     vol.wait_till_ready()
     provider.storage.volumes.list()
 

+ 12 - 1
docs/topics/design_decisions.rst

@@ -119,7 +119,18 @@ Resource identification, naming, and labeling
   `labels` adhere to the same restrictions - a minimum length of 3 which
   should be alphanumeric characters or dashes only. Names or labels should
   not begin or end with a dash, or have consecutive dashes.
-   
+
+Make providers single zone
+---------------------------
+
+  Allowing each operation to specify its own zone led to various complications,
+  such as the one detailed above. Ultimately, it led to an impasse with GCP,
+  which tended to require the zone for almost every operation and some of our
+  methods were not geared to do so. Therefore, by making the provider zone
+  specific, we have removed a considerable amount of complexity from both the
+  code, with no significant impact on usability, since operations generally
+  tend to be confined to the same zone. Multi-zone operations now require
+  multiple cloud provider instances.
 
   .. _63: https://github.com/CloudVE/cloudbridge/issues/63
   .. _131: https://github.com/CloudVE/cloudbridge/issues/131

+ 8 - 7
docs/topics/launch.rst

@@ -23,12 +23,11 @@ In addition, CloudBridge instances must be launched into a private subnet.
 While it is possible to create complex network configurations as shown in the
 `Private networking`_ section, if you don't particularly care in which subnet
 the instance is launched, CloudBridge provides a convenience function to
-quickly obtain a default subnet for use. We just need to supply a zone to use.
+quickly obtain a default subnet for use.
 
 .. code-block:: python
 
-    zone = provider.compute.regions.get(provider.region_name).zones[0]
-    subnet = provider.networking.subnets.get_or_create_default(zone)
+    subnet = provider.networking.subnets.get_or_create_default()
 
 When launching an instance, you can also specify several optional arguments
 such as the firewall (aka security group), a key pair, or instance user data.
@@ -44,13 +43,15 @@ if you don't have those resources under your account, take a look at the
 
 Launch an instance
 ------------------
-Once we have all the desired pieces, we'll use them to launch an instance:
+Once we have all the desired pieces, we'll use them to launch an instance.
+Note that the instance is launched in the provider's default region and zone,
+and can be overridden by changing the provider config.
 
 .. code-block:: python
 
     inst = provider.compute.instances.create(
         label='cloudbridge-vpc', image=img, vm_type=vm_type,
-        subnet=subnet, zone=zone, key_pair=kp, vm_firewalls=[fw])
+        subnet=subnet, key_pair=kp, vm_firewalls=[fw])
 
 Private networking
 ~~~~~~~~~~~~~~~~~~
@@ -73,7 +74,7 @@ that subnet.
 
     inst = provider.compute.instances.create(
         label='cloudbridge-vpc', image=img, vm_type=vm_type,
-        subnet=sn, zone=zone, key_pair=kp, vm_firewalls=[fw])
+        subnet=sn, key_pair=kp, vm_firewalls=[fw])
 
 For more information on how to create and setup a private network, take a look
 at `Networking <./networking.html>`_.
@@ -96,7 +97,7 @@ refer to :class:`.LaunchConfig`.
     inst = provider.compute.instances.create(
         label='cloudbridge-bdm', image=img,  vm_type=vm_type,
         launch_config=lc, key_pair=kp, vm_firewalls=[fw],
-        subnet=subnet, zone=zone)
+        subnet=subnet)
 
 where ``img`` is the :class:`.Image` object to use for the root volume.
 

+ 3 - 4
docs/topics/networking.rst

@@ -75,8 +75,7 @@ subnet (``/28``).
     net = provider.networking.networks.create(
         label='my-network', cidr_block='10.0.0.0/16')
     sn = net.subnets.create(label='my-subnet',
-                            cidr_block='10.0.0.0/28',
-                            zone=zone)
+                            cidr_block='10.0.0.0/28')
     router = provider.networking.routers.create(label='my-router', network=net)
     router.attach_subnet(sn)
     gateway = net.gateways.get_or_create()
@@ -91,9 +90,9 @@ The additional step that's required here is to assign a floating IP to the VM:
 
     net = provider.networking.networks.create(
         label='my-network', cidr_block='10.0.0.0/16')
-    sn = net.subnets.create(label='my-subnet', cidr_block='10.0.0.0/28', zone=zone)
+    sn = net.subnets.create(label='my-subnet', cidr_block='10.0.0.0/28')
 
-    vm = provider.compute.instances.create(label='my-inst', subnet=sn, zone=zone, ...)
+    vm = provider.compute.instances.create(label='my-inst', subnet=sn, ...)
 
     router = provider.networking.routers.create(label='my-router', network=net)
     router.attach_subnet(sn)

+ 20 - 18
docs/topics/setup.rst

@@ -86,32 +86,25 @@ AWS
 +---------------------+--------------------------------------------------------------+
 | Variable            | Description                                                  |
 +=====================+==============================================================+
+| aws_region_name     | Default region name. Default is ``us-east-1``.               |
++---------------------+--------------------------------------------------------------+
+| aws_zone_name       | Default zone name. If not specified, defaults to first zone  |
+|                     | in default region. If specified, must match default region.  |
++---------------------+--------------------------------------------------------------+
 | aws_session_token   | Session key for your AWS account (if using temporary         |
 |                     | credentials).                                                |
 +---------------------+--------------------------------------------------------------+
-| ec2_conn_path	      | Connection path. Default is ``/``.                           |
+| ec2_endpoint_url    | Endpoint to use. Default is ``ec2.us-east-1.amazonaws.com``. |
 +---------------------+--------------------------------------------------------------+
 | ec2_is_secure       | True to use an SSL connection. Default is ``True``.          |
 +---------------------+--------------------------------------------------------------+
-| ec2_port            | EC2 connection port. Does not need to be specified unless    |
-|                     | EC2 service is running on an alternative port.               |
-+---------------------+--------------------------------------------------------------+
-| ec2_region_endpoint | Endpoint to use. Default is ``ec2.us-east-1.amazonaws.com``. |
-+---------------------+--------------------------------------------------------------+
-| ec2_region_name     | Default region name. Default is ``us-east-1``.               |
-+---------------------+--------------------------------------------------------------+
 | ec2_validate_certs  | Whether to use SSL certificate verification. Default is      |
 |                     | ``False``.                                                   |
 +---------------------+--------------------------------------------------------------+
-| s3_conn_path        | Connection path. Default is ``/``.                           |
+| s3_endpoint_url     | Host connection endpoint. Default is ``s3.amazonaws.com``.   |
 +---------------------+--------------------------------------------------------------+
 | s3_is_secure        | True to use an SSL connection. Default is ``True``.          |
 +---------------------+--------------------------------------------------------------+
-| s3_host             | Host connection endpoint. Default is ``s3.amazonaws.com``.   |
-+---------------------+--------------------------------------------------------------+
-| s3_port             | Host connection port. Does not need to be specified unless   |
-|                     | S3 service is running on an alternative port.                |
-+---------------------+--------------------------------------------------------------+
 | s3_validate_certs   | Whether to use SSL certificate verification. Default is      |
 |                     | ``False``.                                                   |
 +---------------------+--------------------------------------------------------------+
@@ -130,6 +123,10 @@ Azure
 | azure_region_name                   | Default region to use for the current                    |
 |                                     | session. Default is ``eastus``.                          |
 +-------------------------------------+----------------------------------------------------------+
+| aws_zone_name                       | Default zone name. If not specified, defaults to first   |
+|                                     | zone in default region. If specified, must match default |
+|                                     | region.                                                  |
++-------------------------------------+--------------------------------------------------------------+
 | azure_resource_group                | Azure resource group to use. Default is ``cloudbridge``. |
 +-------------------------------------+----------------------------------------------------------+
 | azure_storage_account               | Azure storage account to use. Note that this value must  |
@@ -148,12 +145,13 @@ GCP
 +-------------------------+----------------------------------------------------------+
 | Variable                | Description                                              |
 +=========================+==========================================================+
-| gcp_default_zone        | Default placement zone to use for the current session.   |
-|                         | Default is ``us-central1-a``.                            |
-+-------------------------+----------------------------------------------------------+
 | gcp_region_name         | Default region to use for the current session. Default   |
 |                         | is ``us-central1``.                                      |
 +-------------------------+----------------------------------------------------------+
+| gcp_zone_name           | Default zone name. If not specified, defaults to first   |
+|                         | zone in default region. If specified, must match default |
+|                         | region.                                                  |
++-------------------------+----------------------------------------------------------+
 | gcp_vm_default_username | System user name for which supplied key pair will be     |
 |                         | placed.                                                  |
 +-------------------------+----------------------------------------------------------+
@@ -209,6 +207,8 @@ https://docs.microsoft.com/en-us/azure/role-based-access-control/overview.
 +-------------------------------------+-----------+
 | AZURE_REGION_NAME                   |           |
 +-------------------------------------+-----------+
+| AZURE_ZONE_NAME                     |           |
++-------------------------------------+-----------+
 | AZURE_RESOURCE_GROUP                |           |
 +-------------------------------------+-----------+
 | AZURE_STORAGE_ACCOUNT               |           |
@@ -226,7 +226,7 @@ GCP
 | or                     |           |
 | GCP_SERVICE_CREDS_FILE |           |
 +------------------------+-----------+
-| GCP_DEFAULT_ZONE       |           |
+| GCP_ZONE_NAME          |           |
 +------------------------+-----------+
 | GCP_PROJECT_NAME       |           |
 +------------------------+-----------+
@@ -249,6 +249,8 @@ OpenStack
 +------------------------+-----------+
 | OS_REGION_NAME         | ✔         |
 +------------------------+-----------+
+| OS_ZONE_NAME           |           |
++------------------------+-----------+
 | NOVA_SERVICE_NAME      |           |
 +------------------------+-----------+
 | OS_AUTH_TOKEN          |           |

+ 25 - 15
test/helpers/__init__.py

@@ -7,6 +7,7 @@ import uuid
 
 from cloudbridge.cloud.base import helpers as cb_helpers
 from cloudbridge.cloud.factory import CloudProviderFactory
+from cloudbridge.cloud.interfaces import CloudProvider
 from cloudbridge.cloud.interfaces import InstanceState
 from cloudbridge.cloud.interfaces import TestMockHelperMixin
 from cloudbridge.cloud.interfaces.resources import FloatingIpState
@@ -82,12 +83,14 @@ TEST_DATA_CONFIG = {
         "image": cb_helpers.get_env('CB_IMAGE_AWS', 'ami-aa2ea6d0'),
         "vm_type": cb_helpers.get_env('CB_VM_TYPE_AWS', 't2.nano'),
         "placement": cb_helpers.get_env('CB_PLACEMENT_AWS', 'us-east-1a'),
+        "placement_cfg_key": "aws_zone_name"
     },
     'OpenStackCloudProvider': {
         'image': cb_helpers.get_env('CB_IMAGE_OS',
                                     'c66bdfa1-62b1-43be-8964-e9ce208ac6a5'),
         "vm_type": cb_helpers.get_env('CB_VM_TYPE_OS', 'm1.tiny'),
         "placement": cb_helpers.get_env('CB_PLACEMENT_OS', 'nova'),
+        "placement_cfg_key": "os_zone_name"
     },
     'GCPCloudProvider': {
         'image': cb_helpers.get_env(
@@ -95,28 +98,32 @@ TEST_DATA_CONFIG = {
             'https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/'
             'global/images/ubuntu-1710-artful-v20180126'),
         'vm_type': cb_helpers.get_env('CB_VM_TYPE_GCP', 'f1-micro'),
-        'placement': cb_helpers.get_env('GCP_DEFAULT_ZONE', 'us-central1-a'),
+        'placement': cb_helpers.get_env('GCP_ZONE_NAME', 'us-central1-a'),
+        "placement_cfg_key": "gcp_zone_name"
     },
     "AzureCloudProvider": {
-        "placement":
-            cb_helpers.get_env('CB_PLACEMENT_AZURE', 'eastus'),
         "image":
             cb_helpers.get_env('CB_IMAGE_AZURE',
                                'Canonical:UbuntuServer:16.04.0-LTS:latest'),
-        "vm_type":
-            cb_helpers.get_env('CB_VM_TYPE_AZURE', 'Basic_A2'),
+        "vm_type": cb_helpers.get_env('CB_VM_TYPE_AZURE', 'Basic_A2'),
+        "placement": cb_helpers.get_env('CB_PLACEMENT_AZURE', 'eastus'),
+        "placement_cfg_key": "azure_zone_name"
     }
 }
 
 
 def get_provider_test_data(provider, key):
-    if "AWSCloudProvider" in provider.name:
+    provider_id = (provider.PROVIDER_ID if isinstance(provider, CloudProvider)
+                   else provider)
+    if "aws" == provider_id:
         return TEST_DATA_CONFIG.get("AWSCloudProvider").get(key)
-    elif "OpenStackCloudProvider" in provider.name:
+    if "mock" == provider_id:
+        return TEST_DATA_CONFIG.get("AWSCloudProvider").get(key)
+    elif "openstack" == provider_id:
         return TEST_DATA_CONFIG.get("OpenStackCloudProvider").get(key)
-    elif "GCPCloudProvider" in provider.name:
+    elif "gcp" == provider_id:
         return TEST_DATA_CONFIG.get("GCPCloudProvider").get(key)
-    elif "AzureCloudProvider" in provider.name:
+    elif "azure" == provider_id:
         return TEST_DATA_CONFIG.get("AzureCloudProvider").get(key)
     return None
 
@@ -125,8 +132,7 @@ def get_or_create_default_subnet(provider):
     """
     Return the default subnet to be used for tests
     """
-    return provider.networking.subnets.get_or_create_default(
-        zone=get_provider_test_data(provider, 'placement'))
+    return provider.networking.subnets.get_or_create_default()
 
 
 def cleanup_subnet(subnet):
@@ -185,7 +191,6 @@ def create_test_instance(
         instance_label, get_provider_test_data(provider, 'image'),
         get_provider_test_data(provider, 'vm_type'),
         subnet=subnet,
-        zone=get_provider_test_data(provider, 'placement'),
         key_pair=key_pair,
         vm_firewalls=vm_firewalls,
         launch_config=launch_config,
@@ -257,11 +262,16 @@ class ProviderTestBase(unittest.TestCase):
 
     def create_provider_instance(self):
         provider_name = cb_helpers.get_env("CB_TEST_PROVIDER", "aws")
+        zone_cfg_key = get_provider_test_data(provider_name,
+                                              'placement_cfg_key')
         factory = CloudProviderFactory()
         provider_class = factory.get_provider_class(provider_name)
-        config = {'default_wait_interval':
-                  self.get_provider_wait_interval(provider_class),
-                  'default_result_limit': 5}
+        config = {
+            'default_wait_interval': self.get_provider_wait_interval(
+                provider_class),
+            'default_result_limit': 5,
+            zone_cfg_key: get_provider_test_data(provider_name, 'placement')
+        }
         return provider_class(config)
 
     @property

+ 7 - 13
test/test_block_store_service.py

@@ -45,8 +45,7 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
     def test_crud_volume(self):
         def create_vol(label):
             return self.provider.storage.volumes.create(
-                label, 1,
-                helpers.get_provider_test_data(self.provider, "placement"))
+                label, 1)
 
         def cleanup_vol(vol):
             if vol:
@@ -77,7 +76,7 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                 self.provider, label, subnet=subnet)
 
             test_vol = self.provider.storage.volumes.create(
-                label, 1, test_instance.zone_id)
+                label, 1)
             with cb_helpers.cleanup_action(lambda: test_vol.delete()):
                 test_vol.wait_till_ready()
                 test_vol.attach(test_instance, '/dev/sda2')
@@ -104,7 +103,7 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                 self.provider, label, subnet=subnet)
 
             test_vol = self.provider.storage.volumes.create(
-                label, 1, test_instance.zone_id, description=vol_desc)
+                label, 1, description=vol_desc)
             with cb_helpers.cleanup_action(lambda: test_vol.delete()):
                 test_vol.wait_till_ready()
                 self.assertTrue(
@@ -154,8 +153,7 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
         # Delete everything afterwards.
         label = "cb-crudsnap-{0}".format(helpers.get_uuid())
         test_vol = self.provider.storage.volumes.create(
-            label, 1,
-            helpers.get_provider_test_data(self.provider, "placement"))
+            label, 1)
         with cb_helpers.cleanup_action(lambda: test_vol.delete()):
             test_vol.wait_till_ready()
 
@@ -193,8 +191,7 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
     def test_snapshot_properties(self):
         label = "cb-snapprop-{0}".format(helpers.get_uuid())
         test_vol = self.provider.storage.volumes.create(
-            label, 1,
-            helpers.get_provider_test_data(self.provider, "placement"))
+            label, 1)
         with cb_helpers.cleanup_action(lambda: test_vol.delete()):
             test_vol.wait_till_ready()
             snap_label = "cb-snap-{0}".format(label)
@@ -230,14 +227,11 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                 # Test volume creation from a snapshot (via VolumeService)
                 sv_label = "cb-snapvol-{0}".format(test_snap.name)
                 snap_vol = self.provider.storage.volumes.create(
-                    sv_label, 1,
-                    helpers.get_provider_test_data(self.provider, "placement"),
-                    snapshot=test_snap)
+                    sv_label, 1, snapshot=test_snap)
                 with cb_helpers.cleanup_action(lambda: snap_vol.delete()):
                     snap_vol.wait_till_ready()
 
                 # Test volume creation from a snapshot (via Snapshot)
-                snap_vol2 = test_snap.create_volume(
-                    helpers.get_provider_test_data(self.provider, "placement"))
+                snap_vol2 = test_snap.create_volume()
                 with cb_helpers.cleanup_action(lambda: snap_vol2.delete()):
                     snap_vol2.wait_till_ready()

+ 2 - 7
test/test_compute_service.py

@@ -241,9 +241,7 @@ class CloudComputeServiceTestCase(ProviderTestBase):
                                 " not stable enough yet")
 
         test_vol = self.provider.storage.volumes.create(
-           label, 1,
-           helpers.get_provider_test_data(self.provider,
-                                          "placement"))
+           label, 1)
         with cb_helpers.cleanup_action(lambda: test_vol.delete()):
             test_vol.wait_till_ready()
             test_snap = test_vol.create_snapshot(label=label,
@@ -332,10 +330,7 @@ class CloudComputeServiceTestCase(ProviderTestBase):
             net = self.provider.networking.networks.create(
                 label=label, cidr_block=BaseNetwork.CB_DEFAULT_IPV4RANGE)
             cidr = '10.0.1.0/24'
-            subnet = net.subnets.create(label=label, cidr_block=cidr,
-                                        zone=helpers.get_provider_test_data(
-                                                     self.provider,
-                                                     'placement'))
+            subnet = net.subnets.create(label=label, cidr_block=cidr)
             test_inst = helpers.get_test_instance(self.provider, label,
                                                   subnet=subnet)
             fw = self.provider.security.vm_firewalls.create(

+ 1 - 3
test/test_image_service.py

@@ -62,9 +62,7 @@ class CloudImageServiceTestCase(ProviderTestBase):
                 img_instance = self.provider.compute.instances.create(
                     img_inst_label, img,
                     helpers.get_provider_test_data(self.provider, 'vm_type'),
-                    subnet=subnet,
-                    zone=helpers.get_provider_test_data(
-                        self.provider, 'placement'))
+                    subnet=subnet)
                 img_instance.wait_till_ready()
                 self.assertIsInstance(img_instance, Instance)
                 self.assertEqual(

+ 28 - 0
test/test_interface.py

@@ -61,3 +61,31 @@ class CloudInterfaceTestCase(ProviderTestBase):
             cloned_provider = CloudProviderFactory().create_provider(
                 self.provider.PROVIDER_ID, cloned_config)
             cloned_provider.authenticate()
+
+    def test_provider_zone_in_region(self):
+        cloned_config = self.provider.config.copy()
+        # Just a simpler way set zone to null for any provider
+        # instead of doing it individually for each provider
+        cloned_config['aws_zone_name'] = None
+        cloned_config['azure_zone_name'] = None
+        cloned_config['gcp_zone_name'] = None
+        cloned_config['openstack_zone_name'] = None
+        cloned_provider = CloudProviderFactory().create_provider(
+                self.provider.PROVIDER_ID, cloned_config)
+        region = cloned_provider.compute.regions.get(
+            cloned_provider.region_name)
+        matches = [zone.name for zone in region.zones
+                   if zone.name == cloned_provider.zone_name]
+        self.assertListEqual([cloned_provider.zone_name], matches)
+
+    def test_provider_always_has_zone(self):
+        cloned_config = self.provider.config.copy()
+        # Just a simpler way set zone to null for any provider
+        # instead of doing it individually for each provider
+        cloned_config['aws_zone_name'] = None
+        cloned_config['azure_zone_name'] = None
+        cloned_config['gcp_zone_name'] = None
+        cloned_config['openstack_zone_name'] = None
+        cloned_provider = CloudProviderFactory().create_provider(
+                self.provider.PROVIDER_ID, cloned_config)
+        self.assertIsNotNone(cloned_provider.zone_name)

+ 4 - 12
test/test_network_service.py

@@ -9,7 +9,6 @@ from cloudbridge.cloud.interfaces.resources import SubnetState
 
 import test.helpers as helpers
 from test.helpers import ProviderTestBase
-from test.helpers import get_provider_test_data
 from test.helpers import standard_interface_tests as sit
 
 
@@ -91,9 +90,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
 
             cidr = '10.0.20.0/24'
             sn = net.subnets.create(
-                label=subnet_label, cidr_block=cidr,
-                zone=helpers.get_provider_test_data(self.provider,
-                                                    'placement'))
+                label=subnet_label, cidr_block=cidr)
             with cb_helpers.cleanup_action(lambda: helpers.cleanup_subnet(sn)):
                 self.assertTrue(
                     sn in net.subnets,
@@ -138,9 +135,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
 
         def create_subnet(label):
             return self.provider.networking.subnets.create(
-                label=label, network=net, cidr_block="10.0.10.0/24",
-                zone=helpers.get_provider_test_data(
-                    self.provider, 'placement'))
+                label=label, network=net, cidr_block="10.0.10.0/24")
 
         def cleanup_subnet(subnet):
             if subnet:
@@ -236,9 +231,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
             router = self.provider.networking.routers.create(label=label,
                                                              network=net)
             cidr = '10.0.15.0/24'
-            sn = net.subnets.create(label=label, cidr_block=cidr,
-                                    zone=helpers.get_provider_test_data(
-                                        self.provider, 'placement'))
+            sn = net.subnets.create(label=label, cidr_block=cidr)
 
             # Check basic router properties
             sit.check_standard_behaviour(
@@ -275,6 +268,5 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
 
     @helpers.skipIfNoService(['networking.networks'])
     def test_default_network(self):
-        subnet = self.provider.networking.subnets.get_or_create_default(
-            zone=get_provider_test_data(self.provider, 'placement'))
+        subnet = self.provider.networking.subnets.get_or_create_default()
         self.assertIsInstance(subnet, Subnet)

+ 1 - 2
test/test_object_life_cycle.py

@@ -17,8 +17,7 @@ class CloudObjectLifeCycleTestCase(ProviderTestBase):
         test_vol = None
         with cb_helpers.cleanup_action(lambda: test_vol.delete()):
             test_vol = self.provider.storage.volumes.create(
-                label, 1,
-                helpers.get_provider_test_data(self.provider, "placement"))
+                label, 1)
 
             # Waiting for an invalid timeout should raise an exception
             with self.assertRaises(AssertionError):