Merge "Azure: Handle IPv6"

This commit is contained in:
Zuul 2021-06-15 02:23:24 +00:00 committed by Gerrit Code Review
commit 8ee837670e
3 changed files with 156 additions and 61 deletions

View File

@ -24,10 +24,11 @@ from . import azul
class AzureInstance(statemachine.Instance): class AzureInstance(statemachine.Instance):
def __init__(self, vm, nic=None, pip=None): def __init__(self, vm, nic=None, pip4=None, pip6=None):
self.external_id = vm['name'] self.external_id = vm['name']
self.metadata = vm['tags'] or {} self.metadata = vm['tags'] or {}
self.private_ipv4 = None self.private_ipv4 = None
self.private_ipv6 = None
self.public_ipv4 = None self.public_ipv4 = None
self.public_ipv6 = None self.public_ipv6 = None
@ -36,12 +37,17 @@ class AzureInstance(statemachine.Instance):
ip_config_prop = ip_config_data['properties'] ip_config_prop = ip_config_data['properties']
if ip_config_prop['privateIPAddressVersion'] == 'IPv4': if ip_config_prop['privateIPAddressVersion'] == 'IPv4':
self.private_ipv4 = ip_config_prop['privateIPAddress'] self.private_ipv4 = ip_config_prop['privateIPAddress']
if ip_config_prop['privateIPAddressVersion'] == 'IPv6':
self.private_ipv6 = ip_config_prop['privateIPAddress']
# public_ipv6 # public_ipv6
if pip: if pip4:
self.public_ipv4 = pip['properties'].get('ipAddress') self.public_ipv4 = pip4['properties'].get('ipAddress')
if pip6:
self.public_ipv6 = pip6['properties'].get('ipAddress')
self.interface_ip = self.public_ipv4 or self.private_ipv4 self.interface_ip = (self.public_ipv4 or self.public_ipv6 or
self.private_ipv4 or self.private_ipv6)
self.region = vm['location'] self.region = vm['location']
self.az = '' self.az = ''
@ -58,6 +64,9 @@ class AzureDeleteStateMachine(statemachine.StateMachine):
self.adapter = adapter self.adapter = adapter
self.external_id = external_id self.external_id = external_id
self.disk_names = [] self.disk_names = []
self.disks = []
self.pip4 = None
self.pip6 = None
def advance(self): def advance(self):
if self.state == self.START: if self.state == self.START:
@ -78,13 +87,16 @@ class AzureDeleteStateMachine(statemachine.StateMachine):
if self.state == self.NIC_DELETING: if self.state == self.NIC_DELETING:
self.nic = self.adapter._refresh_delete(self.nic) self.nic = self.adapter._refresh_delete(self.nic)
if self.nic is None: if self.nic is None:
self.pip = self.adapter._deletePublicIPAddress( self.pip4 = self.adapter._deletePublicIPAddress(
self.external_id + '-nic-pip') self.external_id + '-pip-IPv4')
self.pip6 = self.adapter._deletePublicIPAddress(
self.external_id + '-pip-IPv6')
self.state = self.PIP_DELETING self.state = self.PIP_DELETING
if self.state == self.PIP_DELETING: if self.state == self.PIP_DELETING:
self.pip = self.adapter._refresh_delete(self.pip) self.pip4 = self.adapter._refresh_delete(self.pip4)
if self.pip is None: self.pip6 = self.adapter._refresh_delete(self.pip6)
if self.pip4 is None and self.pip6 is None:
self.disks = [] self.disks = []
for name in self.disk_names: for name in self.disk_names:
disk = self.adapter._deleteDisk(name) disk = self.adapter._deleteDisk(name)
@ -119,25 +131,56 @@ class AzureCreateStateMachine(statemachine.StateMachine):
self.tags.update(metadata) self.tags.update(metadata)
self.hostname = hostname self.hostname = hostname
self.label = label self.label = label
self.pip = None self.pip4 = None
self.pip6 = None
self.nic = None self.nic = None
self.vm = None self.vm = None
# There are two parameters for IP addresses: SKU and
# allocation method. SKU is "basic" or "standard".
# Allocation method is "static" or "dynamic". Between IPv4
# and v6, SKUs cannot be mixed (the same sku must be used for
# both protocols). The standard SKU only supports static
# allocation. Static is cheaper than dynamic, but basic is
# cheaper than standard. Also, dynamic is faster than static.
# Therefore, if IPv6 is used at all, standard+static for
# everything; otherwise basic+dynamic in an IPv4-only
# situation.
if label.pool.ipv6:
self.ip_sku = 'Standard'
self.ip_method = 'static'
else:
self.ip_sku = 'Basic'
self.ip_method = 'dynamic'
def advance(self): def advance(self):
if self.state == self.START: if self.state == self.START:
self.pip = self.adapter._createPublicIPAddress(
self.tags, self.hostname)
self.state = self.PIP_CREATING
self.external_id = self.hostname self.external_id = self.hostname
if self.label.pool.public_ipv4:
self.pip4 = self.adapter._createPublicIPAddress(
self.tags, self.hostname, self.ip_sku, 'IPv4',
self.ip_method)
if self.label.pool.public_ipv6:
self.pip6 = self.adapter._createPublicIPAddress(
self.tags, self.hostname, self.ip_sku, 'IPv6',
self.ip_method)
self.state = self.PIP_CREATING
if self.state == self.PIP_CREATING: if self.state == self.PIP_CREATING:
self.pip = self.adapter._refresh(self.pip) if self.pip4:
if self.adapter._succeeded(self.pip): self.pip4 = self.adapter._refresh(self.pip4)
self.nic = self.adapter._createNetworkInterface( if not self.adapter._succeeded(self.pip4):
self.tags, self.hostname, self.pip)
self.state = self.NIC_CREATING
else:
return return
if self.pip6:
self.pip6 = self.adapter._refresh(self.pip6)
if not self.adapter._succeeded(self.pip6):
return
# At this point, every pip we have has succeeded (we may
# have 0, 1, or 2).
self.nic = self.adapter._createNetworkInterface(
self.tags, self.hostname,
self.label.pool.ipv4, self.label.pool.ipv6,
self.pip4, self.pip6)
self.state = self.NIC_CREATING
if self.state == self.NIC_CREATING: if self.state == self.NIC_CREATING:
self.nic = self.adapter._refresh(self.nic) self.nic = self.adapter._refresh(self.nic)
@ -163,20 +206,30 @@ class AzureCreateStateMachine(statemachine.StateMachine):
if self.state == self.NIC_QUERY: if self.state == self.NIC_QUERY:
self.nic = self.adapter._refresh(self.nic, force=True) self.nic = self.adapter._refresh(self.nic, force=True)
all_found = True
for ip_config_data in self.nic['properties']['ipConfigurations']: for ip_config_data in self.nic['properties']['ipConfigurations']:
ip_config_prop = ip_config_data['properties'] ip_config_prop = ip_config_data['properties']
if ip_config_prop['privateIPAddressVersion'] == 'IPv4': if 'privateIPAddress' not in ip_config_prop:
if 'privateIPAddress' in ip_config_prop: all_found = False
if all_found:
self.state = self.PIP_QUERY self.state = self.PIP_QUERY
if self.state == self.PIP_QUERY: if self.state == self.PIP_QUERY:
self.pip = self.adapter._refresh(self.pip, force=True) all_found = True
if 'ipAddress' in self.pip['properties']: if self.pip4:
self.pip4 = self.adapter._refresh(self.pip4, force=True)
if 'ipAddress' not in self.pip4['properties']:
all_found = False
if self.pip6:
self.pip6 = self.adapter._refresh(self.pip6, force=True)
if 'ipAddress' not in self.pip6['properties']:
all_found = False
if all_found:
self.state = self.COMPLETE self.state = self.COMPLETE
if self.state == self.COMPLETE: if self.state == self.COMPLETE:
self.complete = True self.complete = True
return AzureInstance(self.vm, self.nic, self.pip) return AzureInstance(self.vm, self.nic, self.pip4, self.pip6)
class AzureAdapter(statemachine.Adapter): class AzureAdapter(statemachine.Adapter):
@ -283,17 +336,22 @@ class AzureAdapter(statemachine.Adapter):
def _listPublicIPAddresses(self): def _listPublicIPAddresses(self):
return self.azul.public_ip_addresses.list(self.resource_group) return self.azul.public_ip_addresses.list(self.resource_group)
def _createPublicIPAddress(self, tags, hostname): def _createPublicIPAddress(self, tags, hostname, sku, version,
allocation_method):
v4_params_create = { v4_params_create = {
'location': self.provider.location, 'location': self.provider.location,
'tags': tags, 'tags': tags,
'sku': {
'name': sku,
},
'properties': { 'properties': {
'publicIpAllocationMethod': 'dynamic', 'publicIpAddressVersion': version,
'publicIpAllocationMethod': allocation_method,
}, },
} }
return self.azul.public_ip_addresses.create( return self.azul.public_ip_addresses.create(
self.resource_group, self.resource_group,
"%s-nic-pip" % hostname, "%s-pip-%s" % (hostname, version),
v4_params_create, v4_params_create,
) )
@ -310,36 +368,42 @@ class AzureAdapter(statemachine.Adapter):
def _listNetworkInterfaces(self): def _listNetworkInterfaces(self):
return self.azul.network_interfaces.list(self.resource_group) return self.azul.network_interfaces.list(self.resource_group)
def _createNetworkInterface(self, tags, hostname, pip): def _createNetworkInterface(self, tags, hostname, ipv4, ipv6, pip4, pip6):
def make_ip_config(name, version, subnet_id, pip):
ip_config = {
'name': name,
'properties': {
'privateIpAddressVersion': version,
'subnet': {
'id': subnet_id
},
}
}
if pip:
ip_config['properties']['publicIpAddress'] = {
'id': pip['id']
}
return ip_config
ip_configs = []
if ipv4:
ip_configs.append(make_ip_config('nodepool-v4-ip-config',
'IPv4', self.provider.subnet_id,
pip4))
if ipv6:
ip_configs.append(make_ip_config('nodepool-v6-ip-config',
'IPv6', self.provider.subnet_id,
pip6))
nic_data = { nic_data = {
'location': self.provider.location, 'location': self.provider.location,
'tags': tags, 'tags': tags,
'properties': { 'properties': {
'ipConfigurations': [{ 'ipConfigurations': ip_configs
'name': "nodepool-v4-ip-config",
'properties': {
'privateIpAddressVersion': 'IPv4',
'subnet': {
'id': self.provider.subnet_id
},
'publicIpAddress': {
'id': pip['id']
} }
} }
}]
}
}
if self.provider.ipv6:
nic_data['properties']['ipConfigurations'].append({
'name': "nodepool-v6-ip-config",
'properties': {
'privateIpAddressVersion': 'IPv6',
'subnet': {
'id': self.provider.subnet_id
}
}
})
return self.azul.network_interfaces.create( return self.azul.network_interfaces.create(
self.resource_group, self.resource_group,

View File

@ -114,9 +114,20 @@ class AzurePool(ConfigPool):
def load(self, pool_config): def load(self, pool_config):
self.name = pool_config['name'] self.name = pool_config['name']
self.max_servers = pool_config['max-servers'] self.max_servers = pool_config['max-servers']
self.use_internal_ip = bool(pool_config.get('use-internal-ip', False)) self.public_ipv4 = pool_config.get('public-ipv4',
self.host_key_checking = bool(pool_config.get( self.provider.public_ipv4)
'host-key-checking', True)) self.public_ipv6 = pool_config.get('public-ipv6',
self.provider.public_ipv6)
self.ipv4 = pool_config.get('ipv4', self.provider.ipv4)
self.ipv6 = pool_config.get('ipv6', self.provider.ipv6)
self.ipv4 = self.ipv4 or self.public_ipv4
self.ipv6 = self.ipv6 or self.public_ipv6
if not self.ipv4 or self.ipv6:
self.ipv4 = True
self.use_internal_ip = pool_config.get(
'use-internal-ip', self.provider.use_internal_ip)
self.host_key_checking = pool_config.get(
'host-key-checking', self.provider.use_internal_ip)
@staticmethod @staticmethod
def getSchema(): def getSchema():
@ -126,6 +137,12 @@ class AzurePool(ConfigPool):
pool.update({ pool.update({
v.Required('name'): str, v.Required('name'): str,
v.Required('labels'): [azure_label], v.Required('labels'): [azure_label],
'ipv4': bool,
'ipv6': bool,
'public-ipv4': bool,
'public-ipv6': bool,
'use-internal-ip': bool,
'host-key-checking': bool,
}) })
return pool return pool
@ -157,8 +174,15 @@ class AzureProviderConfig(ProviderConfig):
# TODO(corvus): remove # TODO(corvus): remove
self.zuul_public_key = self.provider['zuul-public-key'] self.zuul_public_key = self.provider['zuul-public-key']
self.location = self.provider['location'] self.location = self.provider['location']
self.subnet_id = self.provider['subnet-id'] self.subnet_id = self.provider.get('subnet-id')
self.ipv6 = self.provider.get('ipv6', False) # Don't use these directly; these are default values for
# labels.
self.public_ipv4 = self.provider.get('public-ipv4', False)
self.public_ipv6 = self.provider.get('public-ipv6', False)
self.ipv4 = self.provider.get('ipv4', None)
self.ipv6 = self.provider.get('ipv6', None)
self.use_internal_ip = self.provider.get('use-internal-ip', False)
self.host_key_checking = self.provider.get('host-key-checking', True)
self.resource_group = self.provider['resource-group'] self.resource_group = self.provider['resource-group']
self.resource_group_location = self.provider['resource-group-location'] self.resource_group_location = self.provider['resource-group-location']
self.auth_path = self.provider.get( self.auth_path = self.provider.get(
@ -192,6 +216,12 @@ class AzureProviderConfig(ProviderConfig):
v.Required('subnet-id'): str, v.Required('subnet-id'): str,
v.Required('cloud-images'): [provider_cloud_images], v.Required('cloud-images'): [provider_cloud_images],
v.Required('auth-path'): str, v.Required('auth-path'): str,
'ipv4': bool,
'ipv6': bool,
'public-ipv4': bool,
'public-ipv6': bool,
'use-internal-ip': bool,
'host-key-checking': bool,
}) })
return v.Schema(provider) return v.Schema(provider)

