233 lines
6.7 KiB
Python
233 lines
6.7 KiB
Python
# Author: Ben Howard <bh@digitalocean.com>
|
|
#
|
|
# This file is part of cloud-init. See LICENSE file for license information.
|
|
|
|
import json
|
|
import logging
|
|
import random
|
|
|
|
from cloudinit import dmi
|
|
from cloudinit import net as cloudnet
|
|
from cloudinit import url_helper, util
|
|
|
|
NIC_MAP = {"public": "eth0", "private": "eth1"}
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def assign_ipv4_link_local(distro, nic=None):
|
|
"""Bring up NIC using an address using link-local (ip4LL) IPs.
|
|
On DigitalOcean, the link-local domain is per-droplet routed, so there
|
|
is no risk of collisions. However, to be more safe, the ip4LL
|
|
address is random.
|
|
"""
|
|
|
|
if not nic:
|
|
nic = get_link_local_nic(distro)
|
|
LOG.debug("selected interface '%s' for reading metadata", nic)
|
|
|
|
if not nic:
|
|
raise RuntimeError(
|
|
"unable to find interfaces to access the"
|
|
"meta-data server. This droplet is broken."
|
|
)
|
|
|
|
addr = "169.254.{0}.{1}/16".format(
|
|
random.randint(1, 168), random.randint(0, 255)
|
|
)
|
|
|
|
try:
|
|
distro.net_ops.add_addr(nic, addr)
|
|
distro.net_ops.link_up(nic)
|
|
LOG.debug("brought device '%s' up with address %s", nic, addr)
|
|
except Exception:
|
|
util.logexc(
|
|
LOG,
|
|
"ip4LL address assignment of '%s' to '%s' failed."
|
|
" Droplet networking will be broken",
|
|
addr,
|
|
nic,
|
|
)
|
|
raise
|
|
|
|
return nic
|
|
|
|
|
|
def get_link_local_nic(distro):
|
|
nics = [
|
|
f
|
|
for f in cloudnet.get_devicelist()
|
|
if distro.networking.is_physical(f)
|
|
]
|
|
if not nics:
|
|
return None
|
|
return min(nics, key=lambda d: cloudnet.read_sys_net_int(d, "ifindex"))
|
|
|
|
|
|
def del_ipv4_link_local(distro, nic=None):
|
|
"""Remove the ip4LL address. While this is not necessary, the ip4LL
|
|
address is extraneous and confusing to users.
|
|
"""
|
|
if not nic:
|
|
LOG.debug(
|
|
"no link_local address interface defined, skipping link "
|
|
"local address cleanup"
|
|
)
|
|
return
|
|
|
|
LOG.debug("cleaning up ipv4LL address")
|
|
|
|
try:
|
|
distro.net_ops.flush_addr(nic)
|
|
LOG.debug("removed ip4LL addresses from %s", nic)
|
|
|
|
except Exception as e:
|
|
util.logexc(LOG, "failed to remove ip4LL address from '%s'.", nic, e)
|
|
|
|
|
|
def convert_network_configuration(config, dns_servers):
|
|
"""Convert the DigitalOcean Network description into Cloud-init's netconfig
|
|
format.
|
|
|
|
Example JSON:
|
|
{'public': [
|
|
{'mac': '04:01:58:27:7f:01',
|
|
'ipv4': {'gateway': '45.55.32.1',
|
|
'netmask': '255.255.224.0',
|
|
'ip_address': '45.55.50.93'},
|
|
'anchor_ipv4': {
|
|
'gateway': '10.17.0.1',
|
|
'netmask': '255.255.0.0',
|
|
'ip_address': '10.17.0.9'},
|
|
'type': 'public',
|
|
'ipv6': {'gateway': '....',
|
|
'ip_address': '....',
|
|
'cidr': 64}}
|
|
],
|
|
'private': [
|
|
{'mac': '04:01:58:27:7f:02',
|
|
'ipv4': {'gateway': '10.132.0.1',
|
|
'netmask': '255.255.0.0',
|
|
'ip_address': '10.132.75.35'},
|
|
'type': 'private'}
|
|
]
|
|
}
|
|
"""
|
|
|
|
def _get_subnet_part(pcfg):
|
|
subpart = {
|
|
"type": "static",
|
|
"control": "auto",
|
|
"address": pcfg.get("ip_address"),
|
|
"gateway": pcfg.get("gateway"),
|
|
}
|
|
|
|
if ":" in pcfg.get("ip_address"):
|
|
subpart["address"] = "{0}/{1}".format(
|
|
pcfg.get("ip_address"), pcfg.get("cidr")
|
|
)
|
|
else:
|
|
subpart["netmask"] = pcfg.get("netmask")
|
|
|
|
return subpart
|
|
|
|
nic_configs = []
|
|
macs_to_nics = cloudnet.get_interfaces_by_mac()
|
|
LOG.debug("nic mapping: %s", macs_to_nics)
|
|
|
|
for n in config:
|
|
nic = config[n][0]
|
|
LOG.debug("considering %s", nic)
|
|
|
|
mac_address = nic.get("mac")
|
|
if mac_address not in macs_to_nics:
|
|
raise RuntimeError(
|
|
"Did not find network interface on system "
|
|
"with mac '%s'. Cannot apply configuration: %s"
|
|
% (mac_address, nic)
|
|
)
|
|
|
|
sysfs_name = macs_to_nics.get(mac_address)
|
|
nic_type = nic.get("type", "unknown")
|
|
|
|
if_name = NIC_MAP.get(nic_type, sysfs_name)
|
|
if if_name != sysfs_name:
|
|
LOG.debug(
|
|
"Found %s interface '%s' on '%s', assigned name of '%s'",
|
|
nic_type,
|
|
mac_address,
|
|
sysfs_name,
|
|
if_name,
|
|
)
|
|
else:
|
|
msg = (
|
|
"Found interface '%s' on '%s', which is not a public "
|
|
"or private interface. Using default system naming."
|
|
)
|
|
LOG.debug(msg, mac_address, sysfs_name)
|
|
|
|
ncfg = {
|
|
"type": "physical",
|
|
"mac_address": mac_address,
|
|
"name": if_name,
|
|
}
|
|
|
|
subnets = []
|
|
for netdef in ("ipv4", "ipv6", "anchor_ipv4", "anchor_ipv6"):
|
|
raw_subnet = nic.get(netdef, None)
|
|
if not raw_subnet:
|
|
continue
|
|
|
|
sub_part = _get_subnet_part(raw_subnet)
|
|
if nic_type != "public" or "anchor" in netdef:
|
|
del sub_part["gateway"]
|
|
|
|
subnets.append(sub_part)
|
|
|
|
ncfg["subnets"] = subnets
|
|
nic_configs.append(ncfg)
|
|
LOG.debug("nic '%s' configuration: %s", if_name, ncfg)
|
|
|
|
if dns_servers:
|
|
LOG.debug("added dns servers: %s", dns_servers)
|
|
nic_configs.append({"type": "nameserver", "address": dns_servers})
|
|
|
|
return {"version": 1, "config": nic_configs}
|
|
|
|
|
|
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():
|
|
# DigitalOcean embeds vendor ID and instance/droplet_id in the
|
|
# SMBIOS information
|
|
|
|
# Detect if we are on DigitalOcean and return the Droplet's ID
|
|
vendor_name = dmi.read_dmi_data("system-manufacturer")
|
|
if vendor_name != "DigitalOcean":
|
|
return (False, None)
|
|
|
|
droplet_id = dmi.read_dmi_data("system-serial-number")
|
|
if droplet_id:
|
|
LOG.debug(
|
|
"system identified via SMBIOS as DigitalOcean Droplet: %s",
|
|
droplet_id,
|
|
)
|
|
else:
|
|
msg = (
|
|
"system identified via SMBIOS as a DigitalOcean "
|
|
"Droplet, but did not provide an ID. Please file a "
|
|
"support ticket at: "
|
|
"https://cloud.digitalocean.com/support/tickets/new"
|
|
)
|
|
LOG.critical(msg)
|
|
raise RuntimeError(msg)
|
|
|
|
return (True, droplet_id)
|