cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #05840
Re: [Merge] ~i.galic/cloud-init:refactor/net-fbsd into cloud-init:master
Review: Needs Fixing
Here's some parts i'm unhappy about myself, i hope you can help me fix them!
Diff comments:
> diff --git a/cloudinit/net/common.py b/cloudinit/net/common.py
> new file mode 100644
> index 0000000..579739c
> --- /dev/null
> +++ b/cloudinit/net/common.py
> @@ -0,0 +1,40 @@
> +# Copyright (C) 2018 Canonical Ltd.
> +#
> +# This file is part of cloud-init. See LICENSE file for license information.
> +"""
> +This module is used for all high-level functions common to linux & freebsd,
> +that are also used in linux & freebsd!
> +They cannot be put them into net/__init__ because otherwise we'd end up with
> +cyclic imports.
> +"""
> +
> +from . import LO_DEVS, get_devicelist, interface_has_own_mac, is_bridge, is_vlan, get_interface_mac, device_driver, device_devid
> +
> +
> +def get_interfaces():
it would be nice to be able to move this function back into net/__init__.py
however, it's practically impossible to import then:
```
File "/home/meena/src/cloud/cloud-init/cloudinit/net/__init__.py", line 20, in <module>
from cloudinit.net import linux as netimpl
File "/home/meena/src/cloud/cloud-init/cloudinit/net/linux.py", line 12, in <module>
from cloudinit.net import get_interfaces
ImportError: cannot import name 'get_interfaces' from 'cloudinit.net' (/home/meena/src/cloud/cloud-init/cloudinit/net/__init__.py)
```
> + """Return list of interface tuples (name, mac, driver, device_id)
> +
> + Bridges and any devices that have a 'stolen' mac are excluded."""
> + ret = []
> + devs = get_devicelist()
> + # 16 somewhat arbitrarily chosen. Normally a mac is 6 '00:' tokens.
> + zero_mac = ':'.join(('00', ) * 16)
> + for name in devs:
> + if not interface_has_own_mac(name):
> + continue
> + if is_bridge(name):
> + continue
> + if is_vlan(name):
> + continue
> + mac = get_interface_mac(name)
> + # some devices may not have a mac (tun0)
> + if not mac:
> + continue
> + # skip nics that have no mac (00:00....)
> + if name not in LO_DEVS and mac == zero_mac[:len(mac)]:
> + continue
> + ret.append((name, mac, device_driver(name), device_devid(name)))
> + return ret
> +
> +
> +# vi: ts=4 expandtab
> 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 '':
i may have (wrongly?) duplicated this code
similar code should also exist distro/freebsd.py
> + 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")
this is of course wrong!
this code already exists in distros/freebsd.py
However, i really don't quite understand how the call-hierarchy there works.
> +
> +
> +def _rename_interfaces(renames,
> + strict_present=True,
> + strict_busy=True,
> + current_info=None):
> +
> + raise NotImplemented("TODO")
again, this already exists in distros/freebsd.py
> +
> +
> +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
i have no idea what an `ib` is, or how to find its hwaddr 😅
> +
> +
> +
> +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/tests/test_init.py b/cloudinit/net/tests/test_init.py
> index f55c31e..5da76ef 100644
> --- a/cloudinit/net/tests/test_init.py
> +++ b/cloudinit/net/tests/test_init.py
> @@ -14,542 +14,6 @@ from cloudinit.util import ensure_file, write_file, ProcessExecutionError
> from cloudinit.tests.helpers import CiTestCase, HttprettyTestCase
>
>
> -class TestSysDevPath(CiTestCase):
> -
i'm really not quite sure why all of this is still removed…
i was fairly certain i had reverted this part.
> - def test_sys_dev_path(self):
> - """sys_dev_path returns a path under SYS_CLASS_NET for a device."""
> - dev = 'something'
> - path = 'attribute'
> - expected = net.SYS_CLASS_NET + dev + '/' + path
> - self.assertEqual(expected, net.sys_dev_path(dev, path))
> -
> - def test_sys_dev_path_without_path(self):
> - """When path param isn't provided it defaults to empty string."""
> - dev = 'something'
> - expected = net.SYS_CLASS_NET + dev + '/'
> - self.assertEqual(expected, net.sys_dev_path(dev))
> -
> -
> -class TestReadSysNet(CiTestCase):
> - with_logs = True
> -
> - def setUp(self):
> - super(TestReadSysNet, self).setUp()
> - sys_mock = mock.patch('cloudinit.net.get_sys_class_path')
> - self.m_sys_path = sys_mock.start()
> - self.sysdir = self.tmp_dir() + '/'
> - self.m_sys_path.return_value = self.sysdir
> - self.addCleanup(sys_mock.stop)
> -
> - def test_read_sys_net_strips_contents_of_sys_path(self):
> - """read_sys_net strips whitespace from the contents of a sys file."""
> - content = 'some stuff with trailing whitespace\t\r\n'
> - write_file(os.path.join(self.sysdir, 'dev', 'attr'), content)
> - self.assertEqual(content.strip(), net.read_sys_net('dev', 'attr'))
> -
> - def test_read_sys_net_reraises_oserror(self):
> - """read_sys_net raises OSError/IOError when file doesn't exist."""
> - # Non-specific Exception because versions of python OSError vs IOError.
> - with self.assertRaises(Exception) as context_manager: # noqa: H202
> - net.read_sys_net('dev', 'attr')
> - error = context_manager.exception
> - self.assertIn('No such file or directory', str(error))
> -
> - def test_read_sys_net_handles_error_with_on_enoent(self):
> - """read_sys_net handles OSError/IOError with on_enoent if provided."""
> - handled_errors = []
> -
> - def on_enoent(e):
> - handled_errors.append(e)
> -
> - net.read_sys_net('dev', 'attr', on_enoent=on_enoent)
> - error = handled_errors[0]
> - self.assertIsInstance(error, Exception)
> - self.assertIn('No such file or directory', str(error))
> -
> - def test_read_sys_net_translates_content(self):
> - """read_sys_net translates content when translate dict is provided."""
> - content = "you're welcome\n"
> - write_file(os.path.join(self.sysdir, 'dev', 'attr'), content)
> - translate = {"you're welcome": 'de nada'}
> - self.assertEqual(
> - 'de nada',
> - net.read_sys_net('dev', 'attr', translate=translate))
> -
> - def test_read_sys_net_errors_on_translation_failures(self):
> - """read_sys_net raises a KeyError and logs details on failure."""
> - content = "you're welcome\n"
> - write_file(os.path.join(self.sysdir, 'dev', 'attr'), content)
> - with self.assertRaises(KeyError) as context_manager:
> - net.read_sys_net('dev', 'attr', translate={})
> - error = context_manager.exception
> - self.assertEqual('"you\'re welcome"', str(error))
> - self.assertIn(
> - "Found unexpected (not translatable) value 'you're welcome' in "
> - "'{0}dev/attr".format(self.sysdir),
> - self.logs.getvalue())
> -
> - def test_read_sys_net_handles_handles_with_onkeyerror(self):
> - """read_sys_net handles translation errors calling on_keyerror."""
> - content = "you're welcome\n"
> - write_file(os.path.join(self.sysdir, 'dev', 'attr'), content)
> - handled_errors = []
> -
> - def on_keyerror(e):
> - handled_errors.append(e)
> -
> - net.read_sys_net('dev', 'attr', translate={}, on_keyerror=on_keyerror)
> - error = handled_errors[0]
> - self.assertIsInstance(error, KeyError)
> - self.assertEqual('"you\'re welcome"', str(error))
> -
> - def test_read_sys_net_safe_false_on_translate_failure(self):
> - """read_sys_net_safe returns False on translation failures."""
> - content = "you're welcome\n"
> - write_file(os.path.join(self.sysdir, 'dev', 'attr'), content)
> - self.assertFalse(net.read_sys_net_safe('dev', 'attr', translate={}))
> -
> - def test_read_sys_net_safe_returns_false_on_noent_failure(self):
> - """read_sys_net_safe returns False on file not found failures."""
> - self.assertFalse(net.read_sys_net_safe('dev', 'attr'))
> -
> - def test_read_sys_net_int_returns_none_on_error(self):
> - """read_sys_net_safe returns None on failures."""
> - self.assertFalse(net.read_sys_net_int('dev', 'attr'))
> -
> - def test_read_sys_net_int_returns_none_on_valueerror(self):
> - """read_sys_net_safe returns None when content is not an int."""
> - write_file(os.path.join(self.sysdir, 'dev', 'attr'), 'NOTINT\n')
> - self.assertFalse(net.read_sys_net_int('dev', 'attr'))
> -
> - def test_read_sys_net_int_returns_integer_from_content(self):
> - """read_sys_net_safe returns None on failures."""
> - write_file(os.path.join(self.sysdir, 'dev', 'attr'), '1\n')
> - self.assertEqual(1, net.read_sys_net_int('dev', 'attr'))
> -
> - def test_is_up_true(self):
> - """is_up is True if sys/net/devname/operstate is 'up' or 'unknown'."""
> - for state in ['up', 'unknown']:
> - write_file(os.path.join(self.sysdir, 'eth0', 'operstate'), state)
> - self.assertTrue(net.is_up('eth0'))
> -
> - def test_is_up_false(self):
> - """is_up is False if sys/net/devname/operstate is 'down' or invalid."""
> - for state in ['down', 'incomprehensible']:
> - write_file(os.path.join(self.sysdir, 'eth0', 'operstate'), state)
> - self.assertFalse(net.is_up('eth0'))
> -
> - def test_is_wireless(self):
> - """is_wireless is True when /sys/net/devname/wireless exists."""
> - self.assertFalse(net.is_wireless('eth0'))
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'wireless'))
> - self.assertTrue(net.is_wireless('eth0'))
> -
> - def test_is_bridge(self):
> - """is_bridge is True when /sys/net/devname/bridge exists."""
> - self.assertFalse(net.is_bridge('eth0'))
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'bridge'))
> - self.assertTrue(net.is_bridge('eth0'))
> -
> - def test_is_bond(self):
> - """is_bond is True when /sys/net/devname/bonding exists."""
> - self.assertFalse(net.is_bond('eth0'))
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'bonding'))
> - self.assertTrue(net.is_bond('eth0'))
> -
> - def test_is_vlan(self):
> - """is_vlan is True when /sys/net/devname/uevent has DEVTYPE=vlan."""
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'uevent'))
> - self.assertFalse(net.is_vlan('eth0'))
> - content = 'junk\nDEVTYPE=vlan\njunk\n'
> - write_file(os.path.join(self.sysdir, 'eth0', 'uevent'), content)
> - self.assertTrue(net.is_vlan('eth0'))
> -
> - def test_is_connected_when_physically_connected(self):
> - """is_connected is True when /sys/net/devname/iflink reports 2."""
> - self.assertFalse(net.is_connected('eth0'))
> - write_file(os.path.join(self.sysdir, 'eth0', 'iflink'), "2")
> - self.assertTrue(net.is_connected('eth0'))
> -
> - def test_is_connected_when_wireless_and_carrier_active(self):
> - """is_connected is True if wireless /sys/net/devname/carrier is 1."""
> - self.assertFalse(net.is_connected('eth0'))
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'wireless'))
> - self.assertFalse(net.is_connected('eth0'))
> - write_file(os.path.join(self.sysdir, 'eth0', 'carrier'), "1")
> - self.assertTrue(net.is_connected('eth0'))
> -
> - def test_is_physical(self):
> - """is_physical is True when /sys/net/devname/device exists."""
> - self.assertFalse(net.is_physical('eth0'))
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'device'))
> - self.assertTrue(net.is_physical('eth0'))
> -
> - def test_is_present(self):
> - """is_present is True when /sys/net/devname exists."""
> - self.assertFalse(net.is_present('eth0'))
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'device'))
> - self.assertTrue(net.is_present('eth0'))
> -
> -
> -class TestGenerateFallbackConfig(CiTestCase):
> -
> - def setUp(self):
> - super(TestGenerateFallbackConfig, self).setUp()
> - sys_mock = mock.patch('cloudinit.net.get_sys_class_path')
> - self.m_sys_path = sys_mock.start()
> - self.sysdir = self.tmp_dir() + '/'
> - self.m_sys_path.return_value = self.sysdir
> - self.addCleanup(sys_mock.stop)
> - self.add_patch('cloudinit.net.util.is_container', 'm_is_container',
> - return_value=False)
> - self.add_patch('cloudinit.net.util.udevadm_settle', 'm_settle')
> -
> - def test_generate_fallback_finds_connected_eth_with_mac(self):
> - """generate_fallback_config finds any connected device with a mac."""
> - write_file(os.path.join(self.sysdir, 'eth0', 'carrier'), '1')
> - write_file(os.path.join(self.sysdir, 'eth1', 'carrier'), '1')
> - mac = 'aa:bb:cc:aa:bb:cc'
> - write_file(os.path.join(self.sysdir, 'eth1', 'address'), mac)
> - expected = {
> - 'config': [{'type': 'physical', 'mac_address': mac,
> - 'name': 'eth1', 'subnets': [{'type': 'dhcp'}]}],
> - 'version': 1}
> - self.assertEqual(expected, net.generate_fallback_config())
> -
> - def test_generate_fallback_finds_dormant_eth_with_mac(self):
> - """generate_fallback_config finds any dormant device with a mac."""
> - write_file(os.path.join(self.sysdir, 'eth0', 'dormant'), '1')
> - mac = 'aa:bb:cc:aa:bb:cc'
> - write_file(os.path.join(self.sysdir, 'eth0', 'address'), mac)
> - expected = {
> - 'config': [{'type': 'physical', 'mac_address': mac,
> - 'name': 'eth0', 'subnets': [{'type': 'dhcp'}]}],
> - 'version': 1}
> - self.assertEqual(expected, net.generate_fallback_config())
> -
> - def test_generate_fallback_finds_eth_by_operstate(self):
> - """generate_fallback_config finds any dormant device with a mac."""
> - mac = 'aa:bb:cc:aa:bb:cc'
> - write_file(os.path.join(self.sysdir, 'eth0', 'address'), mac)
> - expected = {
> - 'config': [{'type': 'physical', 'mac_address': mac,
> - 'name': 'eth0', 'subnets': [{'type': 'dhcp'}]}],
> - 'version': 1}
> - valid_operstates = ['dormant', 'down', 'lowerlayerdown', 'unknown']
> - for state in valid_operstates:
> - write_file(os.path.join(self.sysdir, 'eth0', 'operstate'), state)
> - self.assertEqual(expected, net.generate_fallback_config())
> - write_file(os.path.join(self.sysdir, 'eth0', 'operstate'), 'noworky')
> - self.assertIsNone(net.generate_fallback_config())
> -
> - def test_generate_fallback_config_skips_veth(self):
> - """generate_fallback_config will skip any veth interfaces."""
> - # A connected veth which gets ignored
> - write_file(os.path.join(self.sysdir, 'veth0', 'carrier'), '1')
> - self.assertIsNone(net.generate_fallback_config())
> -
> - def test_generate_fallback_config_skips_bridges(self):
> - """generate_fallback_config will skip any bridges interfaces."""
> - # A connected veth which gets ignored
> - write_file(os.path.join(self.sysdir, 'eth0', 'carrier'), '1')
> - mac = 'aa:bb:cc:aa:bb:cc'
> - write_file(os.path.join(self.sysdir, 'eth0', 'address'), mac)
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'bridge'))
> - self.assertIsNone(net.generate_fallback_config())
> -
> - def test_generate_fallback_config_skips_bonds(self):
> - """generate_fallback_config will skip any bonded interfaces."""
> - # A connected veth which gets ignored
> - write_file(os.path.join(self.sysdir, 'eth0', 'carrier'), '1')
> - mac = 'aa:bb:cc:aa:bb:cc'
> - write_file(os.path.join(self.sysdir, 'eth0', 'address'), mac)
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'bonding'))
> - self.assertIsNone(net.generate_fallback_config())
> -
> -
> -class TestGetDeviceList(CiTestCase):
> -
> - def setUp(self):
> - super(TestGetDeviceList, self).setUp()
> - sys_mock = mock.patch('cloudinit.net.get_sys_class_path')
> - self.m_sys_path = sys_mock.start()
> - self.sysdir = self.tmp_dir() + '/'
> - self.m_sys_path.return_value = self.sysdir
> - self.addCleanup(sys_mock.stop)
> -
> - def test_get_devicelist_raise_oserror(self):
> - """get_devicelist raise any non-ENOENT OSerror."""
> - error = OSError('Can not do it')
> - error.errno = errno.EPERM # Set non-ENOENT
> - self.m_sys_path.side_effect = error
> - with self.assertRaises(OSError) as context_manager:
> - net.get_devicelist()
> - exception = context_manager.exception
> - self.assertEqual('Can not do it', str(exception))
> -
> - def test_get_devicelist_empty_without_sys_net(self):
> - """get_devicelist returns empty list when missing SYS_CLASS_NET."""
> - self.m_sys_path.return_value = 'idontexist'
> - self.assertEqual([], net.get_devicelist())
> -
> - def test_get_devicelist_empty_with_no_devices_in_sys_net(self):
> - """get_devicelist returns empty directoty listing for SYS_CLASS_NET."""
> - self.assertEqual([], net.get_devicelist())
> -
> - def test_get_devicelist_lists_any_subdirectories_in_sys_net(self):
> - """get_devicelist returns a directory listing for SYS_CLASS_NET."""
> - write_file(os.path.join(self.sysdir, 'eth0', 'operstate'), 'up')
> - write_file(os.path.join(self.sysdir, 'eth1', 'operstate'), 'up')
> - self.assertItemsEqual(['eth0', 'eth1'], net.get_devicelist())
> -
> -
> -class TestGetInterfaceMAC(CiTestCase):
> -
> - def setUp(self):
> - super(TestGetInterfaceMAC, self).setUp()
> - sys_mock = mock.patch('cloudinit.net.get_sys_class_path')
> - self.m_sys_path = sys_mock.start()
> - self.sysdir = self.tmp_dir() + '/'
> - self.m_sys_path.return_value = self.sysdir
> - self.addCleanup(sys_mock.stop)
> -
> - def test_get_interface_mac_false_with_no_mac(self):
> - """get_device_list returns False when no mac is reported."""
> - ensure_file(os.path.join(self.sysdir, 'eth0', 'bonding'))
> - mac_path = os.path.join(self.sysdir, 'eth0', 'address')
> - self.assertFalse(os.path.exists(mac_path))
> - self.assertFalse(net.get_interface_mac('eth0'))
> -
> - def test_get_interface_mac(self):
> - """get_interfaces returns the mac from SYS_CLASS_NET/dev/address."""
> - mac = 'aa:bb:cc:aa:bb:cc'
> - write_file(os.path.join(self.sysdir, 'eth1', 'address'), mac)
> - self.assertEqual(mac, net.get_interface_mac('eth1'))
> -
> - def test_get_interface_mac_grabs_bonding_address(self):
> - """get_interfaces returns the source device mac for bonded devices."""
> - source_dev_mac = 'aa:bb:cc:aa:bb:cc'
> - bonded_mac = 'dd:ee:ff:dd:ee:ff'
> - write_file(os.path.join(self.sysdir, 'eth1', 'address'), bonded_mac)
> - write_file(
> - os.path.join(self.sysdir, 'eth1', 'bonding_slave', 'perm_hwaddr'),
> - source_dev_mac)
> - self.assertEqual(source_dev_mac, net.get_interface_mac('eth1'))
> -
> - def test_get_interfaces_empty_list_without_sys_net(self):
> - """get_interfaces returns an empty list when missing SYS_CLASS_NET."""
> - self.m_sys_path.return_value = 'idontexist'
> - self.assertEqual([], net.get_interfaces())
> -
> - def test_get_interfaces_by_mac_skips_empty_mac(self):
> - """Ignore 00:00:00:00:00:00 addresses from get_interfaces_by_mac."""
> - empty_mac = '00:00:00:00:00:00'
> - mac = 'aa:bb:cc:aa:bb:cc'
> - write_file(os.path.join(self.sysdir, 'eth1', 'address'), empty_mac)
> - write_file(os.path.join(self.sysdir, 'eth1', 'addr_assign_type'), '0')
> - write_file(os.path.join(self.sysdir, 'eth2', 'addr_assign_type'), '0')
> - write_file(os.path.join(self.sysdir, 'eth2', 'address'), mac)
> - expected = [('eth2', 'aa:bb:cc:aa:bb:cc', None, None)]
> - self.assertEqual(expected, net.get_interfaces())
> -
> - def test_get_interfaces_by_mac_skips_missing_mac(self):
> - """Ignore interfaces without an address from get_interfaces_by_mac."""
> - write_file(os.path.join(self.sysdir, 'eth1', 'addr_assign_type'), '0')
> - address_path = os.path.join(self.sysdir, 'eth1', 'address')
> - self.assertFalse(os.path.exists(address_path))
> - mac = 'aa:bb:cc:aa:bb:cc'
> - write_file(os.path.join(self.sysdir, 'eth2', 'addr_assign_type'), '0')
> - write_file(os.path.join(self.sysdir, 'eth2', 'address'), mac)
> - expected = [('eth2', 'aa:bb:cc:aa:bb:cc', None, None)]
> - self.assertEqual(expected, net.get_interfaces())
> -
> -
> -class TestInterfaceHasOwnMAC(CiTestCase):
> -
> - def setUp(self):
> - super(TestInterfaceHasOwnMAC, self).setUp()
> - sys_mock = mock.patch('cloudinit.net.get_sys_class_path')
> - self.m_sys_path = sys_mock.start()
> - self.sysdir = self.tmp_dir() + '/'
> - self.m_sys_path.return_value = self.sysdir
> - self.addCleanup(sys_mock.stop)
> -
> - def test_interface_has_own_mac_false_when_stolen(self):
> - """Return False from interface_has_own_mac when address is stolen."""
> - write_file(os.path.join(self.sysdir, 'eth1', 'addr_assign_type'), '2')
> - self.assertFalse(net.interface_has_own_mac('eth1'))
> -
> - def test_interface_has_own_mac_true_when_not_stolen(self):
> - """Return False from interface_has_own_mac when mac isn't stolen."""
> - valid_assign_types = ['0', '1', '3']
> - assign_path = os.path.join(self.sysdir, 'eth1', 'addr_assign_type')
> - for _type in valid_assign_types:
> - write_file(assign_path, _type)
> - self.assertTrue(net.interface_has_own_mac('eth1'))
> -
> - def test_interface_has_own_mac_strict_errors_on_absent_assign_type(self):
> - """When addr_assign_type is absent, interface_has_own_mac errors."""
> - with self.assertRaises(ValueError):
> - net.interface_has_own_mac('eth1', strict=True)
> -
> -
> -@mock.patch('cloudinit.net.util.subp')
> -class TestEphemeralIPV4Network(CiTestCase):
> -
> - with_logs = True
> -
> - def setUp(self):
> - super(TestEphemeralIPV4Network, self).setUp()
> - sys_mock = mock.patch('cloudinit.net.get_sys_class_path')
> - self.m_sys_path = sys_mock.start()
> - self.sysdir = self.tmp_dir() + '/'
> - self.m_sys_path.return_value = self.sysdir
> - self.addCleanup(sys_mock.stop)
> -
> - def test_ephemeral_ipv4_network_errors_on_missing_params(self, m_subp):
> - """No required params for EphemeralIPv4Network can be None."""
> - required_params = {
> - 'interface': 'eth0', 'ip': '192.168.2.2',
> - 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255'}
> - for key in required_params.keys():
> - params = copy.deepcopy(required_params)
> - params[key] = None
> - with self.assertRaises(ValueError) as context_manager:
> - net.EphemeralIPv4Network(**params)
> - error = context_manager.exception
> - self.assertIn('Cannot init network on', str(error))
> - self.assertEqual(0, m_subp.call_count)
> -
> - def test_ephemeral_ipv4_network_errors_invalid_mask_prefix(self, m_subp):
> - """Raise an error when prefix_or_mask is not a netmask or prefix."""
> - params = {
> - 'interface': 'eth0', 'ip': '192.168.2.2',
> - 'broadcast': '192.168.2.255'}
> - invalid_masks = ('invalid', 'invalid.', '123.123.123')
> - for error_val in invalid_masks:
> - params['prefix_or_mask'] = error_val
> - with self.assertRaises(ValueError) as context_manager:
> - with net.EphemeralIPv4Network(**params):
> - pass
> - error = context_manager.exception
> - self.assertIn('Cannot setup network: netmask', str(error))
> - self.assertEqual(0, m_subp.call_count)
> -
> - def test_ephemeral_ipv4_network_performs_teardown(self, m_subp):
> - """EphemeralIPv4Network performs teardown on the device if setup."""
> - expected_setup_calls = [
> - mock.call(
> - ['ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24',
> - 'broadcast', '192.168.2.255', 'dev', 'eth0'],
> - capture=True, update_env={'LANG': 'C'}),
> - mock.call(
> - ['ip', '-family', 'inet', 'link', 'set', 'dev', 'eth0', 'up'],
> - capture=True)]
> - expected_teardown_calls = [
> - mock.call(
> - ['ip', '-family', 'inet', 'link', 'set', 'dev', 'eth0',
> - 'down'], capture=True),
> - mock.call(
> - ['ip', '-family', 'inet', 'addr', 'del', '192.168.2.2/24',
> - 'dev', 'eth0'], capture=True)]
> - params = {
> - 'interface': 'eth0', 'ip': '192.168.2.2',
> - 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255'}
> - with net.EphemeralIPv4Network(**params):
> - self.assertEqual(expected_setup_calls, m_subp.call_args_list)
> - m_subp.assert_has_calls(expected_teardown_calls)
> -
> - @mock.patch('cloudinit.net.readurl')
> - def test_ephemeral_ipv4_no_network_if_url_connectivity(
> - self, m_readurl, m_subp):
> - """No network setup is performed if we can successfully connect to
> - connectivity_url."""
> - params = {
> - 'interface': 'eth0', 'ip': '192.168.2.2',
> - 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255',
> - 'connectivity_url': 'http://example.org/index.html'}
> -
> - with net.EphemeralIPv4Network(**params):
> - self.assertEqual([mock.call('http://example.org/index.html',
> - timeout=5)], m_readurl.call_args_list)
> - # Ensure that no teardown happens:
> - m_subp.assert_has_calls([])
> -
> - def test_ephemeral_ipv4_network_noop_when_configured(self, m_subp):
> - """EphemeralIPv4Network handles exception when address is setup.
> -
> - It performs no cleanup as the interface was already setup.
> - """
> - params = {
> - 'interface': 'eth0', 'ip': '192.168.2.2',
> - 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255'}
> - m_subp.side_effect = ProcessExecutionError(
> - '', 'RTNETLINK answers: File exists', 2)
> - expected_calls = [
> - mock.call(
> - ['ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24',
> - 'broadcast', '192.168.2.255', 'dev', 'eth0'],
> - capture=True, update_env={'LANG': 'C'})]
> - with net.EphemeralIPv4Network(**params):
> - pass
> - self.assertEqual(expected_calls, m_subp.call_args_list)
> - self.assertIn(
> - 'Skip ephemeral network setup, eth0 already has address',
> - self.logs.getvalue())
> -
> - def test_ephemeral_ipv4_network_with_prefix(self, m_subp):
> - """EphemeralIPv4Network takes a valid prefix to setup the network."""
> - params = {
> - 'interface': 'eth0', 'ip': '192.168.2.2',
> - 'prefix_or_mask': '24', 'broadcast': '192.168.2.255'}
> - for prefix_val in ['24', 16]: # prefix can be int or string
> - params['prefix_or_mask'] = prefix_val
> - with net.EphemeralIPv4Network(**params):
> - pass
> - m_subp.assert_has_calls([mock.call(
> - ['ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24',
> - 'broadcast', '192.168.2.255', 'dev', 'eth0'],
> - capture=True, update_env={'LANG': 'C'})])
> - m_subp.assert_has_calls([mock.call(
> - ['ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/16',
> - 'broadcast', '192.168.2.255', 'dev', 'eth0'],
> - capture=True, update_env={'LANG': 'C'})])
> -
> - def test_ephemeral_ipv4_network_with_new_default_route(self, m_subp):
> - """Add the route when router is set and no default route exists."""
> - params = {
> - 'interface': 'eth0', 'ip': '192.168.2.2',
> - 'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255',
> - 'router': '192.168.2.1'}
> - m_subp.return_value = '', '' # Empty response from ip route gw check
> - expected_setup_calls = [
> - mock.call(
> - ['ip', '-family', 'inet', 'addr', 'add', '192.168.2.2/24',
> - 'broadcast', '192.168.2.255', 'dev', 'eth0'],
> - capture=True, update_env={'LANG': 'C'}),
> - mock.call(
> - ['ip', '-family', 'inet', 'link', 'set', 'dev', 'eth0', 'up'],
> - capture=True),
> - mock.call(
> - ['ip', 'route', 'show', '0.0.0.0/0'], capture=True),
> - mock.call(['ip', '-4', 'route', 'add', '192.168.2.1',
> - 'dev', 'eth0', 'src', '192.168.2.2'], capture=True),
> - mock.call(
> - ['ip', '-4', 'route', 'add', 'default', 'via',
> - '192.168.2.1', 'dev', 'eth0'], capture=True)]
> - expected_teardown_calls = [
> - mock.call(['ip', '-4', 'route', 'del', 'default', 'dev', 'eth0'],
> - capture=True),
> - mock.call(['ip', '-4', 'route', 'del', '192.168.2.1',
> - 'dev', 'eth0', 'src', '192.168.2.2'], capture=True),
> - ]
> -
> - with net.EphemeralIPv4Network(**params):
> - self.assertEqual(expected_setup_calls, m_subp.call_args_list)
> - m_subp.assert_has_calls(expected_teardown_calls)
> -
> -
> class TestApplyNetworkCfgNames(CiTestCase):
> V1_CONFIG = textwrap.dedent("""\
> version: 1
--
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