View File

@ -103,8 +103,9 @@ class StateMachineNodeLauncher(stats.StatsReporter):
pool = self.handler.pool pool = self.handler.pool
label = pool.labels[self.node.type[0]] label = pool.labels[self.node.type[0]]
if pool.use_internal_ip and instance.private_ipv4: if (pool.use_internal_ip and
server_ip = instance.private_ipv4 (instance.private_ipv4 or instance.private_ipv6)):
server_ip = instance.private_ipv4 or instance.private_ipv6
else: else:
server_ip = instance.interface_ip server_ip = instance.interface_ip
@ -160,8 +161,9 @@ class StateMachineNodeLauncher(stats.StatsReporter):
node.external_id = state_machine.external_id node.external_id = state_machine.external_id
self.zk.storeNode(node) self.zk.storeNode(node)
if state_machine.complete and not self.keyscan_future: if state_machine.complete and not self.keyscan_future:
self.log.debug("Submitting keyscan request")
self.updateNodeFromInstance(instance) self.updateNodeFromInstance(instance)
self.log.debug("Submitting keyscan request for %s",
node.interface_ip)
future = self.manager.keyscan_worker.submit( future = self.manager.keyscan_worker.submit(
keyscan, keyscan,
node.id, node.interface_ip, node.id, node.interface_ip,
@ -427,7 +429,6 @@ class StateMachineProvider(Provider, QuotaSupport):
def stop(self): def stop(self):
self.log.debug("Stopping") self.log.debug("Stopping")
self.running = False
if self.state_machine_thread: if self.state_machine_thread:
while self.launchers or self.deleters: while self.launchers or self.deleters:
time.sleep(1) time.sleep(1)