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

Added support for listing zones within regions for AWS and OpenStack.

Nuwan Goonasekera пре 10 година
родитељ
комит
3bfeddd108

+ 6 - 0
cloudbridge/cloud/base/resources.py

@@ -562,6 +562,12 @@ class BaseRegion(Region, BaseCloudResource):
                 self._provider == other._provider and
                 self._provider == other._provider and
                 self.id == other.id)
                 self.id == other.id)
 
 
+    def to_json(self):
+        attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
+        js = {k: v for(k, v) in attr if not k.startswith('_')}
+        js['zones'] = [z.name for z in self.zones]
+        return json.dumps(js, sort_keys=True)
+
 
 
 class BaseBucketObject(BucketObject, BaseCloudResource):
 class BaseBucketObject(BucketObject, BaseCloudResource):
 
 

+ 4 - 1
cloudbridge/cloud/providers/aws/provider.py

@@ -106,6 +106,9 @@ class AWSCloudProvider(BaseCloudProvider):
         Get a boto ec2 connection object.
         Get a boto ec2 connection object.
         """
         """
         r = RegionInfo(name=self.region_name, endpoint=self.region_endpoint)
         r = RegionInfo(name=self.region_name, endpoint=self.region_endpoint)
+        return self._conect_ec2_region(r)
+
+    def _conect_ec2_region(self, region):
         ec2_conn = boto.connect_ec2(
         ec2_conn = boto.connect_ec2(
             aws_access_key_id=self.a_key,
             aws_access_key_id=self.a_key,
             aws_secret_access_key=self.s_key,
             aws_secret_access_key=self.s_key,
@@ -113,7 +116,7 @@ class AWSCloudProvider(BaseCloudProvider):
             # zone support for EC2
             # zone support for EC2
             api_version='2012-06-01' if self.cloud_type == 'aws' else None,
             api_version='2012-06-01' if self.cloud_type == 'aws' else None,
             is_secure=self.ec2_is_secure,
             is_secure=self.ec2_is_secure,
-            region=r,
+            region=region,
             port=self.ec2_port,
             port=self.ec2_port,
             path=self.ec2_conn_path,
             path=self.ec2_conn_path,
             validate_certs=self.ec2_validate_certs,
             validate_certs=self.ec2_validate_certs,

+ 19 - 4
cloudbridge/cloud/providers/aws/resources.py

@@ -104,13 +104,15 @@ class AWSMachineImage(BaseMachineImage):
 
 
 class AWSPlacementZone(BasePlacementZone):
 class AWSPlacementZone(BasePlacementZone):
 
 
-    def __init__(self, provider, zone):
+    def __init__(self, provider, zone, region):
         super(AWSPlacementZone, self).__init__(provider)
         super(AWSPlacementZone, self).__init__(provider)
         if isinstance(zone, AWSPlacementZone):
         if isinstance(zone, AWSPlacementZone):
             # pylint:disable=protected-access
             # pylint:disable=protected-access
             self._aws_zone = zone._aws_zone
             self._aws_zone = zone._aws_zone
+            self._aws_region = zone._aws_region
         else:
         else:
             self._aws_zone = zone
             self._aws_zone = zone
+            self._aws_region = region
 
 
     @property
     @property
     def id(self):
     def id(self):
@@ -140,7 +142,7 @@ class AWSPlacementZone(BasePlacementZone):
         :rtype: ``str``
         :rtype: ``str``
         :return: Name of this zone's region as returned by the cloud middleware
         :return: Name of this zone's region as returned by the cloud middleware
         """
         """
-        return self._aws_zone.region_name
+        return self._aws_region
 
 
 
 
 class AWSInstanceType(BaseInstanceType):
 class AWSInstanceType(BaseInstanceType):
@@ -282,7 +284,8 @@ class AWSInstance(BaseInstance):
         """
         """
         Get the placement zone where this instance is running.
         Get the placement zone where this instance is running.
         """
         """
-        return AWSPlacementZone(self._provider, self._ec2_instance.placement)
+        return AWSPlacementZone(self._provider, self._ec2_instance.placement,
+                                self._provider.region_name)
 
 
     @property
     @property
     def security_groups(self):
     def security_groups(self):
@@ -721,7 +724,19 @@ class AWSRegion(BaseRegion):
         """
         """
         Accesss information about placement zones within this region.
         Accesss information about placement zones within this region.
         """
         """
-        pass
+        if self.name == self._provider.region_name:  # optimisation
+            zones = self._provider.ec2_conn.get_all_zones()
+            return [AWSPlacementZone(self._provider, zone.name,
+                                     self._provider.region_name)
+                    for zone in zones]
+        else:
+            region = [region for region in
+                      self._provider.ec2_conn.get_all_regions()
+                      if self.name == region.name][0]
+            conn = self._provider._conect_ec2_region(region)
+            zones = conn.get_all_zones()
+            return [AWSPlacementZone(self._provider, zone.name, region.name)
+                    for zone in zones]
 
 
 
 
 class AWSNetwork(BaseNetwork):
 class AWSNetwork(BaseNetwork):

+ 0 - 1
cloudbridge/cloud/providers/aws/services.py

@@ -626,7 +626,6 @@ class AWSRegionService(BaseRegionService):
             return None
             return None
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
-
         regions = [AWSRegion(self.provider, region)
         regions = [AWSRegion(self.provider, region)
                    for region in self.provider.ec2_conn.get_all_regions()]
                    for region in self.provider.ec2_conn.get_all_regions()]
         return ClientPagedResultList(self.provider, regions,
         return ClientPagedResultList(self.provider, regions,

+ 4 - 1
cloudbridge/cloud/providers/openstack/provider.py

@@ -131,6 +131,9 @@ class OpenStackCloudProvider(BaseCloudProvider):
         return self._object_store
         return self._object_store
 
 
     def _connect_nova(self):
     def _connect_nova(self):
+        return self._connect_nova_region(self.region_name)
+
+    def _connect_nova_region(self, region_name):
         """
         """
         Get an OpenStack Nova (compute) client object for the given cloud.
         Get an OpenStack Nova (compute) client object for the given cloud.
         """
         """
@@ -147,7 +150,7 @@ class OpenStackCloudProvider(BaseCloudProvider):
         return nova_client.Client(
         return nova_client.Client(
             api_version, username=self.username, api_key=self.password,
             api_version, username=self.username, api_key=self.password,
             project_id=self.tenant_name, auth_url=self.auth_url,
             project_id=self.tenant_name, auth_url=self.auth_url,
-            region_name=self.region_name, service_name=service_name,
+            region_name=region_name, service_name=service_name,
             http_log_debug=True if self.config.debug_mode else False)
             http_log_debug=True if self.config.debug_mode else False)
 
 
     def _connect_keystone(self):
     def _connect_keystone(self):

+ 14 - 9
cloudbridge/cloud/providers/openstack/resources.py

@@ -98,12 +98,14 @@ class OpenStackMachineImage(BaseMachineImage):
 
 
 class OpenStackPlacementZone(BasePlacementZone):
 class OpenStackPlacementZone(BasePlacementZone):
 
 
-    def __init__(self, provider, zone):
+    def __init__(self, provider, zone, region):
         super(OpenStackPlacementZone, self).__init__(provider)
         super(OpenStackPlacementZone, self).__init__(provider)
         if isinstance(zone, OpenStackPlacementZone):
         if isinstance(zone, OpenStackPlacementZone):
             self._os_zone = zone._os_zone  # pylint:disable=protected-access
             self._os_zone = zone._os_zone  # pylint:disable=protected-access
+            self._os_region = zone._os_region
         else:
         else:
             self._os_zone = zone
             self._os_zone = zone
+            self._os_region = region
 
 
     @property
     @property
     def id(self):
     def id(self):
@@ -134,7 +136,7 @@ class OpenStackPlacementZone(BasePlacementZone):
         :rtype: ``str``
         :rtype: ``str``
         :return: Name of this zone's region as returned by the cloud middleware
         :return: Name of this zone's region as returned by the cloud middleware
         """
         """
-        return self._os_zone.region_name
+        return self._os_region
 
 
 
 
 class OpenStackInstanceType(BaseInstanceType):
 class OpenStackInstanceType(BaseInstanceType):
@@ -297,7 +299,8 @@ class OpenStackInstance(BaseInstance):
         """
         """
         return OpenStackPlacementZone(
         return OpenStackPlacementZone(
             self._provider,
             self._provider,
-            getattr(self._os_instance, 'OS-EXT-AZ:availability_zone', None))
+            getattr(self._os_instance, 'OS-EXT-AZ:availability_zone', None),
+            self._provider.region_name)
 
 
     @property
     @property
     def security_groups(self):
     def security_groups(self):
@@ -373,13 +376,15 @@ class OpenStackRegion(BaseRegion):
     def zones(self):
     def zones(self):
         # detailed must be set to ``False`` because the (default) ``True``
         # detailed must be set to ``False`` because the (default) ``True``
         # value requires Admin privileges
         # value requires Admin privileges
-        return self._provider.nova.availability_zones.list(detailed=False)
+        if self.name == self._provider.region_name:  # optimisation
+            zones = self._provider.nova.availability_zones.list(detailed=False)
+        else:
+            region_nova = self._provider._connect_nova_region(self.name)
+            zones = region_nova.availability_zones.list(detailed=False)
 
 
-    def to_json(self):
-        attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
-        js = {k: v for(k, v) in attr if not k.startswith('_')}
-        js['zones'] = [z.zoneName for z in self.zones]
-        return json.dumps(js, sort_keys=True)
+        return [OpenStackPlacementZone(self._provider, z.zoneName,
+                                       self._provider.region_name)
+                for z in zones]
 
 
 
 
 class OpenStackVolume(BaseVolume):
 class OpenStackVolume(BaseVolume):

+ 2 - 1
cloudbridge/cloud/providers/openstack/services.py

@@ -453,7 +453,8 @@ class OpenStackRegionService(BaseRegionService):
         return next(region, None)
         return next(region, None)
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
-
+        # TODO: KeyStone V3 onwards will support directly listing regions
+        # but for now, this convoluted method is necessary
         regions = (
         regions = (
             endpoint.get('region') or endpoint.get('region_id')
             endpoint.get('region') or endpoint.get('region_id')
             for svc in self.provider.keystone.service_catalog.get_data()
             for svc in self.provider.keystone.service_catalog.get_data()

+ 23 - 0
test/test_region_service.py

@@ -1,5 +1,6 @@
 from cloudbridge.cloud.interfaces import Region
 from cloudbridge.cloud.interfaces import Region
 from test.helpers import ProviderTestBase
 from test.helpers import ProviderTestBase
+import test.helpers as helpers
 
 
 
 
 class CloudRegionServiceTestCase(ProviderTestBase):
 class CloudRegionServiceTestCase(ProviderTestBase):
@@ -51,3 +52,25 @@ class CloudRegionServiceTestCase(ProviderTestBase):
         regions = self.provider.compute.regions.list()
         regions = self.provider.compute.regions.list()
         unique_regions = set([region.id for region in regions])
         unique_regions = set([region.id for region in regions])
         self.assertTrue(len(regions) == len(list(unique_regions)))
         self.assertTrue(len(regions) == len(list(unique_regions)))
+
+    def test_zones(self):
+        """
+        Test whether regions return the correct zone information
+        """
+        zone_find_count = 0
+        test_zone = helpers.get_provider_test_data(self.provider, "placement")
+        regions = self.provider.compute.regions.list()
+        for region in regions:
+            self.assertTrue(region.name)
+            for zone in region.zones:
+                self.assertTrue(zone.id)
+                self.assertTrue(zone.name)
+                self.assertTrue(zone.region)
+                if test_zone == zone.name:
+                    zone_find_count += 1
+        # TODO: Can't do a check for zone_find_count == 1 because Moto
+        # always returns the same zone for any region
+        self.assertTrue(zone_find_count > 0,
+                        "The test zone: {0} should appear exactly"
+                        " once in the list of regions, but was not found"
+                        .format(test_zone, zone_find_count))