Update Azure API and add volume-size
The addition of the volume-size attribute necessitates an upgrade to the Azure API. The newer version of the API allows us to remove the individual handling we have for NICs, PIPs, and Disks. That simplifies the driver greatly, but comes with some caveats that are noted in the docs and release notes. Finally, the volume-size attribute is added as well. Change-Id: I6e335318cedbf0ac8944107aff9d1a2cfcab271a
This commit is contained in:
parent
4279b4766d
commit
77d9512764
@ -14,6 +14,12 @@ the instructions at `Azure CLI`_ and use the ``--sdk-auth`` flag::
|
|||||||
You must also have created a network for Nodepool to use. Be sure to
|
You must also have created a network for Nodepool to use. Be sure to
|
||||||
enable IPv6 on the network if you plan to use it.
|
enable IPv6 on the network if you plan to use it.
|
||||||
|
|
||||||
|
The Azure driver now uses "Standard" SKU for all public IP addresses.
|
||||||
|
Standard IP addresses block all incoming traffic by default, therefore
|
||||||
|
the use of a Network Security Group is required in order to allow
|
||||||
|
incoming traffic. You will need to create one, add any required
|
||||||
|
rules, and attach it to the subnet created above.
|
||||||
|
|
||||||
You may also need to register the `Microsoft.Compute` resource
|
You may also need to register the `Microsoft.Compute` resource
|
||||||
provider with your subscription.
|
provider with your subscription.
|
||||||
|
|
||||||
@ -735,6 +741,11 @@ section of the configuration.
|
|||||||
|
|
||||||
The `Azure Custom Data`_ value for newly created VMs.
|
The `Azure Custom Data`_ value for newly created VMs.
|
||||||
|
|
||||||
|
.. attr:: volume-size
|
||||||
|
:type: int
|
||||||
|
|
||||||
|
If given, the size of the operating system disk, in GiB.
|
||||||
|
|
||||||
|
|
||||||
.. _`Azure CLI`: https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest
|
.. _`Azure CLI`: https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright 2021 Acme Gating, LLC
|
# Copyright 2021, 2023 Acme Gating, LLC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
# not use this file except in compliance with the License. You may obtain
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@ -76,7 +76,6 @@ class AzureInstance(statemachine.Instance):
|
|||||||
self.private_ipv4 = ip_config_prop['privateIPAddress']
|
self.private_ipv4 = ip_config_prop['privateIPAddress']
|
||||||
if ip_config_prop['privateIPAddressVersion'] == 'IPv6':
|
if ip_config_prop['privateIPAddressVersion'] == 'IPv6':
|
||||||
self.private_ipv6 = ip_config_prop['privateIPAddress']
|
self.private_ipv6 = ip_config_prop['privateIPAddress']
|
||||||
# public_ipv6
|
|
||||||
|
|
||||||
if public_ipv4:
|
if public_ipv4:
|
||||||
self.public_ipv4 = public_ipv4['properties'].get('ipAddress')
|
self.public_ipv4 = public_ipv4['properties'].get('ipAddress')
|
||||||
@ -110,69 +109,27 @@ class AzureResource(statemachine.Resource):
|
|||||||
|
|
||||||
class AzureDeleteStateMachine(statemachine.StateMachine):
|
class AzureDeleteStateMachine(statemachine.StateMachine):
|
||||||
VM_DELETING = 'deleting vm'
|
VM_DELETING = 'deleting vm'
|
||||||
NIC_DELETING = 'deleting nic'
|
|
||||||
PIP_DELETING = 'deleting pip'
|
|
||||||
DISK_DELETING = 'deleting disk'
|
|
||||||
COMPLETE = 'complete'
|
COMPLETE = 'complete'
|
||||||
|
|
||||||
def __init__(self, adapter, external_id):
|
def __init__(self, adapter, external_id):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.adapter = adapter
|
self.adapter = adapter
|
||||||
self.external_id = external_id
|
self.external_id = external_id
|
||||||
self.disk_names = []
|
|
||||||
self.disks = []
|
|
||||||
self.public_ipv4 = None
|
|
||||||
self.public_ipv6 = None
|
|
||||||
|
|
||||||
def advance(self):
|
def advance(self):
|
||||||
if self.state == self.START:
|
if self.state == self.START:
|
||||||
self.vm = self.adapter._deleteVirtualMachine(
|
self.vm = self.adapter._deleteVirtualMachine(
|
||||||
self.external_id)
|
self.external_id)
|
||||||
if self.vm:
|
|
||||||
self.disk_names.append(
|
|
||||||
self.vm['properties']['storageProfile']['osDisk']['name'])
|
|
||||||
self.state = self.VM_DELETING
|
self.state = self.VM_DELETING
|
||||||
|
|
||||||
if self.state == self.VM_DELETING:
|
if self.state == self.VM_DELETING:
|
||||||
self.vm = self.adapter._refresh_delete(self.vm)
|
self.vm = self.adapter._refresh_delete(self.vm)
|
||||||
if self.vm is None:
|
if self.vm is None:
|
||||||
self.nic = self.adapter._deleteNetworkInterface(
|
|
||||||
self.external_id + '-nic')
|
|
||||||
self.state = self.NIC_DELETING
|
|
||||||
|
|
||||||
if self.state == self.NIC_DELETING:
|
|
||||||
self.nic = self.adapter._refresh_delete(self.nic)
|
|
||||||
if self.nic is None:
|
|
||||||
self.public_ipv4 = self.adapter._deletePublicIPAddress(
|
|
||||||
self.external_id + '-pip-IPv4')
|
|
||||||
self.public_ipv6 = self.adapter._deletePublicIPAddress(
|
|
||||||
self.external_id + '-pip-IPv6')
|
|
||||||
self.state = self.PIP_DELETING
|
|
||||||
|
|
||||||
if self.state == self.PIP_DELETING:
|
|
||||||
self.public_ipv4 = self.adapter._refresh_delete(self.public_ipv4)
|
|
||||||
self.public_ipv6 = self.adapter._refresh_delete(self.public_ipv6)
|
|
||||||
if self.public_ipv4 is None and self.public_ipv6 is None:
|
|
||||||
self.disks = []
|
|
||||||
for name in self.disk_names:
|
|
||||||
disk = self.adapter._deleteDisk(name)
|
|
||||||
self.disks.append(disk)
|
|
||||||
self.state = self.DISK_DELETING
|
|
||||||
|
|
||||||
if self.state == self.DISK_DELETING:
|
|
||||||
all_deleted = True
|
|
||||||
for disk in self.disks:
|
|
||||||
disk = self.adapter._refresh_delete(disk)
|
|
||||||
if disk:
|
|
||||||
all_deleted = False
|
|
||||||
if all_deleted:
|
|
||||||
self.state = self.COMPLETE
|
self.state = self.COMPLETE
|
||||||
self.complete = True
|
self.complete = True
|
||||||
|
|
||||||
|
|
||||||
class AzureCreateStateMachine(statemachine.StateMachine):
|
class AzureCreateStateMachine(statemachine.StateMachine):
|
||||||
PIP_CREATING = 'creating pip'
|
|
||||||
NIC_CREATING = 'creating nic'
|
|
||||||
VM_CREATING = 'creating vm'
|
VM_CREATING = 'creating vm'
|
||||||
NIC_QUERY = 'querying nic'
|
NIC_QUERY = 'querying nic'
|
||||||
PIP_QUERY = 'querying pip'
|
PIP_QUERY = 'querying pip'
|
||||||
@ -200,22 +157,6 @@ class AzureCreateStateMachine(statemachine.StateMachine):
|
|||||||
self.public_ipv6 = None
|
self.public_ipv6 = 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:
|
||||||
@ -226,47 +167,18 @@ class AzureCreateStateMachine(statemachine.StateMachine):
|
|||||||
self.image_reference = self.adapter._getImageFromFilter(
|
self.image_reference = self.adapter._getImageFromFilter(
|
||||||
self.label.cloud_image.image_filter)
|
self.label.cloud_image.image_filter)
|
||||||
|
|
||||||
if self.label.pool.public_ipv4:
|
self.vm = self.adapter._createVirtualMachine(
|
||||||
self.public_ipv4 = self.adapter._createPublicIPAddress(
|
self.label, self.image_external_id,
|
||||||
self.tags, self.hostname, self.ip_sku, 'IPv4',
|
self.image_reference, self.tags, self.hostname,
|
||||||
self.ip_method)
|
self.nic)
|
||||||
if self.label.pool.public_ipv6:
|
self.state = self.VM_CREATING
|
||||||
self.public_ipv6 = 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.public_ipv4:
|
|
||||||
self.public_ipv4 = self.adapter._refresh(self.public_ipv4)
|
|
||||||
if not self.adapter._succeeded(self.public_ipv4):
|
|
||||||
return
|
|
||||||
if self.public_ipv6:
|
|
||||||
self.public_ipv6 = self.adapter._refresh(self.public_ipv6)
|
|
||||||
if not self.adapter._succeeded(self.public_ipv6):
|
|
||||||
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.public_ipv4, self.public_ipv6)
|
|
||||||
self.state = self.NIC_CREATING
|
|
||||||
|
|
||||||
if self.state == self.NIC_CREATING:
|
|
||||||
self.nic = self.adapter._refresh(self.nic)
|
|
||||||
if self.adapter._succeeded(self.nic):
|
|
||||||
self.vm = self.adapter._createVirtualMachine(
|
|
||||||
self.label, self.image_external_id,
|
|
||||||
self.image_reference, self.tags, self.hostname,
|
|
||||||
self.nic)
|
|
||||||
self.state = self.VM_CREATING
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.state == self.VM_CREATING:
|
if self.state == self.VM_CREATING:
|
||||||
self.vm = self.adapter._refresh(self.vm)
|
self.vm = self.adapter._refresh(self.vm)
|
||||||
if self.adapter._succeeded(self.vm):
|
if self.adapter._succeeded(self.vm):
|
||||||
|
self.nic = (self.vm['properties']['networkProfile']
|
||||||
|
['networkInterfaces'][0]).copy()
|
||||||
|
self.nic['type'] = 'Microsoft.Network/networkInterfaces'
|
||||||
self.state = self.NIC_QUERY
|
self.state = self.NIC_QUERY
|
||||||
elif self.adapter._failed(self.vm):
|
elif self.adapter._failed(self.vm):
|
||||||
raise exceptions.LaunchStatusException("VM in failed state")
|
raise exceptions.LaunchStatusException("VM in failed state")
|
||||||
@ -281,6 +193,15 @@ class AzureCreateStateMachine(statemachine.StateMachine):
|
|||||||
if 'privateIPAddress' not in ip_config_prop:
|
if 'privateIPAddress' not in ip_config_prop:
|
||||||
all_found = False
|
all_found = False
|
||||||
if all_found:
|
if all_found:
|
||||||
|
for ip_config in self.nic['properties']['ipConfigurations']:
|
||||||
|
if 'publicIPAddress' in ip_config['properties']:
|
||||||
|
public_ip = ip_config['properties']['publicIPAddress']
|
||||||
|
ip_version = (public_ip['properties']
|
||||||
|
['publicIPAddressVersion'])
|
||||||
|
if ip_version == 'IPv4':
|
||||||
|
self.public_ipv4 = public_ip
|
||||||
|
if ip_version == 'IPv6':
|
||||||
|
self.public_ipv6 = public_ip
|
||||||
self.state = self.PIP_QUERY
|
self.state = self.PIP_QUERY
|
||||||
|
|
||||||
if self.state == self.PIP_QUERY:
|
if self.state == self.PIP_QUERY:
|
||||||
@ -516,11 +437,11 @@ class AzureAdapter(statemachine.Adapter):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _succeeded(obj):
|
def _succeeded(obj):
|
||||||
return obj['properties']['provisioningState'] == 'Succeeded'
|
return obj['properties'].get('provisioningState') == 'Succeeded'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _failed(obj):
|
def _failed(obj):
|
||||||
return obj['properties']['provisioningState'] == 'Failed'
|
return obj['properties'].get('provisioningState') == 'Failed'
|
||||||
|
|
||||||
def _refresh(self, obj, force=False):
|
def _refresh(self, obj, force=False):
|
||||||
if self._succeeded(obj) and not force:
|
if self._succeeded(obj) and not force:
|
||||||
@ -573,101 +494,11 @@ class AzureAdapter(statemachine.Adapter):
|
|||||||
with self.rate_limiter:
|
with self.rate_limiter:
|
||||||
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, sku, version,
|
|
||||||
allocation_method):
|
|
||||||
v4_params_create = {
|
|
||||||
'location': self.provider.location,
|
|
||||||
'tags': tags,
|
|
||||||
'sku': {
|
|
||||||
'name': sku,
|
|
||||||
},
|
|
||||||
'properties': {
|
|
||||||
'publicIpAddressVersion': version,
|
|
||||||
'publicIpAllocationMethod': allocation_method,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
name = "%s-pip-%s" % (hostname, version)
|
|
||||||
with self.rate_limiter:
|
|
||||||
self.log.debug(f"Creating external IP address {name}")
|
|
||||||
return self.azul.public_ip_addresses.create(
|
|
||||||
self.resource_group,
|
|
||||||
name,
|
|
||||||
v4_params_create,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _deletePublicIPAddress(self, name):
|
|
||||||
for pip in self._listPublicIPAddresses():
|
|
||||||
if pip['name'] == name:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
with self.rate_limiter:
|
|
||||||
self.log.debug(f"Deleting external IP address {name}")
|
|
||||||
self.azul.public_ip_addresses.delete(self.resource_group, name)
|
|
||||||
return pip
|
|
||||||
|
|
||||||
@cachetools.func.ttl_cache(maxsize=1, ttl=10)
|
@cachetools.func.ttl_cache(maxsize=1, ttl=10)
|
||||||
def _listNetworkInterfaces(self):
|
def _listNetworkInterfaces(self):
|
||||||
with self.rate_limiter:
|
with self.rate_limiter:
|
||||||
return self.azul.network_interfaces.list(self.resource_group)
|
return self.azul.network_interfaces.list(self.resource_group)
|
||||||
|
|
||||||
def _createNetworkInterface(self, tags, hostname, ipv4, ipv6,
|
|
||||||
public_ipv4, public_ipv6):
|
|
||||||
|
|
||||||
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.subnet_id,
|
|
||||||
public_ipv4))
|
|
||||||
if ipv6:
|
|
||||||
ip_configs.append(make_ip_config('nodepool-v6-ip-config',
|
|
||||||
'IPv6', self.subnet_id,
|
|
||||||
public_ipv6))
|
|
||||||
|
|
||||||
nic_data = {
|
|
||||||
'location': self.provider.location,
|
|
||||||
'tags': tags,
|
|
||||||
'properties': {
|
|
||||||
'ipConfigurations': ip_configs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name = "%s-nic" % hostname
|
|
||||||
with self.rate_limiter:
|
|
||||||
self.log.debug(f"Creating NIC {name}")
|
|
||||||
return self.azul.network_interfaces.create(
|
|
||||||
self.resource_group,
|
|
||||||
name,
|
|
||||||
nic_data
|
|
||||||
)
|
|
||||||
|
|
||||||
def _deleteNetworkInterface(self, name):
|
|
||||||
for nic in self._listNetworkInterfaces():
|
|
||||||
if nic['name'] == name:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
with self.rate_limiter:
|
|
||||||
self.log.debug(f"Deleting NIC {name}")
|
|
||||||
self.azul.network_interfaces.delete(self.resource_group, name)
|
|
||||||
return nic
|
|
||||||
|
|
||||||
@cachetools.func.ttl_cache(maxsize=1, ttl=10)
|
@cachetools.func.ttl_cache(maxsize=1, ttl=10)
|
||||||
def _listVirtualMachines(self):
|
def _listVirtualMachines(self):
|
||||||
with self.rate_limiter:
|
with self.rate_limiter:
|
||||||
@ -736,29 +567,91 @@ class AzureAdapter(statemachine.Adapter):
|
|||||||
if label.custom_data:
|
if label.custom_data:
|
||||||
os_profile['customData'] = label.custom_data
|
os_profile['customData'] = label.custom_data
|
||||||
|
|
||||||
|
# Begin disk config
|
||||||
|
|
||||||
|
os_disk = {
|
||||||
|
'createOption': 'FromImage',
|
||||||
|
'deleteOption': 'Delete',
|
||||||
|
}
|
||||||
|
if label.volume_size:
|
||||||
|
os_disk['diskSizeGB'] = label.volume_size
|
||||||
|
|
||||||
|
# Begin network config
|
||||||
|
# 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.
|
||||||
|
# Basic is being retired, and static allocation (contrary to
|
||||||
|
# what it sounds like) works for our use case.
|
||||||
|
# Therefore, we use Standird + static for everything.
|
||||||
|
|
||||||
|
def make_ip_config(name, version, subnet_id, pip):
|
||||||
|
ip_config = {
|
||||||
|
'name': f'{name}-config',
|
||||||
|
'properties': {
|
||||||
|
'privateIPAddressVersion': version,
|
||||||
|
'subnet': {
|
||||||
|
'id': subnet_id
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pip:
|
||||||
|
ip_config['properties']['publicIPAddressConfiguration'] = {
|
||||||
|
'name': f'{name}-ip',
|
||||||
|
'properties': {
|
||||||
|
'deleteOption': 'Delete',
|
||||||
|
'publicIPAllocationMethod': 'Static',
|
||||||
|
},
|
||||||
|
'sku': {
|
||||||
|
'name': 'Standard',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return ip_config
|
||||||
|
|
||||||
|
ip_configs = []
|
||||||
|
|
||||||
|
if label.pool.ipv4:
|
||||||
|
ip_configs.append(make_ip_config(f'{hostname}-v4',
|
||||||
|
'IPv4', self.subnet_id,
|
||||||
|
label.pool.public_ipv4))
|
||||||
|
if label.pool.ipv6:
|
||||||
|
ip_configs.append(make_ip_config(f'{hostname}-v6',
|
||||||
|
'IPv6', self.subnet_id,
|
||||||
|
label.pool.public_ipv6))
|
||||||
|
|
||||||
|
nic_config = {
|
||||||
|
'name': f'{hostname}-nic',
|
||||||
|
'properties': {
|
||||||
|
'deleteOption': 'Delete',
|
||||||
|
'ipConfigurations': ip_configs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
spec = {
|
spec = {
|
||||||
'location': self.provider.location,
|
'location': self.provider.location,
|
||||||
'tags': tags,
|
'tags': tags,
|
||||||
'properties': {
|
'properties': {
|
||||||
'osProfile': os_profile,
|
'osProfile': os_profile,
|
||||||
'hardwareProfile': {
|
'hardwareProfile': {
|
||||||
'vmSize': label.hardware_profile["vm-size"]
|
'vmSize': label.hardware_profile['vm-size']
|
||||||
},
|
},
|
||||||
'storageProfile': {
|
'storageProfile': {
|
||||||
'imageReference': image_reference,
|
'imageReference': image_reference,
|
||||||
|
'osDisk': os_disk,
|
||||||
},
|
},
|
||||||
'networkProfile': {
|
'networkProfile': {
|
||||||
'networkInterfaces': [{
|
'networkApiVersion': (
|
||||||
'id': nic['id'],
|
self.azul.network_interfaces.args['apiVersion']),
|
||||||
'properties': {
|
'networkInterfaceConfigurations': [nic_config],
|
||||||
'primary': True,
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if label.user_data:
|
if label.user_data:
|
||||||
spec['properties']['userData'] = label.user_data
|
spec['properties']['userData'] = label.user_data
|
||||||
|
|
||||||
with self.rate_limiter:
|
with self.rate_limiter:
|
||||||
self.log.debug(f"Creating VM {hostname}")
|
self.log.debug(f"Creating VM {hostname}")
|
||||||
return self.azul.virtual_machines.create(
|
return self.azul.virtual_machines.create(
|
||||||
@ -781,17 +674,6 @@ class AzureAdapter(statemachine.Adapter):
|
|||||||
with self.rate_limiter:
|
with self.rate_limiter:
|
||||||
return self.azul.disks.list(self.resource_group)
|
return self.azul.disks.list(self.resource_group)
|
||||||
|
|
||||||
def _deleteDisk(self, name):
|
|
||||||
# Because the disk listing is unreliable (there is up to a 30
|
|
||||||
# minute delay between disks being created and appearing in
|
|
||||||
# the listing) we can't use the listing to efficiently
|
|
||||||
# determine if the deletion is complete. We could fall back
|
|
||||||
# on the asynchronous operation record, but since disks are
|
|
||||||
# the last thing we delete anyway, let's just fire and forget.
|
|
||||||
with self.rate_limiter:
|
|
||||||
self.azul.disks.delete(self.resource_group, name)
|
|
||||||
return None
|
|
||||||
|
|
||||||
@cachetools.func.ttl_cache(maxsize=1, ttl=10)
|
@cachetools.func.ttl_cache(maxsize=1, ttl=10)
|
||||||
def _listImages(self):
|
def _listImages(self):
|
||||||
with self.rate_limiter:
|
with self.rate_limiter:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright 2021 Acme Gating, LLC
|
# Copyright 2021, 2023 Acme Gating, LLC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
# not use this file except in compliance with the License. You may obtain
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@ -230,17 +230,17 @@ class AzureCloud:
|
|||||||
self,
|
self,
|
||||||
providerId='Microsoft.Network',
|
providerId='Microsoft.Network',
|
||||||
resource='networkInterfaces',
|
resource='networkInterfaces',
|
||||||
apiVersion='2020-07-01')
|
apiVersion='2020-11-01')
|
||||||
self.public_ip_addresses = AzureResourceProviderCRUD(
|
self.public_ip_addresses = AzureResourceProviderCRUD(
|
||||||
self,
|
self,
|
||||||
providerId='Microsoft.Network',
|
providerId='Microsoft.Network',
|
||||||
resource='publicIPAddresses',
|
resource='publicIPAddresses',
|
||||||
apiVersion='2020-07-01')
|
apiVersion='2020-11-01')
|
||||||
self.virtual_machines = AzureResourceProviderCRUD(
|
self.virtual_machines = AzureResourceProviderCRUD(
|
||||||
self,
|
self,
|
||||||
providerId='Microsoft.Compute',
|
providerId='Microsoft.Compute',
|
||||||
resource='virtualMachines',
|
resource='virtualMachines',
|
||||||
apiVersion='2022-11-01')
|
apiVersion='2023-03-01')
|
||||||
self.disks = AzureResourceProviderCRUD(
|
self.disks = AzureResourceProviderCRUD(
|
||||||
self,
|
self,
|
||||||
providerId='Microsoft.Compute',
|
providerId='Microsoft.Compute',
|
||||||
|
@ -192,6 +192,7 @@ class AzureLabel(ConfigValue):
|
|||||||
self.user_data = self._encodeData(label.get('user-data', None))
|
self.user_data = self._encodeData(label.get('user-data', None))
|
||||||
self.custom_data = self._encodeData(label.get('custom-data', None))
|
self.custom_data = self._encodeData(label.get('custom-data', None))
|
||||||
self.host_key_checking = self.pool.host_key_checking
|
self.host_key_checking = self.pool.host_key_checking
|
||||||
|
self.volume_size = label.get('volume-size')
|
||||||
|
|
||||||
def _encodeData(self, s):
|
def _encodeData(self, s):
|
||||||
if not s:
|
if not s:
|
||||||
@ -213,6 +214,7 @@ class AzureLabel(ConfigValue):
|
|||||||
'dynamic-tags': dict,
|
'dynamic-tags': dict,
|
||||||
'user-data': str,
|
'user-data': str,
|
||||||
'custom-data': str,
|
'custom-data': str,
|
||||||
|
'volume-size': int,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2021 Acme Gating, LLC
|
# Copyright (C) 2021, 2023 Acme Gating, LLC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -72,8 +72,11 @@ class PublicIPAddressesCRUD(CRUDManager):
|
|||||||
name = "Microsoft.Network/publicIPAddresses"
|
name = "Microsoft.Network/publicIPAddresses"
|
||||||
|
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
data = json.loads(request.body)
|
return self._put(json.loads(request.body),
|
||||||
url = urllib.parse.urlparse(request.path_url)
|
request.path_url)
|
||||||
|
|
||||||
|
def _put(self, data, url):
|
||||||
|
url = urllib.parse.urlparse(url)
|
||||||
name = url.path.split('/')[-1]
|
name = url.path.split('/')[-1]
|
||||||
data['id'] = url.path
|
data['id'] = url.path
|
||||||
data['name'] = name
|
data['name'] = name
|
||||||
@ -82,7 +85,7 @@ class PublicIPAddressesCRUD(CRUDManager):
|
|||||||
"provisioningState": "Updating",
|
"provisioningState": "Updating",
|
||||||
"resourceGuid": str(uuid.uuid4()),
|
"resourceGuid": str(uuid.uuid4()),
|
||||||
"publicIPAddressVersion": "IPv4",
|
"publicIPAddressVersion": "IPv4",
|
||||||
"publicIPAllocationMethod": "Dynamic",
|
"publicIPAllocationMethod": "Static",
|
||||||
"idleTimeoutInMinutes": 4,
|
"idleTimeoutInMinutes": 4,
|
||||||
"ipTags": [],
|
"ipTags": [],
|
||||||
}
|
}
|
||||||
@ -98,8 +101,11 @@ class NetworkInterfacesCRUD(CRUDManager):
|
|||||||
name = "Microsoft.Network/networkInterfaces"
|
name = "Microsoft.Network/networkInterfaces"
|
||||||
|
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
data = json.loads(request.body)
|
return self._put(json.loads(request.body),
|
||||||
url = urllib.parse.urlparse(request.path_url)
|
request.path_url)
|
||||||
|
|
||||||
|
def _put(self, data, url):
|
||||||
|
url = urllib.parse.urlparse(url)
|
||||||
name = url.path.split('/')[-1]
|
name = url.path.split('/')[-1]
|
||||||
data['id'] = url.path
|
data['id'] = url.path
|
||||||
data['name'] = name
|
data['name'] = name
|
||||||
@ -119,7 +125,7 @@ class NetworkInterfacesCRUD(CRUDManager):
|
|||||||
"privateIPAddress": "10.0.0.4",
|
"privateIPAddress": "10.0.0.4",
|
||||||
"privateIPAllocationMethod": "Dynamic",
|
"privateIPAllocationMethod": "Dynamic",
|
||||||
"publicIPAddress": (ipconfig['properties']
|
"publicIPAddress": (ipconfig['properties']
|
||||||
['publicIpAddress']),
|
['publicIPAddress']),
|
||||||
"subnet": ipconfig['properties']['subnet'],
|
"subnet": ipconfig['properties']['subnet'],
|
||||||
"primary": True,
|
"primary": True,
|
||||||
"privateIPAddressVersion": "IPv4",
|
"privateIPAddressVersion": "IPv4",
|
||||||
@ -170,6 +176,8 @@ class VirtualMachinesCRUD(CRUDManager):
|
|||||||
}
|
}
|
||||||
data['zones'] = ["1"]
|
data['zones'] = ["1"]
|
||||||
self.items.append(data)
|
self.items.append(data)
|
||||||
|
|
||||||
|
# Add a disk
|
||||||
disk_data = data.copy()
|
disk_data = data.copy()
|
||||||
disk_data['name'] = 'bionic-azure-' + str(uuid.uuid4())
|
disk_data['name'] = 'bionic-azure-' + str(uuid.uuid4())
|
||||||
disk_data['type'] = "Microsoft.Compute/disks"
|
disk_data['type'] = "Microsoft.Compute/disks"
|
||||||
@ -178,11 +186,82 @@ class VirtualMachinesCRUD(CRUDManager):
|
|||||||
disk_data['properties'] = {"provisioningState": "Succeeded"}
|
disk_data['properties'] = {"provisioningState": "Succeeded"}
|
||||||
self.cloud.crud["Microsoft.Compute/disks"].items.append(disk_data)
|
self.cloud.crud["Microsoft.Compute/disks"].items.append(disk_data)
|
||||||
|
|
||||||
|
# Add a PIP
|
||||||
|
resource_group = self.cloud._extract_resource_group(url.path)
|
||||||
|
data_nic = (data['properties']['networkProfile']
|
||||||
|
['networkInterfaceConfigurations'][0])
|
||||||
|
data_ip = data_nic['properties']['ipConfigurations'][0]
|
||||||
|
data_pip = data_ip['properties']['publicIPAddressConfiguration']
|
||||||
|
pip_data = {
|
||||||
|
'location': data['location'],
|
||||||
|
'sku': {
|
||||||
|
'name': data_pip['sku'],
|
||||||
|
},
|
||||||
|
'properties': {
|
||||||
|
'publicIPAddressVersion': (data_ip['properties']
|
||||||
|
['privateIPAddressVersion']),
|
||||||
|
'publicIPAllocationMethod': (data_pip['properties']
|
||||||
|
['publicIPAllocationMethod']),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pip_url = (f'/subscriptions/{self.cloud.subscription_id}'
|
||||||
|
f'/resourceGroups/{resource_group}/providers'
|
||||||
|
f'/Microsoft.Network/publicIPAddresses/{name}-v4-abcd')
|
||||||
|
self.cloud.crud["Microsoft.Network/publicIPAddresses"]._put(
|
||||||
|
pip_data, pip_url)
|
||||||
|
|
||||||
|
# Add a NIC
|
||||||
|
nic_data = {
|
||||||
|
'location': data['location'],
|
||||||
|
'properties': {
|
||||||
|
'ipConfigurations': [{
|
||||||
|
'name': data_ip['name'],
|
||||||
|
'properties': {
|
||||||
|
'privateIPAddressVersion': (
|
||||||
|
data_ip['properties']
|
||||||
|
['privateIPAddressVersion']),
|
||||||
|
'subnet': data_ip['properties']['subnet'],
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data['properties']['networkProfile']['networkInterfaces'] =\
|
||||||
|
[nic_data.copy()]
|
||||||
|
(nic_data['properties']['ipConfigurations'][0]['properties']
|
||||||
|
['publicIPAddress']) = {
|
||||||
|
'id': pip_url,
|
||||||
|
'type': 'Microsoft.Network/publicIPAddresses',
|
||||||
|
'properties': {
|
||||||
|
'publicIPAddressVersion': (data_ip['properties']
|
||||||
|
['privateIPAddressVersion']),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nic_url = (f'/subscriptions/{self.cloud.subscription_id}'
|
||||||
|
f'/resourceGroups/{resource_group}/providers'
|
||||||
|
f'/Microsoft.Network/networkInterfaces/{name}-nic-abcd')
|
||||||
|
data['properties']['networkProfile']['networkInterfaces'][0]['id'] =\
|
||||||
|
nic_url
|
||||||
|
self.cloud.crud["Microsoft.Network/networkInterfaces"]._put(
|
||||||
|
nic_data, nic_url)
|
||||||
|
|
||||||
ret = json.dumps(data)
|
ret = json.dumps(data)
|
||||||
# Finish provisioning after return
|
# Finish provisioning after return
|
||||||
data['properties']['provisioningState'] = "Succeeded"
|
data['properties']['provisioningState'] = "Succeeded"
|
||||||
return (200, {}, ret)
|
return (200, {}, ret)
|
||||||
|
|
||||||
|
def delete(self, request):
|
||||||
|
url = urllib.parse.urlparse(request.path_url)
|
||||||
|
name = url.path.split('/')[-1]
|
||||||
|
for item in self.items:
|
||||||
|
if item['name'] == name:
|
||||||
|
self.items.remove(item)
|
||||||
|
return (200, {}, '')
|
||||||
|
return (404, {}, json.dumps({
|
||||||
|
'error': {
|
||||||
|
'message': 'Not Found',
|
||||||
|
'code': 'NotFound',
|
||||||
|
}}))
|
||||||
|
|
||||||
|
|
||||||
class DisksCRUD(CRUDManager):
|
class DisksCRUD(CRUDManager):
|
||||||
name = "Microsoft.Compute/disks"
|
name = "Microsoft.Compute/disks"
|
||||||
@ -325,9 +404,9 @@ class FakeAzureFixture(fixtures.Fixture):
|
|||||||
self._setup_crud(ResourceGroupsCRUD, '2020-06-01',
|
self._setup_crud(ResourceGroupsCRUD, '2020-06-01',
|
||||||
resource_grouped=False)
|
resource_grouped=False)
|
||||||
|
|
||||||
self._setup_crud(VirtualMachinesCRUD, '2022-11-01')
|
self._setup_crud(VirtualMachinesCRUD, '2023-03-01')
|
||||||
self._setup_crud(NetworkInterfacesCRUD, '2020-07-01')
|
self._setup_crud(NetworkInterfacesCRUD, '2020-11-01')
|
||||||
self._setup_crud(PublicIPAddressesCRUD, '2020-07-01')
|
self._setup_crud(PublicIPAddressesCRUD, '2020-11-01')
|
||||||
self._setup_crud(DisksCRUD, '2020-06-30')
|
self._setup_crud(DisksCRUD, '2020-06-30')
|
||||||
self._setup_crud(ImagesCRUD, '2020-12-01')
|
self._setup_crud(ImagesCRUD, '2020-12-01')
|
||||||
|
|
||||||
|
@ -104,6 +104,10 @@ class TestDriverAzure(tests.DBTestCase):
|
|||||||
self.assertEqual(tags.get('team'), 'DevOps')
|
self.assertEqual(tags.get('team'), 'DevOps')
|
||||||
self.assertEqual(tags.get('dynamic-tenant'), 'Tenant is tenant-1')
|
self.assertEqual(tags.get('dynamic-tenant'), 'Tenant is tenant-1')
|
||||||
|
|
||||||
|
node.state = zk.USED
|
||||||
|
self.zk.storeNode(node)
|
||||||
|
self.waitForNodeDeletion(node)
|
||||||
|
|
||||||
def test_azure_min_ready(self):
|
def test_azure_min_ready(self):
|
||||||
configfile = self.setup_config(
|
configfile = self.setup_config(
|
||||||
'azure-min-ready.yaml',
|
'azure-min-ready.yaml',
|
||||||
|
28
releasenotes/notes/azure-volume-size-1e08f6e62742b3f7.yaml
Normal file
28
releasenotes/notes/azure-volume-size-1e08f6e62742b3f7.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The Azure driver now supports specifying the size of the OS disk.
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
The Azure driver now uses the "Standard" SKU for all public IP
|
||||||
|
addresses. Previously it would chose either "Standard" or "Basic"
|
||||||
|
depending on the selection of IPv4 or IPv6 addresses.
|
||||||
|
|
||||||
|
Pricing for public IP addresses may differ between the SKU levels.
|
||||||
|
|
||||||
|
Standard IP addresses block all incoming traffic by default,
|
||||||
|
therefore the use of a Network Security Group is required in order
|
||||||
|
to allow incoming traffic.
|
||||||
|
|
||||||
|
If you are not currently using a Network Security Group, then
|
||||||
|
before upgrading Nodepool it is recommended to create one, add any
|
||||||
|
required rules, and attach it to the subnet that Nodepool uses.
|
||||||
|
- |
|
||||||
|
The Azure driver no longer creates and deletes Disks, Network
|
||||||
|
Interfaces, or Public IP Addresses as separate steps.
|
||||||
|
|
||||||
|
Nodepool and user-supplied tags will no longer be applied to
|
||||||
|
Network Interfaces, or Public IP Addresses. This also limits
|
||||||
|
Nodepool's ability to detect leaks of these resources (however
|
||||||
|
this is unlikely since Azure is now responsible for deleting
|
||||||
|
them).
|
Loading…
x
Reference in New Issue
Block a user