|
@@ -1,3 +1,4 @@
|
|
|
|
|
+import ipaddress
|
|
|
import json
|
|
import json
|
|
|
import logging
|
|
import logging
|
|
|
import time
|
|
import time
|
|
@@ -446,6 +447,32 @@ class GCEInstanceService(BaseInstanceService):
|
|
|
config['tags'] = {}
|
|
config['tags'] = {}
|
|
|
config['tags']['items'] = vm_firewall_names
|
|
config['tags']['items'] = vm_firewall_names
|
|
|
|
|
|
|
|
|
|
+ if user_data:
|
|
|
|
|
+ entry = {'key': 'user-data', 'value': user_data}
|
|
|
|
|
+ config['metadata'] = {'items': [entry]}
|
|
|
|
|
+
|
|
|
|
|
+ if key_pair:
|
|
|
|
|
+ if not isinstance(key_pair, GCEKeyPair):
|
|
|
|
|
+ key_pair = self._provider.security.key_pairs.get(key_pair)
|
|
|
|
|
+ if key_pair:
|
|
|
|
|
+ kp = key_pair._key_pair
|
|
|
|
|
+ kp_entry = {
|
|
|
|
|
+ "key": "ssh-keys",
|
|
|
|
|
+ # Format is not removed from public key portion
|
|
|
|
|
+ "value": "{}:{} {}".format(
|
|
|
|
|
+ self.provider.vm_default_user_name,
|
|
|
|
|
+ kp.public_key,
|
|
|
|
|
+ kp.name)
|
|
|
|
|
+ }
|
|
|
|
|
+ meta = config.get('metadata', {})
|
|
|
|
|
+ if meta:
|
|
|
|
|
+ items = meta.get('items', [])
|
|
|
|
|
+ items.append(kp_entry)
|
|
|
|
|
+ else:
|
|
|
|
|
+ config['metadata'] = {'items': [kp_entry]}
|
|
|
|
|
+
|
|
|
|
|
+ config['labels'] = {'cblabel': label}
|
|
|
|
|
+
|
|
|
operation = (self.provider
|
|
operation = (self.provider
|
|
|
.gce_compute.instances()
|
|
.gce_compute.instances()
|
|
|
.insert(project=self.provider.project_name,
|
|
.insert(project=self.provider.project_name,
|
|
@@ -455,9 +482,6 @@ class GCEInstanceService(BaseInstanceService):
|
|
|
instance_id = operation.get('targetLink')
|
|
instance_id = operation.get('targetLink')
|
|
|
self.provider.wait_for_operation(operation, zone=zone_name)
|
|
self.provider.wait_for_operation(operation, zone=zone_name)
|
|
|
cb_inst = self.get(instance_id)
|
|
cb_inst = self.get(instance_id)
|
|
|
- cb_inst.label = label
|
|
|
|
|
- if key_pair:
|
|
|
|
|
- cb_inst.key_pair_id = key_pair
|
|
|
|
|
return cb_inst
|
|
return cb_inst
|
|
|
|
|
|
|
|
def get(self, instance_id):
|
|
def get(self, instance_id):
|
|
@@ -749,7 +773,7 @@ class GCESubnetService(BaseSubnetService):
|
|
|
filter = 'network eq %s' % network.resource_url
|
|
filter = 'network eq %s' % network.resource_url
|
|
|
region_names = []
|
|
region_names = []
|
|
|
if zone:
|
|
if zone:
|
|
|
- region_names.append(self._zone_to_region_name(zone))
|
|
|
|
|
|
|
+ region_names.append(self._zone_to_region(zone))
|
|
|
else:
|
|
else:
|
|
|
for r in self.provider.compute.regions:
|
|
for r in self.provider.compute.regions:
|
|
|
region_names.append(r.name)
|
|
region_names.append(r.name)
|
|
@@ -779,7 +803,7 @@ class GCESubnetService(BaseSubnetService):
|
|
|
"""
|
|
"""
|
|
|
GCESubnet.assert_valid_resource_label(label)
|
|
GCESubnet.assert_valid_resource_label(label)
|
|
|
name = GCESubnet._generate_name_from_label(label, 'cbsubnet')
|
|
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):
|
|
# for subnet in self.iter(network=network):
|
|
|
# if BaseNetwork.cidr_blocks_overlap(subnet.cidr_block, cidr_block):
|
|
# if BaseNetwork.cidr_blocks_overlap(subnet.cidr_block, cidr_block):
|
|
|
# if subnet.region_name != region_name:
|
|
# if subnet.region_name != region_name:
|
|
@@ -811,20 +835,42 @@ class GCESubnetService(BaseSubnetService):
|
|
|
|
|
|
|
|
def get_or_create_default(self, zone):
|
|
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()
|
|
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(
|
|
sn = self.provider.networking.subnets.create(
|
|
|
label=GCESubnet.CB_DEFAULT_SUBNET_LABEL,
|
|
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 = self.provider.networking.routers.get_or_create_default(net)
|
|
|
router.attach_subnet(sn)
|
|
router.attach_subnet(sn)
|
|
|
gateway = net.gateways.get_or_create_inet_gateway()
|
|
gateway = net.gateways.get_or_create_inet_gateway()
|
|
@@ -846,14 +892,24 @@ class GCESubnetService(BaseSubnetService):
|
|
|
log.warning('No label was found associated with this subnet '
|
|
log.warning('No label was found associated with this subnet '
|
|
|
'"{}" when deleted.'.format(subnet.name))
|
|
'"{}" 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 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):
|
|
class GCPStorageService(BaseStorageService):
|
|
@@ -973,6 +1029,7 @@ class GCEVolumeService(BaseVolumeService):
|
|
|
'type': 'zones/{0}/diskTypes/{1}'.format(zone_name, 'pd-standard'),
|
|
'type': 'zones/{0}/diskTypes/{1}'.format(zone_name, 'pd-standard'),
|
|
|
'sourceSnapshot': snapshot_id,
|
|
'sourceSnapshot': snapshot_id,
|
|
|
'description': description,
|
|
'description': description,
|
|
|
|
|
+ 'labels': {'cblabel': label}
|
|
|
}
|
|
}
|
|
|
operation = (self.provider
|
|
operation = (self.provider
|
|
|
.gce_compute
|
|
.gce_compute
|
|
@@ -983,7 +1040,6 @@ class GCEVolumeService(BaseVolumeService):
|
|
|
body=disk_body)
|
|
body=disk_body)
|
|
|
.execute())
|
|
.execute())
|
|
|
cb_vol = self.get(operation.get('targetLink'))
|
|
cb_vol = self.get(operation.get('targetLink'))
|
|
|
- cb_vol.label = label
|
|
|
|
|
return cb_vol
|
|
return cb_vol
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1052,7 +1108,8 @@ class GCESnapshotService(BaseSnapshotService):
|
|
|
volume_name = volume.name if isinstance(volume, GCEVolume) else volume
|
|
volume_name = volume.name if isinstance(volume, GCEVolume) else volume
|
|
|
snapshot_body = {
|
|
snapshot_body = {
|
|
|
"name": name,
|
|
"name": name,
|
|
|
- "description": description
|
|
|
|
|
|
|
+ "description": description,
|
|
|
|
|
+ "labels": {'cblabel': label}
|
|
|
}
|
|
}
|
|
|
operation = (self.provider
|
|
operation = (self.provider
|
|
|
.gce_compute
|
|
.gce_compute
|
|
@@ -1067,7 +1124,6 @@ class GCESnapshotService(BaseSnapshotService):
|
|
|
self.provider.wait_for_operation(operation,
|
|
self.provider.wait_for_operation(operation,
|
|
|
zone=self.provider.default_zone)
|
|
zone=self.provider.default_zone)
|
|
|
cb_snap = self.get(name)
|
|
cb_snap = self.get(name)
|
|
|
- cb_snap.label = label
|
|
|
|
|
return cb_snap
|
|
return cb_snap
|
|
|
|
|
|
|
|
|
|
|