cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #05851
Re: [Merge] ~i.galic/cloud-init:refactor/net-fbsd into cloud-init:master
Diff comments:
> diff --git a/cloudinit/net/freebsd.py b/cloudinit/net/freebsd.py
> new file mode 100644
> index 0000000..4f2179b
> --- /dev/null
> +++ b/cloudinit/net/freebsd.py
> @@ -0,0 +1,268 @@
> +# Copyright (C) 2018 Canonical Ltd.
> +#
> +# This file is part of cloud-init. See LICENSE file for license information.
> +
> +import errno
> +import logging
> +import re
> +
> +from cloudinit.net.network_state import mask_to_net_prefix
> +from cloudinit import util
> +
> +LOG = logging.getLogger(__name__)
> +DEFAULT_PRIMARY_INTERFACE = 'vtnet0'
> +
> +
> +def _ifconfig(*args):
> + return util.subp(['ifconfig', *args])
> +
> +
> +def _sysctl(*args):
> + return util.subp(['sysctl', *args])
> +
> +
> +def _pciconf(*args):
> + return util.subp(['pciconf', *args])
> +
> +
> +def _route(*args):
> + return util.subp(['route', *args])
> +
> +
> +def _parse_devname(devname):
> + dev = re.match("^(?P<driver>[a-z]+)(?P<devid>[0-9]+)$", devname)
> + if dev is None:
> + return None
> + return (
> + dev.group("driver"),
> + dev.group("devid"),
> + )
> +
> +
> +def is_up(devname):
> + output = _ifconfig(['-u', devname])
> + if output[0] is '' or output[1] is not '':
> + return False
> +
> + return devname in output[0].split("\n")[0]
> +
> +
> +def is_wireless(devname):
> + output = _sysctl('-n', "net.wlan.devices")
> + if output[0] is '' or output[1] is not '':
> + return False
> +
> + wlan_devs = output[0].strip().split(" ")
> + return devname in wlan_devs
> +
> +
> +def is_bridge(devname):
> + output = _ifconfig(['-g', 'bridge'])
> + if output[0] is '' or output[1] is not '':
> + return False
> +
> + bridges = output[0].strip().split("\n")
> + return devname in bridges
> +
> +
> +def is_bond(devname):
> + output = _ifconfig(['-g', 'lagg'])
> + if output[0] is '' or output[1] is not '':
> + return False
> +
> + bonds = output[0].strip().split("\n")
> + return devname in bonds
> +
> +
> +def is_vlan(devname):
> + output = _ifconfig(['-g', 'vlan'])
> + if output[0] is '' or output[1] is not '':
> + return False
> +
> + vlans = output[0].strip().split("\n")
> + return devname in vlans
> +
> +
> +def is_renamed(devname):
> + # we can only tell if a *physical* device was renamed, so
> + if not is_physical(devname):
> + return False
> +
> + dev = _parse_devname(devname)
> + renamed = _syctl(['-n', "dev.", dev[0] + "." + dev[1] + ".%driver"])
> + if renamed[0] is '' or renamed[1] is not '':
Yeah, let's avoid duplication down in distros. I think your brancn is appropriately specializing net-related utils to specific distros in the top-level cloudinit.net.freebsd module. I think cloudinit.distros.freebsd should source any utility it can for various operations.
> + return False
> +
> + return dev[0] == renamed[0]
> +
> +
> +def is_connected(devname):
> + output = _ifconfig([devname])
> + if output[0] is '' or output[1] is not '':
> + return False
> +
> + ifcfg = output[0].split("\n")
> + status_re = re.compile("^\s+status: active$")
> + for l in ifcfg:
> + if status_re.match(l):
> + return True
> + return False
> +
> +
> +def is_physical(devname):
> + # reject interfaces like: vnet0:3
> + if ":" in devname:
> + return False
> +
> + dev = _parse_devname(devname)
> + if dev is None:
> + return False
> +
> + output = _sysctl("dev." + dev[0] + "." + dev[1])
> + if output[0] is '' or output[1] is not '':
> + return False
> +
> + return True
> +
> +
> +def is_present(devname):
> + return devname in get_devicelist()
> +
> +
> +def device_driver(devname):
> + dev = _parse_devname(devname)
> + driver = _syctl(['-n', "dev.", dev[0] + "." + dev[1] + ".%driver"])
> + if driver[0] is '' or output[1] is not '':
> + return None
> +
> + return driver[0]
> +
> +
> +def device_devid(devname):
> + if not is_physical(devname):
> + return None
> +
> + dev = _parse_devname(devname)
> + output = _sysctl(['-n', "dev." + dev[0] + "." + dev[1] + ".%parent"])
> + if output[0] is '' or output[1] is not '':
> + return None
> +
> + # well, isn't that clear? rather than parsing `pciconf -l` output, we just
> + # *read* (-r) a *halfword* (-h) 44 bytes away from the %parent
> + devid = _pciconf(['-rh', output[0], '44'])
> + if devid[0] is '' or devid[1] is not '':
> + return None
> + return "0x" + devid[0]
> +
> +
> +def get_devicelist():
> + maybe_devs = _ifconfig(['-l']).strip().split(' ')
> + if maybe_devs == ['']:
> + return []
> + return maybe_devs
> +
> +
> +def find_fallback_nic(blacklist_drivers=None):
> + output = _route(["-n", "show", "default"])
> + if output[0] is '' or output[1] is not '':
> + return None
> +
> + route = output[0].split("\n")
> + via_re = re.compile("^\s+interface: (?P<netif>.+)$")
> + for l in route:
> + maybe_netif = via_re.match(l)
> + if maybe_netif is None:
> + continue
> +
> + driver = device_driver(maybe_netif)
> + if driver not in blacklist_drivers:
> + return maybe_netif.group("netif")
> +
> + return None
> +
> +
> +def interface_has_own_mac(ifname, strict=False):
> + # breakdown:
> + # physical interfaces have their own MAC
> + # bridges have their own MAC, unless net.link.bridge.inherit_mac is non-zero
> + # vlan interfaces inherit MACs
> + # bond interfaces have ??
> + # tunnel interfaces have ??
> + # virtual interfaces *usually* have their own, unless set not to
> + if is_physical(ifname):
> + return True
> +
> + if is_bridge(ifname):
> + output = _sysctl(['-n', 'net.link.bridge.inherit_mac'])
> + inherit_mac = output[0].strip()
> + if inherit_mac == '0':
> + return False
> + return True
> + # for now:
> + return False
> +
> +
> +def _get_current_rename_info(check_downable=True):
> + """Collect information necessary for rename_interfaces.
> +
> + returns a dictionary by mac address like:
> + {name:
> + {
> + 'downable': None or boolean indicating that the
> + device has only automatically assigned ip addrs.
> + 'device_id': Device id value (if it has one)
> + 'driver': Device driver (if it has one)
> + 'mac': mac address (in lower case)
> + 'name': name
> + 'up': boolean: is_up(name)
> + }}
> + """
> + raise NotImplemented("TODO")
> +
> +
> +def _rename_interfaces(renames,
> + strict_present=True,
> + strict_busy=True,
> + current_info=None):
> +
> + raise NotImplemented("TODO")
> +
> +
> +def get_interface_mac(ifname):
> + """Returns the string value of an interface's MAC Address"""
> + output = _ifconfig([ifname, 'ether'])
> + mac_zero = '00:00:00:00:00:00'
> + if output[0] is '' or output[1] is not '':
> + return mac_zero
> +
> + mac_re = re.compile("\s+ether (?P<mac>([a-f0-9]{2}:){5}[a-f0-9]{2})", re.I)
> + ifcfg = output[0].split("\n")
> + for l in ifcfg:
> + maybe_mac = mac_re.match(l)
> + if maybe_mac is not None:
> + return maybe_mac.group('mac')
> +
> + return mac_zero
> +
> +
> +def get_ib_interface_hwaddr(ifname, ethernet_format):
> + """Since this isn't properly implemented yet, we're returning None for now
> + rather than raise NotImplemented("TODO")
> + That way, we can move get_interfaces_by_mac() into __init__"""
> + return None
> +
> +
> +
> +class EphemeralIPv4Network(object):
> + """Context manager which sets up temporary static network configuration.
> +
> + No operations are performed if the provided interface is already connected.
> + If unconnected, bring up the interface with valid ip, prefix and broadcast.
> + If router is provided setup a default route for that interface. Upon
> + context exit, clean up the interface leaving no configuration behind.
> + """
> +
> + raise NotImplemented("TODO")
> +
> +
> +# vi: ts=4 expandtab
> diff --git a/cloudinit/net/linux.py b/cloudinit/net/linux.py
> new file mode 100644
> index 0000000..25d6f6c
> --- /dev/null
> +++ b/cloudinit/net/linux.py
> @@ -0,0 +1,601 @@
> +# Copyright (C) 2018 Canonical Ltd.
> +#
> +# This file is part of cloud-init. See LICENSE file for license information.
> +
> +import errno
> +import logging
> +import os
> +import re
> +
> +from cloudinit.net.network_state import mask_to_net_prefix
> +from cloudinit import util
> +
> +
> +LOG = logging.getLogger(__name__)
> +SYS_CLASS_NET = "/sys/class/net/"
> +DEFAULT_PRIMARY_INTERFACE = 'eth0'
> +LO_DEVS = ['lo', 'lo0']
> +
> +
> +def get_sys_class_path():
> + """Simple function to return the global SYS_CLASS_NET."""
> + return SYS_CLASS_NET
> +
> +
> +def sys_dev_path(devname, path=""):
> + return get_sys_class_path() + devname + "/" + path
> +
> +
> +def read_sys_net(devname, path, translate=None,
> + on_enoent=None, on_keyerror=None,
> + on_einval=None):
> + dev_path = sys_dev_path(devname, path)
> + try:
> + contents = util.load_file(dev_path)
> + except (OSError, IOError) as e:
> + e_errno = getattr(e, 'errno', None)
> + if e_errno in (errno.ENOENT, errno.ENOTDIR):
> + if on_enoent is not None:
> + return on_enoent(e)
> + if e_errno in (errno.EINVAL,):
> + if on_einval is not None:
> + return on_einval(e)
> + raise
> + contents = contents.strip()
> + if translate is None:
> + return contents
> + try:
> + return translate[contents]
> + except KeyError as e:
> + if on_keyerror is not None:
> + return on_keyerror(e)
> + else:
> + LOG.debug("Found unexpected (not translatable) value"
> + " '%s' in '%s", contents, dev_path)
> + raise
> +
> +
> +def read_sys_net_safe(iface, field, translate=None):
> + def on_excp_false(e):
> + return False
> + return read_sys_net(iface, field,
> + on_keyerror=on_excp_false,
> + on_enoent=on_excp_false,
> + on_einval=on_excp_false,
> + translate=translate)
> +
> +
> +def read_sys_net_int(iface, field):
> + val = read_sys_net_safe(iface, field)
> + if val is False:
> + return None
> + try:
> + return int(val)
> + except ValueError:
> + return None
> +
> +
> +def is_up(devname):
> + # The linux kernel says to consider devices in 'unknown'
> + # operstate as up for the purposes of network configuration. See
> + # Documentation/networking/operstates.txt in the kernel source.
> + translate = {'up': True, 'unknown': True, 'down': False}
> + return read_sys_net_safe(devname, "operstate", translate=translate)
> +
> +
> +def is_wireless(devname):
> + return os.path.exists(sys_dev_path(devname, "wireless"))
> +
> +
> +def is_bridge(devname):
> + return os.path.exists(sys_dev_path(devname, "bridge"))
> +
> +
> +def is_bond(devname):
> + return os.path.exists(sys_dev_path(devname, "bonding"))
> +
> +
> +def is_renamed(devname):
> + """
> + /* interface name assignment types (sysfs name_assign_type attribute) */
> + #define NET_NAME_UNKNOWN 0 /* unknown origin (not exposed to user) */
> + #define NET_NAME_ENUM 1 /* enumerated by kernel */
> + #define NET_NAME_PREDICTABLE 2 /* predictably named by the kernel */
> + #define NET_NAME_USER 3 /* provided by user-space */
> + #define NET_NAME_RENAMED 4 /* renamed by user-space */
> + """
> + name_assign_type = read_sys_net_safe(devname, 'name_assign_type')
> + if name_assign_type and name_assign_type in ['3', '4']:
> + return True
> + return False
> +
> +
> +def is_vlan(devname):
> + uevent = str(read_sys_net_safe(devname, "uevent"))
> + return 'DEVTYPE=vlan' in uevent.splitlines()
> +
> +
> +def is_connected(devname):
> + # is_connected isn't really as simple as that. 2 is
> + # 'physically connected'. 3 is 'not connected'. but a wlan interface will
> + # always show 3.
> + iflink = read_sys_net_safe(devname, "iflink")
> + if iflink == "2":
> + return True
> + if not is_wireless(devname):
> + return False
> + LOG.debug("'%s' is wireless, basing 'connected' on carrier", devname)
> + return read_sys_net_safe(devname, "carrier",
> + translate={'0': False, '1': True})
> +
> +
> +def is_physical(devname):
> + return os.path.exists(sys_dev_path(devname, "device"))
> +
> +
> +def is_present(devname):
> + return os.path.exists(sys_dev_path(devname))
> +
> +
> +def device_driver(devname):
> + """Return the device driver for net device named 'devname'."""
> + driver = None
> + driver_path = sys_dev_path(devname, "device/driver")
> + # driver is a symlink to the driver *dir*
> + if os.path.islink(driver_path):
> + driver = os.path.basename(os.readlink(driver_path))
> +
> + return driver
> +
> +
> +def device_devid(devname):
> + """Return the device id string for net device named 'devname'."""
> + dev_id = read_sys_net_safe(devname, "device/device")
> + if dev_id is False:
> + return None
> +
> + return dev_id
> +
> +
> +def get_devicelist():
> + try:
> + devs = os.listdir(get_sys_class_path())
> + except OSError as e:
> + if e.errno == errno.ENOENT:
> + devs = []
> + else:
> + raise
> + return devs
> +
> +
> +def is_disabled_cfg(cfg):
> + if not cfg or not isinstance(cfg, dict):
> + return False
> + return cfg.get('config') == "disabled"
> +
> +
> +def find_fallback_nic(blacklist_drivers=None):
> + """Return the name of the 'fallback' network device."""
> + if not blacklist_drivers:
> + blacklist_drivers = []
> +
> + if 'net.ifnames=0' in util.get_cmdline():
> + LOG.debug('Stable ifnames disabled by net.ifnames=0 in /proc/cmdline')
> + else:
> + unstable = [device for device in get_devicelist()
> + if device not in LO_DEVS and not is_renamed(device)]
> + if len(unstable):
> + LOG.debug('Found unstable nic names: %s; calling udevadm settle',
> + unstable)
> + msg = 'Waiting for udev events to settle'
> + util.log_time(LOG.debug, msg, func=util.udevadm_settle)
> +
> + # get list of interfaces that could have connections
> + invalid_interfaces = set(LO_DEVS)
> + potential_interfaces = set([device for device in get_devicelist()
> + if device_driver(device) not in
> + blacklist_drivers])
> + potential_interfaces = potential_interfaces.difference(invalid_interfaces)
> + # sort into interfaces with carrier, interfaces which could have carrier,
> + # and ignore interfaces that are definitely disconnected
> + connected = []
> + possibly_connected = []
> + for interface in potential_interfaces:
> + if interface.startswith("veth"):
> + continue
> + if is_bridge(interface):
> + # skip any bridges
> + continue
> + if is_bond(interface):
> + # skip any bonds
> + continue
> + carrier = read_sys_net_int(interface, 'carrier')
> + if carrier:
> + connected.append(interface)
> + continue
> + # check if nic is dormant or down, as this may make a nick appear to
> + # not have a carrier even though it could acquire one when brought
> + # online by dhclient
> + dormant = read_sys_net_int(interface, 'dormant')
> + if dormant:
> + possibly_connected.append(interface)
> + continue
> + operstate = read_sys_net_safe(interface, 'operstate')
> + if operstate in ['dormant', 'down', 'lowerlayerdown', 'unknown']:
> + possibly_connected.append(interface)
> + continue
> +
> + # don't bother with interfaces that might not be connected if there are
> + # some that definitely are
> + if connected:
> + potential_interfaces = connected
> + else:
> + potential_interfaces = possibly_connected
> +
> + # if eth0 exists use it above anything else, otherwise get the interface
> + # that we can read 'first' (using the sorted defintion of first).
> + names = list(sorted(potential_interfaces, key=util.natural_sort_key))
> + if DEFAULT_PRIMARY_INTERFACE in names:
> + names.remove(DEFAULT_PRIMARY_INTERFACE)
> + names.insert(0, DEFAULT_PRIMARY_INTERFACE)
> +
> + # pick the first that has a mac-address
> + for name in names:
> + if read_sys_net_safe(name, 'address'):
> + return name
> + return None
> +
> +
> +
> +def interface_has_own_mac(ifname, strict=False):
> + """return True if the provided interface has its own address.
> +
> + Based on addr_assign_type in /sys. Return true for any interface
> + that does not have a 'stolen' address. Examples of such devices
> + are bonds or vlans that inherit their mac from another device.
> + Possible values are:
> + 0: permanent address 2: stolen from another device
> + 1: randomly generated 3: set using dev_set_mac_address"""
> +
> + assign_type = read_sys_net_int(ifname, "addr_assign_type")
> + if assign_type is None:
> + # None is returned if this nic had no 'addr_assign_type' entry.
> + # if strict, raise an error, if not return True.
> + if strict:
> + raise ValueError("%s had no addr_assign_type.")
> + return True
> + return assign_type in (0, 1, 3)
> +
> +
> +def _get_current_rename_info(check_downable=True):
> + """Collect information necessary for rename_interfaces.
> +
> + returns a dictionary by mac address like:
> + {name:
> + {
> + 'downable': None or boolean indicating that the
> + device has only automatically assigned ip addrs.
> + 'device_id': Device id value (if it has one)
> + 'driver': Device driver (if it has one)
> + 'mac': mac address (in lower case)
> + 'name': name
> + 'up': boolean: is_up(name)
> + }}
> + """
> + cur_info = {}
> + for (name, mac, driver, device_id) in get_interfaces():
> + cur_info[name] = {
> + 'downable': None,
> + 'device_id': device_id,
> + 'driver': driver,
> + 'mac': mac.lower(),
> + 'name': name,
> + 'up': is_up(name),
> + }
> +
> + if check_downable:
> + nmatch = re.compile(r"[0-9]+:\s+(\w+)[@:]")
> + ipv6, _err = util.subp(['ip', '-6', 'addr', 'show', 'permanent',
> + 'scope', 'global'], capture=True)
> + ipv4, _err = util.subp(['ip', '-4', 'addr', 'show'], capture=True)
> +
> + nics_with_addresses = set()
> + for bytes_out in (ipv6, ipv4):
> + nics_with_addresses.update(nmatch.findall(bytes_out))
> +
> + for d in cur_info.values():
> + d['downable'] = (d['up'] is False or
> + d['name'] not in nics_with_addresses)
> +
> + return cur_info
> +
> +
> +def _rename_interfaces(renames, strict_present=True, strict_busy=True,
> + current_info=None):
> +
> + if not len(renames):
> + LOG.debug("no interfaces to rename")
> + return
> +
> + if current_info is None:
> + current_info = _get_current_rename_info()
> +
> + cur_info = {}
> + for name, data in current_info.items():
> + cur = data.copy()
> + if cur.get('mac'):
> + cur['mac'] = cur['mac'].lower()
> + cur['name'] = name
> + cur_info[name] = cur
> +
> + def update_byname(bymac):
> + return dict((data['name'], data)
> + for data in cur_info.values())
> +
> + def rename(cur, new):
> + util.subp(["ip", "link", "set", cur, "name", new], capture=True)
> +
> + def down(name):
> + util.subp(["ip", "link", "set", name, "down"], capture=True)
> +
> + def up(name):
> + util.subp(["ip", "link", "set", name, "up"], capture=True)
> +
> + ops = []
> + errors = []
> + ups = []
> + cur_byname = update_byname(cur_info)
> + tmpname_fmt = "cirename%d"
> + tmpi = -1
> +
> + def entry_match(data, mac, driver, device_id):
> + """match if set and in data"""
> + if mac and driver and device_id:
> + return (data['mac'] == mac and
> + data['driver'] == driver and
> + data['device_id'] == device_id)
> + elif mac and driver:
> + return (data['mac'] == mac and
> + data['driver'] == driver)
> + elif mac:
> + return (data['mac'] == mac)
> +
> + return False
> +
> + def find_entry(mac, driver, device_id):
> + match = [data for data in cur_info.values()
> + if entry_match(data, mac, driver, device_id)]
> + if len(match):
> + if len(match) > 1:
> + msg = ('Failed to match a single device. Matched devices "%s"'
> + ' with search values "(mac:%s driver:%s device_id:%s)"'
> + % (match, mac, driver, device_id))
> + raise ValueError(msg)
> + return match[0]
> +
> + return None
> +
> + for mac, new_name, driver, device_id in renames:
> + if mac:
> + mac = mac.lower()
> + cur_ops = []
> + cur = find_entry(mac, driver, device_id)
> + if not cur:
> + if strict_present:
> + errors.append(
> + "[nic not present] Cannot rename mac=%s to %s"
> + ", not available." % (mac, new_name))
> + continue
> +
> + cur_name = cur.get('name')
> + if cur_name == new_name:
> + # nothing to do
> + continue
> +
> + if not cur_name:
> + if strict_present:
> + errors.append(
> + "[nic not present] Cannot rename mac=%s to %s"
> + ", not available." % (mac, new_name))
> + continue
> +
> + if cur['up']:
> + msg = "[busy] Error renaming mac=%s from %s to %s"
> + if not cur['downable']:
> + if strict_busy:
> + errors.append(msg % (mac, cur_name, new_name))
> + continue
> + cur['up'] = False
> + cur_ops.append(("down", mac, new_name, (cur_name,)))
> + ups.append(("up", mac, new_name, (new_name,)))
> +
> + if new_name in cur_byname:
> + target = cur_byname[new_name]
> + if target['up']:
> + msg = "[busy-target] Error renaming mac=%s from %s to %s."
> + if not target['downable']:
> + if strict_busy:
> + errors.append(msg % (mac, cur_name, new_name))
> + continue
> + else:
> + cur_ops.append(("down", mac, new_name, (new_name,)))
> +
> + tmp_name = None
> + while tmp_name is None or tmp_name in cur_byname:
> + tmpi += 1
> + tmp_name = tmpname_fmt % tmpi
> +
> + cur_ops.append(("rename", mac, new_name, (new_name, tmp_name)))
> + target['name'] = tmp_name
> + cur_byname = update_byname(cur_info)
> + if target['up']:
> + ups.append(("up", mac, new_name, (tmp_name,)))
> +
> + cur_ops.append(("rename", mac, new_name, (cur['name'], new_name)))
> + cur['name'] = new_name
> + cur_byname = update_byname(cur_info)
> + ops += cur_ops
> +
> + opmap = {'rename': rename, 'down': down, 'up': up}
> +
> + if len(ops) + len(ups) == 0:
> + if len(errors):
> + LOG.debug("unable to do any work for renaming of %s", renames)
> + else:
> + LOG.debug("no work necessary for renaming of %s", renames)
> + else:
> + LOG.debug("achieving renaming of %s with ops %s", renames, ops + ups)
> +
> + for op, mac, new_name, params in ops + ups:
> + try:
> + opmap.get(op)(*params)
> + except Exception as e:
> + errors.append(
> + "[unknown] Error performing %s%s for %s, %s: %s" %
> + (op, params, mac, new_name, e))
> +
> + if len(errors):
> + raise Exception('\n'.join(errors))
> +
> +
> +def get_interface_mac(ifname):
> + """Returns the string value of an interface's MAC Address"""
> + path = "address"
> + if os.path.isdir(sys_dev_path(ifname, "bonding_slave")):
> + # for a bond slave, get the nic's hwaddress, not the address it
> + # is using because its part of a bond.
> + path = "bonding_slave/perm_hwaddr"
> + return read_sys_net_safe(ifname, path)
> +
> +def net_setup_link(run=False):
> + """To ensure device link properties are applied, we poke
> + udev to re-evaluate networkd .link files and call
> + the setup_link udev builtin command
> + """
> + if not run:
> + LOG.debug("netplan net_setup_link postcmd disabled")
> + return
> + setup_lnk = ['udevadm', 'test-builtin', 'net_setup_link']
> + for cmd in [setup_lnk + [SYS_CLASS_NET + iface]
> + for iface in get_devicelist() if
> + os.path.islink(SYS_CLASS_NET + iface)]:
> + util.subp(cmd, capture=True)
> +
> +def get_ib_interface_hwaddr(ifname, ethernet_format):
> + """Returns the string value of an Infiniband interface's hardware
> + address. If ethernet_format is True, an Ethernet MAC-style 6 byte
> + representation of the address will be returned.
> + """
> + # Type 32 is Infiniband.
> + if read_sys_net_safe(ifname, 'type') == '32':
+1 on that change proposal. you added for this in a followup commit. is_ib check (distro-dependent) then process mac info.
> + mac = get_interface_mac(ifname)
> + if mac and ethernet_format:
> + # Use bytes 13-15 and 18-20 of the hardware address.
> + mac = mac[36:-14] + mac[51:]
> + return mac
> +
> +
> +class EphemeralIPv4Network(object):
> + """Context manager which sets up temporary static network configuration.
> +
> + No operations are performed if the provided interface is already connected.
> + If unconnected, bring up the interface with valid ip, prefix and broadcast.
> + If router is provided setup a default route for that interface. Upon
> + context exit, clean up the interface leaving no configuration behind.
> + """
> +
> + def __init__(self, interface, ip, prefix_or_mask, broadcast, router=None):
> + """Setup context manager and validate call signature.
> +
> + @param interface: Name of the network interface to bring up.
> + @param ip: IP address to assign to the interface.
> + @param prefix_or_mask: Either netmask of the format X.X.X.X or an int
> + prefix.
> + @param broadcast: Broadcast address for the IPv4 network.
> + @param router: Optionally the default gateway IP.
> + """
> + if not all([interface, ip, prefix_or_mask, broadcast]):
> + raise ValueError(
> + 'Cannot init network on {0} with {1}/{2} and bcast {3}'.format(
> + interface, ip, prefix_or_mask, broadcast))
> + try:
> + self.prefix = mask_to_net_prefix(prefix_or_mask)
> + except ValueError as e:
> + raise ValueError(
> + 'Cannot setup network: {0}'.format(e))
> + self.interface = interface
> + self.ip = ip
> + self.broadcast = broadcast
> + self.router = router
> + self.cleanup_cmds = [] # List of commands to run to cleanup state.
> +
> + def __enter__(self):
> + """Perform ephemeral network setup if interface is not connected."""
> + self._bringup_device()
> + if self.router:
> + self._bringup_router()
> +
> + def __exit__(self, excp_type, excp_value, excp_traceback):
> + """Teardown anything we set up."""
> + for cmd in self.cleanup_cmds:
> + util.subp(cmd, capture=True)
> +
> + def _delete_address(self, address, prefix):
> + """Perform the ip command to remove the specified address."""
> + util.subp(
> + ['ip', '-family', 'inet', 'addr', 'del',
> + '%s/%s' % (address, prefix), 'dev', self.interface],
> + capture=True)
> +
> + def _bringup_device(self):
> + """Perform the ip comands to fully setup the device."""
> + cidr = '{0}/{1}'.format(self.ip, self.prefix)
> + LOG.debug(
> + 'Attempting setup of ephemeral network on %s with %s brd %s',
> + self.interface, cidr, self.broadcast)
> + try:
> + util.subp(
> + ['ip', '-family', 'inet', 'addr', 'add', cidr, 'broadcast',
> + self.broadcast, 'dev', self.interface],
> + capture=True, update_env={'LANG': 'C'})
> + except util.ProcessExecutionError as e:
> + if "File exists" not in e.stderr:
> + raise
> + LOG.debug(
> + 'Skip ephemeral network setup, %s already has address %s',
> + self.interface, self.ip)
> + else:
> + # Address creation success, bring up device and queue cleanup
> + util.subp(
> + ['ip', '-family', 'inet', 'link', 'set', 'dev', self.interface,
> + 'up'], capture=True)
> + self.cleanup_cmds.append(
> + ['ip', '-family', 'inet', 'link', 'set', 'dev', self.interface,
> + 'down'])
> + self.cleanup_cmds.append(
> + ['ip', '-family', 'inet', 'addr', 'del', cidr, 'dev',
> + self.interface])
> +
> + def _bringup_router(self):
> + """Perform the ip commands to fully setup the router if needed."""
> + # Check if a default route exists and exit if it does
> + out, _ = util.subp(['ip', 'route', 'show', '0.0.0.0/0'], capture=True)
> + if 'default' in out:
> + LOG.debug(
> + 'Skip ephemeral route setup. %s already has default route: %s',
> + self.interface, out.strip())
> + return
> + util.subp(
> + ['ip', '-4', 'route', 'add', self.router, 'dev', self.interface,
> + 'src', self.ip], capture=True)
> + self.cleanup_cmds.insert(
> + 0,
> + ['ip', '-4', 'route', 'del', self.router, 'dev', self.interface,
> + 'src', self.ip])
> + util.subp(
> + ['ip', '-4', 'route', 'add', 'default', 'via', self.router,
> + 'dev', self.interface], capture=True)
> + self.cleanup_cmds.insert(
> + 0, ['ip', '-4', 'route', 'del', 'default', 'dev', self.interface])
> +
> +# vi: ts=4 expandtab
--
https://code.launchpad.net/~i.galic/cloud-init/+git/cloud-init/+merge/358228
Your team cloud-init commiters is requested to review the proposed merge of ~i.galic/cloud-init:refactor/net-fbsd into cloud-init:master.
References