Explorar el Código

Get or create a GCE default subnet so it respects placement zone

Enis Afgan hace 7 años
padre
commit
cac5667b8f
Se han modificado 1 ficheros con 52 adiciones y 19 borrados
  1. 52 19
      cloudbridge/cloud/providers/gce/services.py

+ 52 - 19
cloudbridge/cloud/providers/gce/services.py

@@ -1,3 +1,4 @@
+import ipaddress
 import json
 import logging
 import time
@@ -749,7 +750,7 @@ class GCESubnetService(BaseSubnetService):
             filter = 'network eq %s' % network.resource_url
         region_names = []
         if zone:
-            region_names.append(self._zone_to_region_name(zone))
+            region_names.append(self._zone_to_region(zone))
         else:
             for r in self.provider.compute.regions:
                 region_names.append(r.name)
@@ -779,7 +780,7 @@ class GCESubnetService(BaseSubnetService):
         """
         GCESubnet.assert_valid_resource_label(label)
         name = GCESubnet._generate_name_from_label(label, 'cbsubnet')
-        region_name = self._zone_to_region_name(zone)
+        region_name = self._zone_to_region(zone)
 #         for subnet in self.iter(network=network):
 #            if BaseNetwork.cidr_blocks_overlap(subnet.cidr_block, cidr_block):
 #                 if subnet.region_name != region_name:
@@ -811,20 +812,42 @@ class GCESubnetService(BaseSubnetService):
 
     def get_or_create_default(self, zone):
         """
-        Every GCP project comes with a default auto mode VPC network. An auto
-        mode VPC network has exactly one subnetwork per region. This method
-        returns the subnetwork of the default network that spans the given
-        zone.
+        Return an existing or create a new subnet for the supplied 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.
         """
-        sn = self.find(label=GCESubnet.CB_DEFAULT_SUBNET_LABEL)
-        if sn:
-            return sn[0]
-        # No default subnet look for default network, then create subnet
+        # 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)
+        # Check if a default subnet already exists for the given region/zone
+        for sn in self.find(label=GCESubnet.CB_DEFAULT_SUBNET_LABEL):
+            if sn.region == region.id:
+                return sn
+        # No default subnet in the supplied 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
+        # does the calculations (e.g., 10.0.0.0/24, 10.0.1.0/24).
+        cidr_block = GCESubnet.CB_DEFAULT_SUBNET_IPV4RANGE
         net = self.provider.networking.networks.get_or_create_default()
+        if net.subnets:
+            max_sn = net.subnets[0]
+            # Find the maximum address subnet address space within the network
+            for esn in net.subnets:
+                if (ipaddress.ip_network(esn.cidr_block) >
+                        ipaddress.ip_network(max_sn.cidr_block)):
+                    max_sn = esn
+            max_sn_ipa = ipaddress.ip_network(max_sn.cidr_block)
+            # Find the next available subnet after the max one, based on the
+            # max subnet size
+            next_sn_address = (
+                next(max_sn_ipa.hosts()) + max_sn_ipa.num_addresses - 1)
+            cidr_block = "{}/{}".format(next_sn_address, max_sn_ipa.prefixlen)
         sn = self.provider.networking.subnets.create(
                 label=GCESubnet.CB_DEFAULT_SUBNET_LABEL,
-                cidr_block=GCESubnet.CB_DEFAULT_SUBNET_IPV4RANGE,
-                network=net, zone=zone)
+                cidr_block=cidr_block, network=net, zone=zone)
         router = self.provider.networking.routers.get_or_create_default(net)
         router.attach_subnet(sn)
         gateway = net.gateways.get_or_create_inet_gateway()
@@ -846,14 +869,24 @@ class GCESubnetService(BaseSubnetService):
             log.warning('No label was found associated with this subnet '
                         '"{}" when deleted.'.format(subnet.name))
 
-    def _zone_to_region_name(self, zone):
+    def _zone_to_region(self, zone, return_name_only=True):
+        """
+        Given a GCE zone, return parent region.
+
+        Supplied `zone` param can be a `str` or `GCEPlacementZone`.
+        
+        If ``return_name_only`` is set, return the region name as a string;
+        otherwise, return a GCERegion object.
+        """
+        region_name = self.provider.region_name
         if zone:
-            if not isinstance(zone, GCEPlacementZone):
-                zone = GCEPlacementZone(
-                    self.provider,
-                    self.provider.get_resource('zones', zone))
-            return zone.region_name
-        return self.provider.region_name
+            if isinstance(zone, GCEPlacementZone):
+                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):