230 lines
6.5 KiB
Python
230 lines
6.5 KiB
Python
# Author: Antti Myyrä <antti.myyra@upcloud.com>
|
|
#
|
|
# This file is part of cloud-init. See LICENSE file for license information.
|
|
|
|
import json
|
|
import logging
|
|
|
|
from cloudinit import dmi
|
|
from cloudinit import net as cloudnet
|
|
from cloudinit import url_helper
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def convert_to_network_config_v1(config):
|
|
"""
|
|
Convert the UpCloud network metadata description into
|
|
Cloud-init's version 1 netconfig format.
|
|
|
|
Example JSON:
|
|
{
|
|
"interfaces": [
|
|
{
|
|
"index": 1,
|
|
"ip_addresses": [
|
|
{
|
|
"address": "94.237.105.53",
|
|
"dhcp": true,
|
|
"dns": [
|
|
"94.237.127.9",
|
|
"94.237.40.9"
|
|
],
|
|
"family": "IPv4",
|
|
"floating": false,
|
|
"gateway": "94.237.104.1",
|
|
"network": "94.237.104.0/22"
|
|
},
|
|
{
|
|
"address": "94.237.105.50",
|
|
"dhcp": false,
|
|
"dns": [],
|
|
"family": "IPv4",
|
|
"floating": true,
|
|
"gateway": "",
|
|
"network": "94.237.105.50/32"
|
|
}
|
|
],
|
|
"mac": "32:d5:ba:4a:36:e7",
|
|
"network_id": "031457f4-0f8c-483c-96f2-eccede02909c",
|
|
"type": "public"
|
|
},
|
|
{
|
|
"index": 2,
|
|
"ip_addresses": [
|
|
{
|
|
"address": "10.6.3.27",
|
|
"dhcp": true,
|
|
"dns": [],
|
|
"family": "IPv4",
|
|
"floating": false,
|
|
"gateway": "10.6.0.1",
|
|
"network": "10.6.0.0/22"
|
|
}
|
|
],
|
|
"mac": "32:d5:ba:4a:84:cc",
|
|
"network_id": "03d82553-5bea-4132-b29a-e1cf67ec2dd1",
|
|
"type": "utility"
|
|
},
|
|
{
|
|
"index": 3,
|
|
"ip_addresses": [
|
|
{
|
|
"address": "2a04:3545:1000:720:38d6:baff:fe4a:63e7",
|
|
"dhcp": true,
|
|
"dns": [
|
|
"2a04:3540:53::1",
|
|
"2a04:3544:53::1"
|
|
],
|
|
"family": "IPv6",
|
|
"floating": false,
|
|
"gateway": "2a04:3545:1000:720::1",
|
|
"network": "2a04:3545:1000:720::/64"
|
|
}
|
|
],
|
|
"mac": "32:d5:ba:4a:63:e7",
|
|
"network_id": "03000000-0000-4000-8046-000000000000",
|
|
"type": "public"
|
|
},
|
|
{
|
|
"index": 4,
|
|
"ip_addresses": [
|
|
{
|
|
"address": "172.30.1.10",
|
|
"dhcp": true,
|
|
"dns": [],
|
|
"family": "IPv4",
|
|
"floating": false,
|
|
"gateway": "172.30.1.1",
|
|
"network": "172.30.1.0/24"
|
|
}
|
|
],
|
|
"mac": "32:d5:ba:4a:8a:e1",
|
|
"network_id": "035a0a4a-77b4-4de5-820d-189fc8135714",
|
|
"type": "private"
|
|
}
|
|
],
|
|
"dns": [
|
|
"94.237.127.9",
|
|
"94.237.40.9"
|
|
]
|
|
}
|
|
"""
|
|
|
|
def _get_subnet_config(ip_addr, dns):
|
|
if ip_addr.get("dhcp"):
|
|
dhcp_type = "dhcp"
|
|
if ip_addr.get("family") == "IPv6":
|
|
# UpCloud currently passes IPv6 addresses via
|
|
# StateLess Address Auto Configuration (SLAAC)
|
|
dhcp_type = "ipv6_dhcpv6-stateless"
|
|
return {"type": dhcp_type}
|
|
|
|
static_type = "static"
|
|
if ip_addr.get("family") == "IPv6":
|
|
static_type = "static6"
|
|
subpart = {
|
|
"type": static_type,
|
|
"control": "auto",
|
|
"address": ip_addr.get("address"),
|
|
}
|
|
|
|
if ip_addr.get("gateway"):
|
|
subpart["gateway"] = ip_addr.get("gateway")
|
|
|
|
if "/" in ip_addr.get("network"):
|
|
subpart["netmask"] = ip_addr.get("network").split("/")[1]
|
|
|
|
if dns != ip_addr.get("dns") and ip_addr.get("dns"):
|
|
subpart["dns_nameservers"] = ip_addr.get("dns")
|
|
|
|
return subpart
|
|
|
|
nic_configs = []
|
|
macs_to_interfaces = cloudnet.get_interfaces_by_mac()
|
|
LOG.debug("NIC mapping: %s", macs_to_interfaces)
|
|
|
|
for raw_iface in config.get("interfaces"):
|
|
LOG.debug("Considering %s", raw_iface)
|
|
|
|
mac_address = raw_iface.get("mac")
|
|
if mac_address not in macs_to_interfaces:
|
|
raise RuntimeError(
|
|
"Did not find network interface on system "
|
|
"with mac '%s'. Cannot apply configuration: %s"
|
|
% (mac_address, raw_iface)
|
|
)
|
|
|
|
iface_type = raw_iface.get("type")
|
|
sysfs_name = macs_to_interfaces.get(mac_address)
|
|
|
|
LOG.debug(
|
|
"Found %s interface '%s' with address '%s' (index %d)",
|
|
iface_type,
|
|
sysfs_name,
|
|
mac_address,
|
|
raw_iface.get("index"),
|
|
)
|
|
|
|
interface = {
|
|
"type": "physical",
|
|
"name": sysfs_name,
|
|
"mac_address": mac_address,
|
|
}
|
|
|
|
subnets = []
|
|
for ip_address in raw_iface.get("ip_addresses"):
|
|
sub_part = _get_subnet_config(ip_address, config.get("dns"))
|
|
subnets.append(sub_part)
|
|
|
|
interface["subnets"] = subnets
|
|
nic_configs.append(interface)
|
|
|
|
if config.get("dns"):
|
|
LOG.debug("Setting DNS nameservers to %s", config.get("dns"))
|
|
nic_configs.append(
|
|
{"type": "nameserver", "address": config.get("dns")}
|
|
)
|
|
|
|
return {"version": 1, "config": nic_configs}
|
|
|
|
|
|
def convert_network_config(config):
|
|
return convert_to_network_config_v1(config)
|
|
|
|
|
|
def read_metadata(url, timeout=2, sec_between=2, retries=30):
|
|
response = url_helper.readurl(
|
|
url, timeout=timeout, sec_between=sec_between, retries=retries
|
|
)
|
|
if not response.ok():
|
|
raise RuntimeError("unable to read metadata at %s" % url)
|
|
return json.loads(response.contents.decode())
|
|
|
|
|
|
def read_sysinfo():
|
|
# UpCloud embeds vendor ID and server UUID in the
|
|
# SMBIOS information
|
|
|
|
# Detect if we are on UpCloud and return the UUID
|
|
|
|
vendor_name = dmi.read_dmi_data("system-manufacturer")
|
|
if vendor_name != "UpCloud":
|
|
return False, None
|
|
|
|
server_uuid = dmi.read_dmi_data("system-uuid")
|
|
if server_uuid:
|
|
LOG.debug(
|
|
"system identified via SMBIOS as UpCloud server: %s", server_uuid
|
|
)
|
|
else:
|
|
msg = (
|
|
"system identified via SMBIOS as a UpCloud server, but "
|
|
"did not provide an ID. Please contact support via"
|
|
"https://hub.upcloud.com or via email with support@upcloud.com"
|
|
)
|
|
LOG.critical(msg)
|
|
raise RuntimeError(msg)
|
|
|
|
return True, server_uuid
|