From 3f12012eba2aabb6ca7b3ef70bc33a4aa1edada4 Mon Sep 17 00:00:00 2001 From: Robert Schweikert Date: Mon, 28 Jan 2019 17:06:58 +0000 Subject: sysconfig: On SUSE, use STARTMODE instead of ONBOOT ONBOOT is not recognized on openSUSE and SUSE Linux Enterprise, add the STARTMODE setting LP: #1799540 --- tests/unittests/test_distros/test_netconfig.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 6e339355..e986b593 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -468,6 +468,7 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): NETMASK=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -476,6 +477,7 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): DEVICE=eth1 NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -499,6 +501,7 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): IPV6_DEFAULTGW=2607:f0d0:1002:0011::1 NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -507,6 +510,7 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): DEVICE=eth1 NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -559,6 +563,7 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): NETMASK=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -567,6 +572,7 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): DEVICE=eth1 NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -587,6 +593,7 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): IPV6_DEFAULTGW=2607:f0d0:1002:0011::1 NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -595,6 +602,7 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): DEVICE=eth1 NM_CONTROLLED=no ONBOOT=yes + STARTMODE=auto TYPE=Ethernet USERCTL=no """), -- cgit v1.2.3 From 94a64529dccebd8fe8c7969370b8696e46023fbd Mon Sep 17 00:00:00 2001 From: Paride Legovini Date: Wed, 30 Jan 2019 15:38:56 +0000 Subject: Resolve flake8 comparison and pycodestyle over-ident issues Fixes: - flake8: use ==/!= to compare str, bytes, and int literals - pycodestyle: E117 over-indented --- cloudinit/config/schema.py | 2 +- cloudinit/handlers/upstart_job.py | 2 +- cloudinit/sources/DataSourceEc2.py | 2 +- cloudinit/stages.py | 4 ++-- cloudinit/url_helper.py | 2 +- tests/unittests/test_distros/test_netconfig.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py index 080a6d06..807c3eee 100644 --- a/cloudinit/config/schema.py +++ b/cloudinit/config/schema.py @@ -367,7 +367,7 @@ def handle_schema_args(name, args): if not args.annotate: error(str(e)) except RuntimeError as e: - error(str(e)) + error(str(e)) else: print("Valid cloud-config file {0}".format(args.config_file)) if args.doc: diff --git a/cloudinit/handlers/upstart_job.py b/cloudinit/handlers/upstart_job.py index 83fb0724..003cad60 100644 --- a/cloudinit/handlers/upstart_job.py +++ b/cloudinit/handlers/upstart_job.py @@ -89,7 +89,7 @@ def _has_suitable_upstart(): util.subp(["dpkg", "--compare-versions", dpkg_ver, "ge", good]) return True except util.ProcessExecutionError as e: - if e.exit_code is 1: + if e.exit_code == 1: pass else: util.logexc(LOG, "dpkg --compare-versions failed [%s]", diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py index 9ccf2cdc..eb6f27b2 100644 --- a/cloudinit/sources/DataSourceEc2.py +++ b/cloudinit/sources/DataSourceEc2.py @@ -442,7 +442,7 @@ def identify_aws(data): if (data['uuid'].startswith('ec2') and (data['uuid_source'] == 'hypervisor' or data['uuid'] == data['serial'])): - return CloudNames.AWS + return CloudNames.AWS return None diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 8a064124..da7d349a 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -548,11 +548,11 @@ class Init(object): with events.ReportEventStack("consume-user-data", "reading and applying user-data", parent=self.reporter): - self._consume_userdata(frequency) + self._consume_userdata(frequency) with events.ReportEventStack("consume-vendor-data", "reading and applying vendor-data", parent=self.reporter): - self._consume_vendordata(frequency) + self._consume_vendordata(frequency) # Perform post-consumption adjustments so that # modules that run during the init stage reflect diff --git a/cloudinit/url_helper.py b/cloudinit/url_helper.py index 396d69ae..0af0d9e3 100644 --- a/cloudinit/url_helper.py +++ b/cloudinit/url_helper.py @@ -521,7 +521,7 @@ class OauthUrlHelper(object): if extra_exception_cb: ret = extra_exception_cb(msg, exception) finally: - self.exception_cb(msg, exception) + self.exception_cb(msg, exception) return ret def _headers_cb(self, extra_headers_cb, url): diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index e986b593..e4530408 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -407,7 +407,7 @@ class TestNetCfgDistroUbuntuNetplan(TestNetCfgDistroBase): self.assertEqual(0o644, get_mode(cfgpath, tmpd)) def netplan_path(self): - return '/etc/netplan/50-cloud-init.yaml' + return '/etc/netplan/50-cloud-init.yaml' def test_apply_network_config_v1_to_netplan_ub(self): expected_cfgs = { -- cgit v1.2.3 From bb0b6f1d4e587d74a6e8fe17fa1c4dc3cf5287f7 Mon Sep 17 00:00:00 2001 From: Robert Schweikert Date: Mon, 25 Mar 2019 15:53:12 +0000 Subject: net/sysconfig: write out SUSE-compatible IPv6 config For writing IPv6 addresses to ifcfg-* the name "IPV6ADDR" is used. For secondary IPs the value for "IPV6ADDR_SECONDARIES" is set. On SUSE based distributions the names "IPADDR6" and "IPADDR6_$SOMELABEL" need to be used. --- cloudinit/net/sysconfig.py | 3 +++ tests/unittests/test_distros/test_netconfig.py | 2 ++ tests/unittests/test_net.py | 14 ++++++++++++++ 3 files changed, 19 insertions(+) (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index e59753d5..09983929 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -386,10 +386,13 @@ class Renderer(renderer.Renderer): ipv6_cidr = "%s/%s" % (subnet['address'], subnet['prefix']) if ipv6_index == 0: iface_cfg['IPV6ADDR'] = ipv6_cidr + iface_cfg['IPADDR6'] = ipv6_cidr elif ipv6_index == 1: iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr + iface_cfg['IPADDR6_0'] = ipv6_cidr else: iface_cfg['IPV6ADDR_SECONDARIES'] += " " + ipv6_cidr + iface_cfg['IPADDR6_%d' % ipv6_index] = ipv6_cidr else: ipv4_index = ipv4_index + 1 suff = "" if ipv4_index == 0 else str(ipv4_index) diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index e4530408..c3c0c8c5 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -496,6 +496,7 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): BOOTPROTO=none DEFROUTE=yes DEVICE=eth0 + IPADDR6=2607:f0d0:1002:0011::2/64 IPV6ADDR=2607:f0d0:1002:0011::2/64 IPV6INIT=yes IPV6_DEFAULTGW=2607:f0d0:1002:0011::1 @@ -588,6 +589,7 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): BOOTPROTO=none DEFROUTE=yes DEVICE=eth0 + IPADDR6=2607:f0d0:1002:0011::2/64 IPV6ADDR=2607:f0d0:1002:0011::2/64 IPV6INIT=yes IPV6_DEFAULTGW=2607:f0d0:1002:0011::1 diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 1b415b00..fd03deb6 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -691,6 +691,9 @@ DEVICE=eth0 GATEWAY=172.19.3.254 HWADDR=fa:16:3e:ed:9a:59 IPADDR=172.19.1.34 +IPADDR6=2001:DB8::10/64 +IPADDR6_0=2001:DB9::10/64 +IPADDR6_2=2001:DB10::10/64 IPV6ADDR=2001:DB8::10/64 IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" IPV6INIT=yes @@ -729,6 +732,9 @@ DEVICE=eth0 GATEWAY=172.19.3.254 HWADDR=fa:16:3e:ed:9a:59 IPADDR=172.19.1.34 +IPADDR6=2001:DB8::10/64 +IPADDR6_0=2001:DB9::10/64 +IPADDR6_2=2001:DB10::10/64 IPV6ADDR=2001:DB8::10/64 IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" IPV6INIT=yes @@ -980,6 +986,7 @@ NETWORK_CONFIGS = { BOOTPROTO=none DEVICE=iface0 IPADDR=192.168.14.2 + IPADDR6=2001:1::1/64 IPV6ADDR=2001:1::1/64 IPV6INIT=yes NETMASK=255.255.255.0 @@ -1249,6 +1256,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true DEFROUTE=yes DEVICE=br0 IPADDR=192.168.14.2 + IPADDR6=2001:1::1/64 IPV6ADDR=2001:1::1/64 IPV6INIT=yes IPV6_DEFAULTGW=2001:4800:78ff:1b::1 @@ -1690,6 +1698,7 @@ iface bond0 inet6 static MACADDR=aa:bb:cc:dd:e8:ff IPADDR=192.168.0.2 IPADDR1=192.168.1.2 + IPADDR6=2001:1::1/92 IPV6ADDR=2001:1::1/92 IPV6INIT=yes MTU=9000 @@ -1745,6 +1754,7 @@ iface bond0 inet6 static MACADDR=aa:bb:cc:dd:e8:ff IPADDR=192.168.0.2 IPADDR1=192.168.1.2 + IPADDR6=2001:1::1/92 IPV6ADDR=2001:1::1/92 IPV6INIT=yes MTU=9000 @@ -1835,6 +1845,7 @@ iface bond0 inet6 static GATEWAY=192.168.1.1 IPADDR=192.168.2.2 IPADDR1=192.168.1.2 + IPADDR6=2001:1::bbbb/96 IPV6ADDR=2001:1::bbbb/96 IPV6INIT=yes IPV6_DEFAULTGW=2001:1::1 @@ -1896,6 +1907,7 @@ iface bond0 inet6 static BRIDGE=br0 DEVICE=eth0 HWADDR=52:54:00:12:34:00 + IPADDR6=2001:1::100/96 IPV6ADDR=2001:1::100/96 IPV6INIT=yes NM_CONTROLLED=no @@ -1909,6 +1921,7 @@ iface bond0 inet6 static BRIDGE=br0 DEVICE=eth1 HWADDR=52:54:00:12:34:01 + IPADDR6=2001:1::101/96 IPV6ADDR=2001:1::101/96 IPV6INIT=yes NM_CONTROLLED=no @@ -2743,6 +2756,7 @@ USERCTL=no GATEWAY=192.168.42.1 HWADDR=52:54:00:ab:cd:ef IPADDR=192.168.42.100 + IPADDR6=2001:db8::100/32 IPV6ADDR=2001:db8::100/32 IPV6INIT=yes IPV6_DEFAULTGW=2001:db8::1 -- cgit v1.2.3 From a785462959e8746cc16609a159fee33dedd713b1 Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Tue, 16 Jul 2019 19:30:41 +0000 Subject: Support netplan renderer in Arch Linux Support is for now implemented in such a way that it will fall back to the old `_write_network()` if netplan is not available on the image. --- cloudinit/distros/arch.py | 14 +++++ tests/unittests/test_distros/test_netconfig.py | 86 ++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/cloudinit/distros/arch.py b/cloudinit/distros/arch.py index b814c8ba..9f89c5f9 100644 --- a/cloudinit/distros/arch.py +++ b/cloudinit/distros/arch.py @@ -12,6 +12,8 @@ from cloudinit import util from cloudinit.distros import net_util from cloudinit.distros.parsers.hostname import HostnameConf +from cloudinit.net.renderers import RendererNotFoundError + from cloudinit.settings import PER_INSTANCE import os @@ -24,6 +26,11 @@ class Distro(distros.Distro): network_conf_dir = "/etc/netctl" resolve_conf_fn = "/etc/resolv.conf" init_cmd = ['systemctl'] # init scripts + renderer_configs = { + "netplan": {"netplan_path": "/etc/netplan/50-cloud-init.yaml", + "netplan_header": "# generated by cloud-init\n", + "postcmds": True} + } def __init__(self, name, cfg, paths): distros.Distro.__init__(self, name, cfg, paths) @@ -50,6 +57,13 @@ class Distro(distros.Distro): self.update_package_sources() self.package_command('', pkgs=pkglist) + def _write_network_config(self, netconfig): + try: + return self._supported_write_network_config(netconfig) + except RendererNotFoundError: + # Fall back to old _write_network + raise NotImplementedError + def _write_network(self, settings): entries = net_util.translate_network(settings) LOG.debug("Translated ubuntu style network settings %s into %s", diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index c3c0c8c5..07b5c0a7 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -614,6 +614,92 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): expected_cfgs=expected_cfgs.copy()) +class TestNetCfgDistroArch(TestNetCfgDistroBase): + def setUp(self): + super(TestNetCfgDistroArch, self).setUp() + self.distro = self._get_distro('arch', renderers=['netplan']) + + def _apply_and_verify(self, apply_fn, config, expected_cfgs=None, + bringup=False, with_netplan=False): + if not expected_cfgs: + raise ValueError('expected_cfg must not be None') + + tmpd = None + with mock.patch('cloudinit.net.netplan.available', + return_value=with_netplan): + with self.reRooted(tmpd) as tmpd: + apply_fn(config, bringup) + + results = dir2dict(tmpd) + for cfgpath, expected in expected_cfgs.items(): + print("----------") + print(expected) + print("^^^^ expected | rendered VVVVVVV") + print(results[cfgpath]) + print("----------") + self.assertEqual(expected, results[cfgpath]) + self.assertEqual(0o644, get_mode(cfgpath, tmpd)) + + def netctl_path(self, iface): + return '/etc/netctl/%s' % iface + + def netplan_path(self): + return '/etc/netplan/50-cloud-init.yaml' + + def test_apply_network_config_v1_without_netplan(self): + # Note that this is in fact an invalid netctl config: + # "Address=None/None" + # But this is what the renderer has been writing out for a long time, + # and the test's purpose is to assert that the netctl renderer is + # still being used in absence of netplan, not the correctness of the + # rendered netctl config. + expected_cfgs = { + self.netctl_path('eth0'): dedent("""\ + Address=192.168.1.5/255.255.255.0 + Connection=ethernet + DNS=() + Gateway=192.168.1.254 + IP=static + Interface=eth0 + """), + self.netctl_path('eth1'): dedent("""\ + Address=None/None + Connection=ethernet + DNS=() + Gateway= + IP=dhcp + Interface=eth1 + """), + } + + # ub_distro.apply_network_config(V1_NET_CFG, False) + self._apply_and_verify(self.distro.apply_network_config, + V1_NET_CFG, + expected_cfgs=expected_cfgs.copy(), + with_netplan=False) + + def test_apply_network_config_v1_with_netplan(self): + expected_cfgs = { + self.netplan_path(): dedent("""\ + # generated by cloud-init + network: + version: 2 + ethernets: + eth0: + addresses: + - 192.168.1.5/24 + gateway4: 192.168.1.254 + eth1: + dhcp4: true + """), + } + + self._apply_and_verify(self.distro.apply_network_config, + V1_NET_CFG, + expected_cfgs=expected_cfgs.copy(), + with_netplan=True) + + def get_mode(path, target=None): return os.stat(util.target_path(target, path)).st_mode & 0o777 -- cgit v1.2.3 From 052d655cfba37243396c491a1e7892ba3736ab6f Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Fri, 27 Sep 2019 20:31:57 +0000 Subject: debian/ubuntu: add missing word to netplan/ENI header Specifically, add in "reboot" to make it clear what people should expect when modifying the file. This also renames the variable to indicate it is used for netplan and ENI, not just ENI. LP: #1845669 --- cloudinit/distros/debian.py | 11 ++++++----- cloudinit/distros/ubuntu.py | 4 ++-- tests/unittests/test_distros/test_netconfig.py | 18 +++++++++--------- 3 files changed, 17 insertions(+), 16 deletions(-) (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py index 0ad93ffe..cf082c73 100644 --- a/cloudinit/distros/debian.py +++ b/cloudinit/distros/debian.py @@ -29,9 +29,10 @@ APT_GET_WRAPPER = { 'enabled': 'auto', } -ENI_HEADER = """# This file is generated from information provided by -# the datasource. Changes to it will not persist across an instance. -# To disable cloud-init's network configuration capabilities, write a file +NETWORK_FILE_HEADER = """\ +# This file is generated from information provided by the datasource. Changes +# to it will not persist across an instance reboot. To disable cloud-init's +# network configuration capabilities, write a file # /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: # network: {config: disabled} """ @@ -48,9 +49,9 @@ class Distro(distros.Distro): } renderer_configs = { "eni": {"eni_path": network_conf_fn["eni"], - "eni_header": ENI_HEADER}, + "eni_header": NETWORK_FILE_HEADER}, "netplan": {"netplan_path": network_conf_fn["netplan"], - "netplan_header": ENI_HEADER, + "netplan_header": NETWORK_FILE_HEADER, "postcmds": True} } diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py index e5fcbc58..23be3bdd 100644 --- a/cloudinit/distros/ubuntu.py +++ b/cloudinit/distros/ubuntu.py @@ -30,9 +30,9 @@ class Distro(debian.Distro): } self.renderer_configs = { "eni": {"eni_path": self.network_conf_fn["eni"], - "eni_header": debian.ENI_HEADER}, + "eni_header": debian.NETWORK_FILE_HEADER}, "netplan": {"netplan_path": self.network_conf_fn["netplan"], - "netplan_header": debian.ENI_HEADER, + "netplan_header": debian.NETWORK_FILE_HEADER, "postcmds": True} } diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 07b5c0a7..67209955 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -91,9 +91,9 @@ V1_NET_CFG = {'config': [{'name': 'eth0', 'version': 1} V1_NET_CFG_OUTPUT = """\ -# This file is generated from information provided by -# the datasource. Changes to it will not persist across an instance. -# To disable cloud-init's network configuration capabilities, write a file +# This file is generated from information provided by the datasource. Changes +# to it will not persist across an instance reboot. To disable cloud-init's +# network configuration capabilities, write a file # /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: # network: {config: disabled} auto lo @@ -125,9 +125,9 @@ V1_NET_CFG_IPV6 = {'config': [{'name': 'eth0', V1_TO_V2_NET_CFG_OUTPUT = """\ -# This file is generated from information provided by -# the datasource. Changes to it will not persist across an instance. -# To disable cloud-init's network configuration capabilities, write a file +# This file is generated from information provided by the datasource. Changes +# to it will not persist across an instance reboot. To disable cloud-init's +# network configuration capabilities, write a file # /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: # network: {config: disabled} network: @@ -154,9 +154,9 @@ V2_NET_CFG = { V2_TO_V2_NET_CFG_OUTPUT = """\ -# This file is generated from information provided by -# the datasource. Changes to it will not persist across an instance. -# To disable cloud-init's network configuration capabilities, write a file +# This file is generated from information provided by the datasource. Changes +# to it will not persist across an instance reboot. To disable cloud-init's +# network configuration capabilities, write a file # /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: # network: {config: disabled} network: -- cgit v1.2.3 From 9bfb2ba7268e2c3c932023fc3d3020cdc6d6cc18 Mon Sep 17 00:00:00 2001 From: Gonéri Le Bouder Date: Fri, 20 Dec 2019 13:45:17 -0500 Subject: freebsd: introduce the freebsd renderer (#61) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * freebsd: introduce the freebsd renderer Refactoring of the FreeBSD code base to provide a real network renderer for FreeBSD. Use the generic update_sysconfig_file() from rhel_util to handle the access to /etc/rc.conf. Interfaces are not automatically renamed by FreeBSD using the following configuration in /etc/rc.conf: ``` ifconfig_fxp0_name="eth0" ``` * freesd: use regex named groups Reduce the complexity of `get_interfaces_by_mac_on_freebsd()` with named groups. * freebsd: breaks up _write_network() in tree small functions - `_write_ifconfig_entries()` - `_write_route_entries()` - `_write_resolve_conf()` * extend find_fallback_nic() to support FreeBSD this uses `route -n show default` to find the default interface * freebsd: use dns keys from NetworkState class The NetworkState class (settings instance) exposes the DNS configuration in two keys: - `dns_nameservers` - `dns_searchdomains` On OpenStack, these keys are set when a global DNS server is set. The alternative is the `dns_nameservers` and `dns_search` keys from each subdomain. We continue to read those. * freebsd: properly target the /etc/resolv.conf file * freebsd: ignore 'service routing restart' ret code On FreeBSD 10, the restart of routing and dhclient is likely to fail because - routing: it cannot remove the loopback route, but it will still set up the default route as expected. - dhclient: it cannot stop the dhclient started by the netif service. In both case, the situation is ok, and we can proceed. * freebsd: handle case when metadata MAC local locally Handle the case where the metadata configuration comes with a MAC that does not exist locally. See: - https://github.com/canonical/cloud-init/pull/61/files/635ce14b3153934ba1041be48b7245062f21e960#r359600604 - https://github.com/canonical/cloud-init/pull/61/files/635ce14b3153934ba1041be48b7245062f21e960#r359600966 * freebsd: show up a warning if several subnet found The FreeBSD provider currently only allow one subnet per interface. * freebsd: honor the target parameter in _write_network * freebsd: log when a bad route is found * freebsd: pass _postcmds to start_services() * freebsd: updatercconf() is depercated Replace `updatercconf()` by `rhel_util.update_sysconfig_file()`. * freebsd: ensure gateway is ipv4 before using it With the legacy ENI format, an IPv6 gateway may be pushed. This instead of the expected IPv4. * freebsd: find_fallback_nic, support FB10 On FreeBSD <= 10, `ifconfig -l` ignores the down interfaces. * freebsd: use util.target_path() to load resolv.conf Ensure we access `/etc/resolv.conf`, not `etc/resolv.conf`. * freebsd: skip subnet without netmask Those are likely to be either invalid of in IPv6 format. IPv6 support will be addressed later in a new patchset. * freebsd: get_devicelist returns netif list Ensure `get_devicelist()` returns the list of known netif on FreeBSD. * replace rhel_util.update_sysconfig_file wrapper call, with a wrapper function * reverse if condition to remove an indent Co-authored-by: Igor Galić --- cloudinit/config/cc_salt_minion.py | 5 +- cloudinit/distros/__init__.py | 2 +- cloudinit/distros/freebsd.py | 442 ++------------------- cloudinit/net/__init__.py | 66 +++ cloudinit/net/freebsd.py | 175 ++++++++ cloudinit/net/renderers.py | 4 +- doc/rtd/topics/network-config.rst | 2 +- tests/data/netinfo/freebsd-ifconfig-output | 52 ++- tests/data/netinfo/freebsd-netdev-formatted-output | 23 +- tests/unittests/test_distros/test_netconfig.py | 203 ++++------ tests/unittests/test_net_freebsd.py | 19 + 11 files changed, 427 insertions(+), 566 deletions(-) create mode 100644 cloudinit/net/freebsd.py create mode 100644 tests/unittests/test_net_freebsd.py (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/cloudinit/config/cc_salt_minion.py b/cloudinit/config/cc_salt_minion.py index 1c991d8d..5dd8de37 100644 --- a/cloudinit/config/cc_salt_minion.py +++ b/cloudinit/config/cc_salt_minion.py @@ -46,6 +46,8 @@ specify them with ``pkg_name``, ``service_name`` and ``config_dir``. import os from cloudinit import safeyaml, util +from cloudinit.distros import rhel_util + # Note: see https://docs.saltstack.com/en/latest/topics/installation/ # Note: see https://docs.saltstack.com/en/latest/ref/configuration/ @@ -123,7 +125,8 @@ def handle(name, cfg, cloud, log, _args): # we need to have the salt minion service enabled in rc in order to be # able to start the service. this does only apply on FreeBSD servers. if cloud.distro.osfamily == 'freebsd': - cloud.distro.updatercconf('salt_minion_enable', 'YES') + rhel_util.update_sysconfig_file( + '/etc/rc.conf', {'salt_minion_enable': 'YES'}) # restart salt-minion. 'service' will start even if not started. if it # was started, it needs to be restarted for config change. diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 6d69e6ca..cdce26f2 100755 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -145,7 +145,7 @@ class Distro(object): # Write it out # pylint: disable=assignment-from-no-return - # We have implementations in arch, freebsd and gentoo still + # We have implementations in arch and gentoo still dev_names = self._write_network(settings) # pylint: enable=assignment-from-no-return # Now try to bring them up diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py index 8e5ae96c..95cabc5c 100644 --- a/cloudinit/distros/freebsd.py +++ b/cloudinit/distros/freebsd.py @@ -13,12 +13,10 @@ import re from cloudinit import distros from cloudinit import helpers from cloudinit import log as logging +from cloudinit import net from cloudinit import ssh_util from cloudinit import util - -from cloudinit.distros import net_util -from cloudinit.distros.parsers.resolv_conf import ResolvConf - +from cloudinit.distros import rhel_util from cloudinit.settings import PER_INSTANCE LOG = logging.getLogger(__name__) @@ -29,9 +27,8 @@ class Distro(distros.Distro): rc_conf_fn = "/etc/rc.conf" login_conf_fn = '/etc/login.conf' login_conf_fn_bak = '/etc/login.conf.orig' - resolv_conf_fn = '/etc/resolv.conf' ci_sudoers_fn = '/usr/local/etc/sudoers.d/90-cloud-init-users' - default_primary_nic = 'hn0' + hostname_conf_fn = '/etc/rc.conf' def __init__(self, name, cfg, paths): distros.Distro.__init__(self, name, cfg, paths) @@ -40,99 +37,8 @@ class Distro(distros.Distro): # should only happen say once per instance...) self._runner = helpers.Runners(paths) self.osfamily = 'freebsd' - self.ipv4_pat = re.compile(r"\s+inet\s+\d+[.]\d+[.]\d+[.]\d+") cfg['ssh_svcname'] = 'sshd' - # Updates a key in /etc/rc.conf. - def updatercconf(self, key, value): - LOG.debug("Checking %s for: %s = %s", self.rc_conf_fn, key, value) - conf = self.loadrcconf() - config_changed = False - if key not in conf: - LOG.debug("Adding key in %s: %s = %s", self.rc_conf_fn, key, - value) - conf[key] = value - config_changed = True - else: - for item in conf.keys(): - if item == key and conf[item] != value: - conf[item] = value - LOG.debug("Changing key in %s: %s = %s", self.rc_conf_fn, - key, value) - config_changed = True - - if config_changed: - LOG.info("Writing %s", self.rc_conf_fn) - buf = StringIO() - for keyval in conf.items(): - buf.write('%s="%s"\n' % keyval) - util.write_file(self.rc_conf_fn, buf.getvalue()) - - # Load the contents of /etc/rc.conf and store all keys in a dict. Make sure - # quotes are ignored: - # hostname="bla" - def loadrcconf(self): - RE_MATCH = re.compile(r'^(\w+)\s*=\s*(.*)\s*') - conf = {} - lines = util.load_file(self.rc_conf_fn).splitlines() - for line in lines: - m = RE_MATCH.match(line) - if not m: - LOG.debug("Skipping line from /etc/rc.conf: %s", line) - continue - key = m.group(1).rstrip() - val = m.group(2).rstrip() - # Kill them quotes (not completely correct, aka won't handle - # quoted values, but should be ok ...) - if val[0] in ('"', "'"): - val = val[1:] - if val[-1] in ('"', "'"): - val = val[0:-1] - if len(val) == 0: - LOG.debug("Skipping empty value from /etc/rc.conf: %s", line) - continue - conf[key] = val - return conf - - def readrcconf(self, key): - conf = self.loadrcconf() - try: - val = conf[key] - except KeyError: - val = None - return val - - # NOVA will inject something like eth0, rewrite that to use the FreeBSD - # adapter. Since this adapter is based on the used driver, we need to - # figure out which interfaces are available. On KVM platforms this is - # vtnet0, where Xen would use xn0. - def getnetifname(self, dev): - LOG.debug("Translating network interface %s", dev) - if dev.startswith('lo'): - return dev - - n = re.search(r'\d+$', dev) - index = n.group(0) - - (out, _err) = util.subp(['ifconfig', '-a']) - ifconfigoutput = [x for x in (out.strip()).splitlines() - if len(x.split()) > 0] - bsddev = 'NOT_FOUND' - for line in ifconfigoutput: - m = re.match(r'^\w+', line) - if m: - if m.group(0).startswith('lo'): - continue - # Just settle with the first non-lo adapter we find, since it's - # rather unlikely there will be multiple nicdrivers involved. - bsddev = m.group(0) - break - - # Replace the index with the one we're after. - bsddev = re.sub(r'\d+$', index, bsddev) - LOG.debug("Using network interface %s", bsddev) - return bsddev - def _select_hostname(self, hostname, fqdn): # Should be FQDN if available. See rc.conf(5) in FreeBSD if fqdn: @@ -140,21 +46,18 @@ class Distro(distros.Distro): return hostname def _read_system_hostname(self): - sys_hostname = self._read_hostname(filename=None) - return ('rc.conf', sys_hostname) + sys_hostname = self._read_hostname(self.hostname_conf_fn) + return (self.hostname_conf_fn, sys_hostname) def _read_hostname(self, filename, default=None): - hostname = None - try: - hostname = self.readrcconf('hostname') - except IOError: - pass - if not hostname: + (_exists, contents) = rhel_util.read_sysconfig_file(filename) + if contents.get('hostname'): + return contents['hostname'] + else: return default - return hostname def _write_hostname(self, hostname, filename): - self.updatercconf('hostname', hostname) + rhel_util.update_sysconfig_file(filename, {'hostname': hostname}) def create_group(self, name, members): group_add_cmd = ['pw', '-n', name] @@ -282,309 +185,16 @@ class Distro(distros.Distro): keys = set(kwargs['ssh_authorized_keys']) or [] ssh_util.setup_user_keys(keys, name, options=None) - @staticmethod - def get_ifconfig_list(): - cmd = ['ifconfig', '-l'] - (nics, err) = util.subp(cmd, rcs=[0, 1]) - if len(err): - LOG.warning("Error running %s: %s", cmd, err) - return None - return nics - - @staticmethod - def get_ifconfig_ifname_out(ifname): - cmd = ['ifconfig', ifname] - (if_result, err) = util.subp(cmd, rcs=[0, 1]) - if len(err): - LOG.warning("Error running %s: %s", cmd, err) - return None - return if_result - - @staticmethod - def get_ifconfig_ether(): - cmd = ['ifconfig', '-l', 'ether'] - (nics, err) = util.subp(cmd, rcs=[0, 1]) - if len(err): - LOG.warning("Error running %s: %s", cmd, err) - return None - return nics - - @staticmethod - def get_interface_mac(ifname): - if_result = Distro.get_ifconfig_ifname_out(ifname) - for item in if_result.splitlines(): - if item.find('ether ') != -1: - mac = str(item.split()[1]) - if mac: - return mac - - @staticmethod - def get_devicelist(): - nics = Distro.get_ifconfig_list() - return nics.split() - - @staticmethod - def get_ipv6(): - ipv6 = [] - nics = Distro.get_devicelist() - for nic in nics: - if_result = Distro.get_ifconfig_ifname_out(nic) - for item in if_result.splitlines(): - if item.find("inet6 ") != -1 and item.find("scopeid") == -1: - ipv6.append(nic) - return ipv6 - - def get_ipv4(self): - ipv4 = [] - nics = Distro.get_devicelist() - for nic in nics: - if_result = Distro.get_ifconfig_ifname_out(nic) - for item in if_result.splitlines(): - print(item) - if self.ipv4_pat.match(item): - ipv4.append(nic) - return ipv4 - - def is_up(self, ifname): - if_result = Distro.get_ifconfig_ifname_out(ifname) - pat = "^" + ifname - for item in if_result.splitlines(): - if re.match(pat, item): - flags = item.split('<')[1].split('>')[0] - if flags.find("UP") != -1: - return True - - def _get_current_rename_info(self, check_downable=True): - """Collect information necessary for rename_interfaces.""" - names = Distro.get_devicelist() - bymac = {} - for n in names: - bymac[Distro.get_interface_mac(n)] = { - 'name': n, 'up': self.is_up(n), 'downable': None} - - nics_with_addresses = set() - if check_downable: - nics_with_addresses = set(self.get_ipv4() + self.get_ipv6()) - - for d in bymac.values(): - d['downable'] = (d['up'] is False or - d['name'] not in nics_with_addresses) - - return bymac - - def _rename_interfaces(self, renames): - if not len(renames): - LOG.debug("no interfaces to rename") - return - - current_info = self._get_current_rename_info() - - cur_bymac = {} - for mac, data in current_info.items(): - cur = data.copy() - cur['mac'] = mac - cur_bymac[mac] = cur - - def update_byname(bymac): - return dict((data['name'], data) - for data in bymac.values()) - - def rename(cur, new): - util.subp(["ifconfig", cur, "name", new], capture=True) - - def down(name): - util.subp(["ifconfig", name, "down"], capture=True) - - def up(name): - util.subp(["ifconfig", name, "up"], capture=True) - - ops = [] - errors = [] - ups = [] - cur_byname = update_byname(cur_bymac) - tmpname_fmt = "cirename%d" - tmpi = -1 - - for mac, new_name in renames: - cur = cur_bymac.get(mac, {}) - cur_name = cur.get('name') - cur_ops = [] - if cur_name == new_name: - # nothing to do - continue - - if not cur_name: - 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']: - 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']: - 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_bymac) - 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_bymac) - 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 apply_network_config_names(self, netcfg): - renames = [] - for ent in netcfg.get('config', {}): - if ent.get('type') != 'physical': - continue - mac = ent.get('mac_address') - name = ent.get('name') - if not mac: - continue - renames.append([mac, name]) - return self._rename_interfaces(renames) - - @classmethod def generate_fallback_config(self): - nics = Distro.get_ifconfig_ether() - if nics is None: - LOG.debug("Fail to get network interfaces") - return None - potential_interfaces = nics.split() - connected = [] - for nic in potential_interfaces: - pat = "^" + nic - if_result = Distro.get_ifconfig_ifname_out(nic) - for item in if_result.split("\n"): - if re.match(pat, item): - flags = item.split('<')[1].split('>')[0] - if flags.find("RUNNING") != -1: - connected.append(nic) - if connected: - potential_interfaces = connected - names = list(sorted(potential_interfaces)) - default_pri_nic = Distro.default_primary_nic - if default_pri_nic in names: - names.remove(default_pri_nic) - names.insert(0, default_pri_nic) - target_name = None - target_mac = None - for name in names: - mac = Distro.get_interface_mac(name) - if mac: - target_name = name - target_mac = mac - break - if target_mac and target_name: - nconf = {'config': [], 'version': 1} + nconf = {'config': [], 'version': 1} + for mac, name in net.get_interfaces_by_mac().items(): nconf['config'].append( - {'type': 'physical', 'name': target_name, - 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]}) - return nconf - else: - return None - - def _write_network(self, settings): - entries = net_util.translate_network(settings) - nameservers = [] - searchdomains = [] - dev_names = entries.keys() - for (device, info) in entries.items(): - # Skip the loopback interface. - if device.startswith('lo'): - continue - - dev = self.getnetifname(device) - - LOG.info('Configuring interface %s', dev) - - if info.get('bootproto') == 'static': - LOG.debug('Configuring dev %s with %s / %s', dev, - info.get('address'), info.get('netmask')) - # Configure an ipv4 address. - ifconfig = (info.get('address') + ' netmask ' + - info.get('netmask')) - - # Configure the gateway. - self.updatercconf('defaultrouter', info.get('gateway')) - - if 'dns-nameservers' in info: - nameservers.extend(info['dns-nameservers']) - if 'dns-search' in info: - searchdomains.extend(info['dns-search']) - else: - ifconfig = 'DHCP' - - self.updatercconf('ifconfig_' + dev, ifconfig) - - # Try to read the /etc/resolv.conf or just start from scratch if that - # fails. - try: - resolvconf = ResolvConf(util.load_file(self.resolv_conf_fn)) - resolvconf.parse() - except IOError: - util.logexc(LOG, "Failed to parse %s, use new empty file", - self.resolv_conf_fn) - resolvconf = ResolvConf('') - resolvconf.parse() - - # Add some nameservers - for server in nameservers: - try: - resolvconf.add_nameserver(server) - except ValueError: - util.logexc(LOG, "Failed to add nameserver %s", server) - - # And add any searchdomains. - for domain in searchdomains: - try: - resolvconf.add_search_domain(domain) - except ValueError: - util.logexc(LOG, "Failed to add search domain %s", domain) - util.write_file(self.resolv_conf_fn, str(resolvconf), 0o644) + {'type': 'physical', 'name': name, + 'mac_address': mac, 'subnets': [{'type': 'dhcp'}]}) + return nconf - return dev_names + def _write_network_config(self, netconfig): + return self._supported_write_network_config(netconfig) def apply_locale(self, locale, out_fn=None): # Adjust the locals value to the new value @@ -612,18 +222,12 @@ class Distro(distros.Distro): util.logexc(LOG, "Failed to restore %s backup", self.login_conf_fn) - def _bring_up_interface(self, device_name): - if device_name.startswith('lo'): - return - dev = self.getnetifname(device_name) - cmd = ['/etc/rc.d/netif', 'start', dev] - LOG.debug("Attempting to bring up interface %s using command %s", - dev, cmd) - # This could return 1 when the interface has already been put UP by the - # OS. This is just fine. - (_out, err) = util.subp(cmd, rcs=[0, 1]) - if len(err): - LOG.warning("Error running %s: %s", cmd, err) + def apply_network_config_names(self, netconfig): + # This is handled by the freebsd network renderer. It writes in + # /etc/rc.conf a line with the following format: + # ifconfig_OLDNAME_name=NEWNAME + # FreeBSD network script will rename the interface automatically. + return def install_packages(self, pkglist): self.update_package_sources() diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index bd806378..1d5eb535 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -307,6 +307,9 @@ def device_devid(devname): def get_devicelist(): + if util.is_FreeBSD(): + return list(get_interfaces_by_mac().values()) + try: devs = os.listdir(get_sys_class_path()) except OSError as e: @@ -329,6 +332,35 @@ def is_disabled_cfg(cfg): def find_fallback_nic(blacklist_drivers=None): """Return the name of the 'fallback' network device.""" + if util.is_FreeBSD(): + return find_fallback_nic_on_freebsd(blacklist_drivers) + else: + return find_fallback_nic_on_linux(blacklist_drivers) + + +def find_fallback_nic_on_freebsd(blacklist_drivers=None): + """Return the name of the 'fallback' network device on FreeBSD. + + @param blacklist_drivers: currently ignored + @return default interface, or None + + + we'll use the first interface from ``ifconfig -l -u ether`` + """ + stdout, _stderr = util.subp(['ifconfig', '-l', '-u', 'ether']) + values = stdout.split() + if values: + return values[0] + # On FreeBSD <= 10, 'ifconfig -l' ignores the interfaces with DOWN + # status + values = list(get_interfaces_by_mac().values()) + values.sort() + if values: + return values[0] + + +def find_fallback_nic_on_linux(blacklist_drivers=None): + """Return the name of the 'fallback' network device on Linux.""" if not blacklist_drivers: blacklist_drivers = [] @@ -765,6 +797,40 @@ def get_ib_interface_hwaddr(ifname, ethernet_format): def get_interfaces_by_mac(): + if util.is_FreeBSD(): + return get_interfaces_by_mac_on_freebsd() + else: + return get_interfaces_by_mac_on_linux() + + +def get_interfaces_by_mac_on_freebsd(): + (out, _) = util.subp(['ifconfig', '-a', 'ether']) + + # flatten each interface block in a single line + def flatten(out): + curr_block = '' + for l in out.split('\n'): + if l.startswith('\t'): + curr_block += l + else: + if curr_block: + yield curr_block + curr_block = l + yield curr_block + + # looks for interface and mac in a list of flatten block + def find_mac(flat_list): + for block in flat_list: + m = re.search( + r"^(?P\S*): .*ether\s(?P[\da-f:]{17}).*", + block) + if m: + yield (m.group('mac'), m.group('ifname')) + results = {mac: ifname for mac, ifname in find_mac(flatten(out))} + return results + + +def get_interfaces_by_mac_on_linux(): """Build a dictionary of tuples {mac: name}. Bridges and any devices that have a 'stolen' mac are excluded.""" diff --git a/cloudinit/net/freebsd.py b/cloudinit/net/freebsd.py new file mode 100644 index 00000000..d6f61da3 --- /dev/null +++ b/cloudinit/net/freebsd.py @@ -0,0 +1,175 @@ +# This file is part of cloud-init. See LICENSE file for license information. + +import re + +from cloudinit import log as logging +from cloudinit import net +from cloudinit import util +from cloudinit.distros import rhel_util +from cloudinit.distros.parsers.resolv_conf import ResolvConf + +from . import renderer + +LOG = logging.getLogger(__name__) + + +class Renderer(renderer.Renderer): + resolv_conf_fn = 'etc/resolv.conf' + rc_conf_fn = 'etc/rc.conf' + + def __init__(self, config=None): + if not config: + config = {} + self.dhcp_interfaces = [] + self._postcmds = config.get('postcmds', True) + + def _update_rc_conf(self, settings, target=None): + fn = util.target_path(target, self.rc_conf_fn) + rhel_util.update_sysconfig_file(fn, settings) + + def _write_ifconfig_entries(self, settings, target=None): + ifname_by_mac = net.get_interfaces_by_mac() + for interface in settings.iter_interfaces(): + device_name = interface.get("name") + device_mac = interface.get("mac_address") + if device_name and re.match(r'^lo\d+$', device_name): + continue + if device_mac not in ifname_by_mac: + LOG.info('Cannot find any device with MAC %s', device_mac) + elif device_mac and device_name: + cur_name = ifname_by_mac[device_mac] + if cur_name != device_name: + LOG.info('netif service will rename interface %s to %s', + cur_name, device_name) + self._update_rc_conf( + {'ifconfig_%s_name' % cur_name: device_name}, + target=target) + else: + device_name = ifname_by_mac[device_mac] + + LOG.info('Configuring interface %s', device_name) + ifconfig = 'DHCP' # default + + for subnet in interface.get("subnets", []): + if ifconfig != 'DHCP': + LOG.info('The FreeBSD provider only set the first subnet.') + break + if subnet.get('type') == 'static': + if not subnet.get('netmask'): + LOG.debug( + 'Skipping IP %s, because there is no netmask', + subnet.get('address')) + continue + LOG.debug('Configuring dev %s with %s / %s', device_name, + subnet.get('address'), subnet.get('netmask')) + # Configure an ipv4 address. + ifconfig = ( + subnet.get('address') + ' netmask ' + + subnet.get('netmask')) + + if ifconfig == 'DHCP': + self.dhcp_interfaces.append(device_name) + self._update_rc_conf( + {'ifconfig_' + device_name: ifconfig}, + target=target) + + def _write_route_entries(self, settings, target=None): + routes = list(settings.iter_routes()) + for interface in settings.iter_interfaces(): + subnets = interface.get("subnets", []) + for subnet in subnets: + if subnet.get('type') != 'static': + continue + gateway = subnet.get('gateway') + if gateway and len(gateway.split('.')) == 4: + routes.append({ + 'network': '0.0.0.0', + 'netmask': '0.0.0.0', + 'gateway': gateway}) + routes += subnet.get('routes', []) + route_cpt = 0 + for route in routes: + network = route.get('network') + if not network: + LOG.debug('Skipping a bad route entry') + continue + netmask = route.get('netmask') + gateway = route.get('gateway') + route_cmd = "-route %s/%s %s" % (network, netmask, gateway) + if network == '0.0.0.0': + self._update_rc_conf( + {'defaultrouter': gateway}, target=target) + else: + self._update_rc_conf( + {'route_net%d' % route_cpt: route_cmd}, target=target) + route_cpt += 1 + + def _write_resolve_conf(self, settings, target=None): + nameservers = settings.dns_nameservers + searchdomains = settings.dns_searchdomains + for interface in settings.iter_interfaces(): + for subnet in interface.get("subnets", []): + if 'dns_nameservers' in subnet: + nameservers.extend(subnet['dns_nameservers']) + if 'dns_search' in subnet: + searchdomains.extend(subnet['dns_search']) + # Try to read the /etc/resolv.conf or just start from scratch if that + # fails. + try: + resolvconf = ResolvConf(util.load_file(util.target_path( + target, self.resolv_conf_fn))) + resolvconf.parse() + except IOError: + util.logexc(LOG, "Failed to parse %s, use new empty file", + util.target_path(target, self.resolv_conf_fn)) + resolvconf = ResolvConf('') + resolvconf.parse() + + # Add some nameservers + for server in nameservers: + try: + resolvconf.add_nameserver(server) + except ValueError: + util.logexc(LOG, "Failed to add nameserver %s", server) + + # And add any searchdomains. + for domain in searchdomains: + try: + resolvconf.add_search_domain(domain) + except ValueError: + util.logexc(LOG, "Failed to add search domain %s", domain) + util.write_file( + util.target_path(target, self.resolv_conf_fn), + str(resolvconf), 0o644) + + def _write_network(self, settings, target=None): + self._write_ifconfig_entries(settings, target=target) + self._write_route_entries(settings, target=target) + self._write_resolve_conf(settings, target=target) + + self.start_services(run=self._postcmds) + + def render_network_state(self, network_state, templates=None, target=None): + self._write_network(network_state, target=target) + + def start_services(self, run=False): + if not run: + LOG.debug("freebsd generate postcmd disabled") + return + + util.subp(['service', 'netif', 'restart'], capture=True) + # On FreeBSD 10, the restart of routing and dhclient is likely to fail + # because + # - routing: it cannot remove the loopback route, but it will still set + # up the default route as expected. + # - dhclient: it cannot stop the dhclient started by the netif service. + # In both case, the situation is ok, and we can proceed. + util.subp(['service', 'routing', 'restart'], capture=True, rcs=[0, 1]) + for dhcp_interface in self.dhcp_interfaces: + util.subp(['service', 'dhclient', 'restart', dhcp_interface], + rcs=[0, 1], + capture=True) + + +def available(target=None): + return util.is_FreeBSD() diff --git a/cloudinit/net/renderers.py b/cloudinit/net/renderers.py index 5117b4a5..b98dbbe3 100644 --- a/cloudinit/net/renderers.py +++ b/cloudinit/net/renderers.py @@ -1,17 +1,19 @@ # This file is part of cloud-init. See LICENSE file for license information. from . import eni +from . import freebsd from . import netplan from . import RendererNotFoundError from . import sysconfig NAME_TO_RENDERER = { "eni": eni, + "freebsd": freebsd, "netplan": netplan, "sysconfig": sysconfig, } -DEFAULT_PRIORITY = ["eni", "sysconfig", "netplan"] +DEFAULT_PRIORITY = ["eni", "sysconfig", "netplan", "freebsd"] def search(priority=None, target=None, first=False): diff --git a/doc/rtd/topics/network-config.rst b/doc/rtd/topics/network-config.rst index 51ced4d1..1520ba9a 100644 --- a/doc/rtd/topics/network-config.rst +++ b/doc/rtd/topics/network-config.rst @@ -191,7 +191,7 @@ supplying an updated configuration in cloud-config. :: system_info: network: - renderers: ['netplan', 'eni', 'sysconfig'] + renderers: ['netplan', 'eni', 'sysconfig', 'freebsd'] Network Configuration Tools diff --git a/tests/data/netinfo/freebsd-ifconfig-output b/tests/data/netinfo/freebsd-ifconfig-output index 3de15a5a..f64c2f60 100644 --- a/tests/data/netinfo/freebsd-ifconfig-output +++ b/tests/data/netinfo/freebsd-ifconfig-output @@ -1,17 +1,39 @@ vtnet0: flags=8843 metric 0 mtu 1500 - options=6c07bb - ether fa:16:3e:14:1f:99 - hwaddr fa:16:3e:14:1f:99 - inet 10.1.80.61 netmask 0xfffff000 broadcast 10.1.95.255 - nd6 options=29 - media: Ethernet 10Gbase-T - status: active -pflog0: flags=0<> metric 0 mtu 33160 -pfsync0: flags=0<> metric 0 mtu 1500 - syncpeer: 0.0.0.0 maxupd: 128 defer: off + options=6c07bb + ether 52:54:00:50:b7:0d +re0.33: flags=8943 metric 0 mtu 1500 + options=80003 + ether 80:00:73:63:5c:48 + groups: vlan + vlan: 33 vlanpcp: 0 parent interface: re0 + media: Ethernet autoselect (1000baseT ) + status: active + nd6 options=21 +bridge0: flags=8843 metric 0 mtu 1500 + ether 02:14:39:0e:25:00 + inet 192.168.1.1 netmask 0xffffff00 broadcast 192.168.1.255 + id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15 + maxage 20 holdcnt 6 proto rstp maxaddr 2000 timeout 1200 + root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0 + member: vnet0:11 flags=143 + ifmaxaddr 0 port 5 priority 128 path cost 2000 + member: vnet0:1 flags=143 + ifmaxaddr 0 port 4 priority 128 path cost 2000 + groups: bridge + nd6 options=9 +vnet0:11: flags=8943 metric 0 mtu 1500 + description: 'associated with jail: webirc' + options=8 + ether 02:ff:60:8c:f3:72 + hwaddr 02:2b:bb:64:3f:0a + inet6 fe80::2b:bbff:fe64:3f0a%vnet0:11 prefixlen 64 tentative scopeid 0x5 + groups: epair + media: Ethernet 10Gbase-T (10Gbase-T ) + status: active + nd6 options=29 lo0: flags=8049 metric 0 mtu 16384 - options=600003 - inet6 ::1 prefixlen 128 - inet6 fe80::1%lo0 prefixlen 64 scopeid 0x4 - inet 127.0.0.1 netmask 0xff000000 - nd6 options=21 + options=600003 + inet6 ::1 prefixlen 128 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2 + inet 127.0.0.1 netmask 0xff000000 + nd6 options=21 diff --git a/tests/data/netinfo/freebsd-netdev-formatted-output b/tests/data/netinfo/freebsd-netdev-formatted-output index a9d2ac14..a0d937b3 100644 --- a/tests/data/netinfo/freebsd-netdev-formatted-output +++ b/tests/data/netinfo/freebsd-netdev-formatted-output @@ -1,11 +1,12 @@ -+++++++++++++++++++++++++++++++Net device info+++++++++++++++++++++++++++++++ -+---------+-------+----------------+------------+-------+-------------------+ -| Device | Up | Address | Mask | Scope | Hw-Address | -+---------+-------+----------------+------------+-------+-------------------+ -| lo0 | True | 127.0.0.1 | 0xff000000 | . | . | -| lo0 | True | ::1/128 | . | . | . | -| lo0 | True | fe80::1%lo0/64 | . | 0x4 | . | -| pflog0 | False | . | . | . | . | -| pfsync0 | False | . | . | . | . | -| vtnet0 | True | 10.1.80.61 | 0xfffff000 | . | fa:16:3e:14:1f:99 | -+---------+-------+----------------+------------+-------+-------------------+ ++++++++++++++++++++++++++++++++++++++++++Net device info++++++++++++++++++++++++++++++++++++++++++ ++----------+------+-------------------------------------+------------+-------+-------------------+ +| Device | Up | Address | Mask | Scope | Hw-Address | ++----------+------+-------------------------------------+------------+-------+-------------------+ +| bridge0 | True | 192.168.1.1 | 0xffffff00 | . | 02:14:39:0e:25:00 | +| lo0 | True | 127.0.0.1 | 0xff000000 | . | . | +| lo0 | True | ::1/128 | . | . | . | +| lo0 | True | fe80::1%lo0/64 | . | 0x2 | . | +| re0.33 | True | . | . | . | 80:00:73:63:5c:48 | +| vnet0:11 | True | fe80::2b:bbff:fe64:3f0a%vnet0:11/64 | . | 0x5 | 02:2b:bb:64:3f:0a | +| vtnet0 | True | . | . | . | 52:54:00:50:b7:0d | ++----------+------+-------------------------------------+------------+-------+-------------------+ diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 67209955..a1611a07 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -1,5 +1,6 @@ # This file is part of cloud-init. See LICENSE file for license information. +import copy import os from six import StringIO from textwrap import dedent @@ -14,7 +15,7 @@ from cloudinit.distros.parsers.sys_conf import SysConf from cloudinit import helpers from cloudinit import settings from cloudinit.tests.helpers import ( - FilesystemMockingTestCase, dir2dict, populate_dir) + FilesystemMockingTestCase, dir2dict) from cloudinit import util @@ -213,128 +214,95 @@ class TestNetCfgDistroBase(FilesystemMockingTestCase): self.assertEqual(v, b2[k]) -class TestNetCfgDistroFreebsd(TestNetCfgDistroBase): +class TestNetCfgDistroFreeBSD(TestNetCfgDistroBase): - frbsd_ifout = """\ -hn0: flags=8843 metric 0 mtu 1500 - options=51b - ether 00:15:5d:4c:73:00 - inet6 fe80::215:5dff:fe4c:7300%hn0 prefixlen 64 scopeid 0x2 - inet 10.156.76.127 netmask 0xfffffc00 broadcast 10.156.79.255 - nd6 options=23 - media: Ethernet autoselect (10Gbase-T ) - status: active + def setUp(self): + super(TestNetCfgDistroFreeBSD, self).setUp() + self.distro = self._get_distro('freebsd', renderers=['freebsd']) + + def _apply_and_verify_freebsd(self, apply_fn, config, expected_cfgs=None, + bringup=False): + if not expected_cfgs: + raise ValueError('expected_cfg must not be None') + + tmpd = None + with mock.patch('cloudinit.net.freebsd.available') as m_avail: + m_avail.return_value = True + with self.reRooted(tmpd) as tmpd: + util.ensure_dir('/etc') + util.ensure_file('/etc/rc.conf') + util.ensure_file('/etc/resolv.conf') + apply_fn(config, bringup) + + results = dir2dict(tmpd) + for cfgpath, expected in expected_cfgs.items(): + print("----------") + print(expected) + print("^^^^ expected | rendered VVVVVVV") + print(results[cfgpath]) + print("----------") + self.assertEqual( + set(expected.split('\n')), + set(results[cfgpath].split('\n'))) + self.assertEqual(0o644, get_mode(cfgpath, tmpd)) + + @mock.patch('cloudinit.net.get_interfaces_by_mac') + def test_apply_network_config_freebsd_standard(self, ifaces_mac): + ifaces_mac.return_value = { + '00:15:5d:4c:73:00': 'eth0', + } + rc_conf_expected = """\ +defaultrouter=192.168.1.254 +ifconfig_eth0='192.168.1.5 netmask 255.255.255.0' +ifconfig_eth1=DHCP """ - @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_list') - @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_ifname_out') - def test_get_ip_nic_freebsd(self, ifname_out, iflist): - frbsd_distro = self._get_distro('freebsd') - iflist.return_value = "lo0 hn0" - ifname_out.return_value = self.frbsd_ifout - res = frbsd_distro.get_ipv4() - self.assertEqual(res, ['lo0', 'hn0']) - res = frbsd_distro.get_ipv6() - self.assertEqual(res, []) - - @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_ether') - @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_ifname_out') - @mock.patch('cloudinit.distros.freebsd.Distro.get_interface_mac') - def test_generate_fallback_config_freebsd(self, mac, ifname_out, if_ether): - frbsd_distro = self._get_distro('freebsd') - - if_ether.return_value = 'hn0' - ifname_out.return_value = self.frbsd_ifout - mac.return_value = '00:15:5d:4c:73:00' - res = frbsd_distro.generate_fallback_config() - self.assertIsNotNone(res) - - def test_simple_write_freebsd(self): - fbsd_distro = self._get_distro('freebsd') - - rc_conf = '/etc/rc.conf' - read_bufs = { - rc_conf: 'initial-rc-conf-not-validated', - '/etc/resolv.conf': 'initial-resolv-conf-not-validated', + expected_cfgs = { + '/etc/rc.conf': rc_conf_expected, + '/etc/resolv.conf': '' } + self._apply_and_verify_freebsd(self.distro.apply_network_config, + V1_NET_CFG, + expected_cfgs=expected_cfgs.copy()) - tmpd = self.tmp_dir() - populate_dir(tmpd, read_bufs) - with self.reRooted(tmpd): - with mock.patch("cloudinit.distros.freebsd.util.subp", - return_value=('vtnet0', '')): - fbsd_distro.apply_network(BASE_NET_CFG, False) - results = dir2dict(tmpd) - - self.assertIn(rc_conf, results) - self.assertCfgEquals( - dedent('''\ - ifconfig_vtnet0="192.168.1.5 netmask 255.255.255.0" - ifconfig_vtnet1="DHCP" - defaultrouter="192.168.1.254" - '''), results[rc_conf]) - self.assertEqual(0o644, get_mode(rc_conf, tmpd)) - - def test_simple_write_freebsd_from_v2eni(self): - fbsd_distro = self._get_distro('freebsd') - - rc_conf = '/etc/rc.conf' - read_bufs = { - rc_conf: 'initial-rc-conf-not-validated', - '/etc/resolv.conf': 'initial-resolv-conf-not-validated', + @mock.patch('cloudinit.net.get_interfaces_by_mac') + def test_apply_network_config_freebsd_ifrename(self, ifaces_mac): + ifaces_mac.return_value = { + '00:15:5d:4c:73:00': 'vtnet0', } + rc_conf_expected = """\ +ifconfig_vtnet0_name=eth0 +defaultrouter=192.168.1.254 +ifconfig_eth0='192.168.1.5 netmask 255.255.255.0' +ifconfig_eth1=DHCP +""" - tmpd = self.tmp_dir() - populate_dir(tmpd, read_bufs) - with self.reRooted(tmpd): - with mock.patch("cloudinit.distros.freebsd.util.subp", - return_value=('vtnet0', '')): - fbsd_distro.apply_network(BASE_NET_CFG_FROM_V2, False) - results = dir2dict(tmpd) - - self.assertIn(rc_conf, results) - self.assertCfgEquals( - dedent('''\ - ifconfig_vtnet0="192.168.1.5 netmask 255.255.255.0" - ifconfig_vtnet1="DHCP" - defaultrouter="192.168.1.254" - '''), results[rc_conf]) - self.assertEqual(0o644, get_mode(rc_conf, tmpd)) - - def test_apply_network_config_fallback_freebsd(self): - fbsd_distro = self._get_distro('freebsd') - - # a weak attempt to verify that we don't have an implementation - # of _write_network_config or apply_network_config in fbsd now, - # which would make this test not actually test the fallback. - self.assertRaises( - NotImplementedError, fbsd_distro._write_network_config, - BASE_NET_CFG) - - # now run - mynetcfg = { - 'config': [{"type": "physical", "name": "eth0", - "mac_address": "c0:d6:9f:2c:e8:80", - "subnets": [{"type": "dhcp"}]}], - 'version': 1} - - rc_conf = '/etc/rc.conf' - read_bufs = { - rc_conf: 'initial-rc-conf-not-validated', - '/etc/resolv.conf': 'initial-resolv-conf-not-validated', + V1_NET_CFG_RENAME = copy.deepcopy(V1_NET_CFG) + V1_NET_CFG_RENAME['config'][0]['mac_address'] = '00:15:5d:4c:73:00' + + expected_cfgs = { + '/etc/rc.conf': rc_conf_expected, + '/etc/resolv.conf': '' } + self._apply_and_verify_freebsd(self.distro.apply_network_config, + V1_NET_CFG_RENAME, + expected_cfgs=expected_cfgs.copy()) - tmpd = self.tmp_dir() - populate_dir(tmpd, read_bufs) - with self.reRooted(tmpd): - with mock.patch("cloudinit.distros.freebsd.util.subp", - return_value=('vtnet0', '')): - fbsd_distro.apply_network_config(mynetcfg, bring_up=False) - results = dir2dict(tmpd) + @mock.patch('cloudinit.net.get_interfaces_by_mac') + def test_apply_network_config_freebsd_nameserver(self, ifaces_mac): + ifaces_mac.return_value = { + '00:15:5d:4c:73:00': 'eth0', + } - self.assertIn(rc_conf, results) - self.assertCfgEquals('ifconfig_vtnet0="DHCP"', results[rc_conf]) - self.assertEqual(0o644, get_mode(rc_conf, tmpd)) + V1_NET_CFG_DNS = copy.deepcopy(V1_NET_CFG) + ns = ['1.2.3.4'] + V1_NET_CFG_DNS['config'][0]['subnets'][0]['dns_nameservers'] = ns + expected_cfgs = { + '/etc/resolv.conf': 'nameserver 1.2.3.4\n' + } + self._apply_and_verify_freebsd(self.distro.apply_network_config, + V1_NET_CFG_DNS, + expected_cfgs=expected_cfgs.copy()) class TestNetCfgDistroUbuntuEni(TestNetCfgDistroBase): @@ -694,10 +662,11 @@ class TestNetCfgDistroArch(TestNetCfgDistroBase): """), } - self._apply_and_verify(self.distro.apply_network_config, - V1_NET_CFG, - expected_cfgs=expected_cfgs.copy(), - with_netplan=True) + with mock.patch('cloudinit.util.is_FreeBSD', return_value=False): + self._apply_and_verify(self.distro.apply_network_config, + V1_NET_CFG, + expected_cfgs=expected_cfgs.copy(), + with_netplan=True) def get_mode(path, target=None): diff --git a/tests/unittests/test_net_freebsd.py b/tests/unittests/test_net_freebsd.py new file mode 100644 index 00000000..48296c30 --- /dev/null +++ b/tests/unittests/test_net_freebsd.py @@ -0,0 +1,19 @@ +from cloudinit import net + +from cloudinit.tests.helpers import (CiTestCase, mock, readResource) + +SAMPLE_FREEBSD_IFCONFIG_OUT = readResource("netinfo/freebsd-ifconfig-output") + + +class TestInterfacesByMac(CiTestCase): + + @mock.patch('cloudinit.util.subp') + @mock.patch('cloudinit.util.is_FreeBSD') + def test_get_interfaces_by_mac(self, mock_is_FreeBSD, mock_subp): + mock_is_FreeBSD.return_value = True + mock_subp.return_value = (SAMPLE_FREEBSD_IFCONFIG_OUT, 0) + a = net.get_interfaces_by_mac() + assert a == {'52:54:00:50:b7:0d': 'vtnet0', + '80:00:73:63:5c:48': 're0.33', + '02:14:39:0e:25:00': 'bridge0', + '02:ff:60:8c:f3:72': 'vnet0:11'} -- cgit v1.2.3 From dacdd30080bd8183d1f1c1dc9dbcbc8448301529 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Wed, 8 Jan 2020 11:30:17 -0600 Subject: net: fix rendering of 'static6' in network config (#77) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * net: fix rendering of 'static6' in network config A V1 static6 network typo was misrendered in eni, it's not valid. It was ignored in sysconfig and netplan. This branch fixes eni, updates sysconfig, netplan to render it correctly and adds unittests for all cases. Reported-by: Raphaël Enrici LP: #1850988 * net: add comment about static6 type in subnet_is_ipv6 Co-authored-by: Chad Smith Co-authored-by: Daniel Watkins --- cloudinit/net/eni.py | 4 +- cloudinit/net/netplan.py | 2 +- cloudinit/net/network_state.py | 2 +- cloudinit/net/sysconfig.py | 4 +- tests/unittests/test_distros/test_netconfig.py | 55 +++++++++++++++++++++++++- 5 files changed, 61 insertions(+), 6 deletions(-) (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index 70771060..2f714563 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -429,7 +429,9 @@ class Renderer(renderer.Renderer): iface['mode'] = 'auto' # Use stateless DHCPv6 (0=off, 1=on) iface['dhcp'] = '0' - elif subnet_is_ipv6(subnet) and subnet['type'] == 'static': + elif subnet_is_ipv6(subnet): + # mode might be static6, eni uses 'static' + iface['mode'] = 'static' if accept_ra is not None: # Accept router advertisements (0=off, 1=on) iface['accept_ra'] = '1' if accept_ra else '0' diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py index 14d3999f..89855270 100644 --- a/cloudinit/net/netplan.py +++ b/cloudinit/net/netplan.py @@ -98,7 +98,7 @@ def _extract_addresses(config, entry, ifname, features=None): entry.update({sn_type: True}) elif sn_type in IPV6_DYNAMIC_TYPES: entry.update({'dhcp6': True}) - elif sn_type in ['static']: + elif sn_type in ['static', 'static6']: addr = "%s" % subnet.get('address') if 'prefix' in subnet: addr += "/%d" % subnet.get('prefix') diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index f3e8e250..9b126100 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -941,7 +941,7 @@ def subnet_is_ipv6(subnet): # 'static6', 'dhcp6', 'ipv6_dhcpv6-stateful', 'ipv6_dhcpv6-stateless' or # 'ipv6_slaac' if subnet['type'].endswith('6') or subnet['type'] in IPV6_DYNAMIC_TYPES: - # This is a request for DHCPv6. + # This is a request either static6 type or DHCPv6. return True elif subnet['type'] == 'static' and is_ipv6_addr(subnet.get('address')): return True diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index 310cdf01..3e06af01 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -367,7 +367,7 @@ class Renderer(renderer.Renderer): iface_cfg['IPV6_AUTOCONF'] = True elif subnet_type in ['dhcp4', 'dhcp']: iface_cfg['BOOTPROTO'] = 'dhcp' - elif subnet_type == 'static': + elif subnet_type in ['static', 'static6']: # grep BOOTPROTO sysconfig.txt -A2 | head -3 # BOOTPROTO=none|bootp|dhcp # 'bootp' or 'dhcp' cause a DHCP client @@ -419,7 +419,7 @@ class Renderer(renderer.Renderer): continue elif subnet_type in IPV6_DYNAMIC_TYPES: continue - elif subnet_type == 'static': + elif subnet_type in ['static', 'static6']: if subnet_is_ipv6(subnet): ipv6_index = ipv6_index + 1 ipv6_cidr = "%s/%s" % (subnet['address'], subnet['prefix']) diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index a1611a07..aeaadaa0 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -110,13 +110,31 @@ auto eth1 iface eth1 inet dhcp """ +V1_NET_CFG_IPV6_OUTPUT = """\ +# This file is generated from information provided by the datasource. Changes +# to it will not persist across an instance reboot. To disable cloud-init's +# network configuration capabilities, write a file +# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: +# network: {config: disabled} +auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet6 static + address 2607:f0d0:1002:0011::2/64 + gateway 2607:f0d0:1002:0011::1 + +auto eth1 +iface eth1 inet dhcp +""" + V1_NET_CFG_IPV6 = {'config': [{'name': 'eth0', 'subnets': [{'address': '2607:f0d0:1002:0011::2', 'gateway': '2607:f0d0:1002:0011::1', 'netmask': '64', - 'type': 'static'}], + 'type': 'static6'}], 'type': 'physical'}, {'name': 'eth1', 'subnets': [{'control': 'auto', @@ -142,6 +160,23 @@ network: dhcp4: true """ +V1_TO_V2_NET_CFG_IPV6_OUTPUT = """\ +# This file is generated from information provided by the datasource. Changes +# to it will not persist across an instance reboot. To disable cloud-init's +# network configuration capabilities, write a file +# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: +# network: {config: disabled} +network: + version: 2 + ethernets: + eth0: + addresses: + - 2607:f0d0:1002:0011::2/64 + gateway6: 2607:f0d0:1002:0011::1 + eth1: + dhcp4: true +""" + V2_NET_CFG = { 'ethernets': { 'eth7': { @@ -344,6 +379,14 @@ class TestNetCfgDistroUbuntuEni(TestNetCfgDistroBase): V1_NET_CFG, expected_cfgs=expected_cfgs.copy()) + def test_apply_network_config_ipv6_ub(self): + expected_cfgs = { + self.eni_path(): V1_NET_CFG_IPV6_OUTPUT + } + self._apply_and_verify_eni(self.distro.apply_network_config, + V1_NET_CFG_IPV6, + expected_cfgs=expected_cfgs.copy()) + class TestNetCfgDistroUbuntuNetplan(TestNetCfgDistroBase): def setUp(self): @@ -387,6 +430,16 @@ class TestNetCfgDistroUbuntuNetplan(TestNetCfgDistroBase): V1_NET_CFG, expected_cfgs=expected_cfgs.copy()) + def test_apply_network_config_v1_ipv6_to_netplan_ub(self): + expected_cfgs = { + self.netplan_path(): V1_TO_V2_NET_CFG_IPV6_OUTPUT, + } + + # ub_distro.apply_network_config(V1_NET_CFG_IPV6, False) + self._apply_and_verify_netplan(self.distro.apply_network_config, + V1_NET_CFG_IPV6, + expected_cfgs=expected_cfgs.copy()) + def test_apply_network_config_v2_passthrough_ub(self): expected_cfgs = { self.netplan_path(): V2_TO_V2_NET_CFG_OUTPUT, -- cgit v1.2.3 From 6b157aa60a10fa74374287f0ec0313255e0462b4 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Thu, 23 Jan 2020 16:32:01 -0500 Subject: cloudinit: remove ImportError handling for mock imports (#182) We only run on Python 3 now, so we can unambiguously expect unittest.mock to exist. --- tests/unittests/test_data.py | 6 +----- tests/unittests/test_distros/test_generic.py | 6 +----- tests/unittests/test_distros/test_netconfig.py | 6 +----- .../test_handler/test_handler_apt_configure_sources_list_v1.py | 6 +----- .../test_handler/test_handler_apt_configure_sources_list_v3.py | 8 ++------ tests/unittests/test_handler/test_handler_apt_source_v1.py | 8 ++------ tests/unittests/test_handler/test_handler_apt_source_v3.py | 9 ++------- tests/unittests/test_handler/test_handler_ca_certs.py | 5 +---- tests/unittests/test_handler/test_handler_growpart.py | 5 +---- tests/unittests/test_handler/test_handler_lxd.py | 5 +---- tests/unittests/test_handler/test_handler_mounts.py | 6 +----- tests/unittests/test_handler/test_handler_spacewalk.py | 6 +----- tests/unittests/test_util.py | 6 +----- 13 files changed, 16 insertions(+), 66 deletions(-) (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/tests/unittests/test_data.py b/tests/unittests/test_data.py index e55feb22..c59db33d 100644 --- a/tests/unittests/test_data.py +++ b/tests/unittests/test_data.py @@ -5,11 +5,7 @@ import gzip import logging import os - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock from six import BytesIO, StringIO diff --git a/tests/unittests/test_distros/test_generic.py b/tests/unittests/test_distros/test_generic.py index 7e0da4f2..02b334e3 100644 --- a/tests/unittests/test_distros/test_generic.py +++ b/tests/unittests/test_distros/test_generic.py @@ -8,11 +8,7 @@ from cloudinit.tests import helpers import os import shutil import tempfile - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock unknown_arch_info = { 'arches': ['default'], diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index aeaadaa0..5ede4d77 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -4,11 +4,7 @@ import copy import os from six import StringIO from textwrap import dedent - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock from cloudinit import distros from cloudinit.distros.parsers.sys_conf import SysConf diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py index 7c17a264..69009a44 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py @@ -7,11 +7,7 @@ import logging import os import shutil import tempfile - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock from cloudinit import cloud from cloudinit import distros diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py index 0a68cb8f..0aa3d51a 100644 --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py @@ -7,12 +7,8 @@ import logging import os import shutil import tempfile - -try: - from unittest import mock -except ImportError: - import mock -from mock import call +from unittest import mock +from unittest.mock import call from cloudinit import cloud from cloudinit import distros diff --git a/tests/unittests/test_handler/test_handler_apt_source_v1.py b/tests/unittests/test_handler/test_handler_apt_source_v1.py index 652d97ab..866752ef 100644 --- a/tests/unittests/test_handler/test_handler_apt_source_v1.py +++ b/tests/unittests/test_handler/test_handler_apt_source_v1.py @@ -9,12 +9,8 @@ import os import re import shutil import tempfile - -try: - from unittest import mock -except ImportError: - import mock -from mock import call +from unittest import mock +from unittest.mock import call from cloudinit.config import cc_apt_configure from cloudinit import gpg diff --git a/tests/unittests/test_handler/test_handler_apt_source_v3.py b/tests/unittests/test_handler/test_handler_apt_source_v3.py index c5cf6785..90949b6d 100644 --- a/tests/unittests/test_handler/test_handler_apt_source_v3.py +++ b/tests/unittests/test_handler/test_handler_apt_source_v3.py @@ -11,13 +11,8 @@ import shutil import socket import tempfile -from unittest import TestCase - -try: - from unittest import mock -except ImportError: - import mock -from mock import call +from unittest import TestCase, mock +from unittest.mock import call from cloudinit import cloud from cloudinit import distros diff --git a/tests/unittests/test_handler/test_handler_ca_certs.py b/tests/unittests/test_handler/test_handler_ca_certs.py index 06e14db0..5b4105dd 100644 --- a/tests/unittests/test_handler/test_handler_ca_certs.py +++ b/tests/unittests/test_handler/test_handler_ca_certs.py @@ -11,11 +11,8 @@ import logging import shutil import tempfile import unittest +from unittest import mock -try: - from unittest import mock -except ImportError: - import mock try: from contextlib import ExitStack except ImportError: diff --git a/tests/unittests/test_handler/test_handler_growpart.py b/tests/unittests/test_handler/test_handler_growpart.py index 1f39ebe7..43b53745 100644 --- a/tests/unittests/test_handler/test_handler_growpart.py +++ b/tests/unittests/test_handler/test_handler_growpart.py @@ -11,11 +11,8 @@ import logging import os import re import unittest +from unittest import mock -try: - from unittest import mock -except ImportError: - import mock try: from contextlib import ExitStack except ImportError: diff --git a/tests/unittests/test_handler/test_handler_lxd.py b/tests/unittests/test_handler/test_handler_lxd.py index b63db616..40b521e5 100644 --- a/tests/unittests/test_handler/test_handler_lxd.py +++ b/tests/unittests/test_handler/test_handler_lxd.py @@ -5,10 +5,7 @@ from cloudinit.sources import DataSourceNoCloud from cloudinit import (distros, helpers, cloud) from cloudinit.tests import helpers as t_help -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock class TestLxd(t_help.CiTestCase): diff --git a/tests/unittests/test_handler/test_handler_mounts.py b/tests/unittests/test_handler/test_handler_mounts.py index 7bcefa0a..05ac183e 100644 --- a/tests/unittests/test_handler/test_handler_mounts.py +++ b/tests/unittests/test_handler/test_handler_mounts.py @@ -1,16 +1,12 @@ # This file is part of cloud-init. See LICENSE file for license information. import os.path +from unittest import mock from cloudinit.config import cc_mounts from cloudinit.tests import helpers as test_helpers -try: - from unittest import mock -except ImportError: - import mock - class TestSanitizeDevname(test_helpers.FilesystemMockingTestCase): diff --git a/tests/unittests/test_handler/test_handler_spacewalk.py b/tests/unittests/test_handler/test_handler_spacewalk.py index ddbf4a79..410e6f77 100644 --- a/tests/unittests/test_handler/test_handler_spacewalk.py +++ b/tests/unittests/test_handler/test_handler_spacewalk.py @@ -6,11 +6,7 @@ from cloudinit import util from cloudinit.tests import helpers import logging - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock LOG = logging.getLogger(__name__) diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index 75a3f0b4..9ff17f52 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -12,15 +12,11 @@ import stat import sys import tempfile import yaml +from unittest import mock from cloudinit import importer, util from cloudinit.tests import helpers -try: - from unittest import mock -except ImportError: - import mock - BASH = util.which('bash') BOGUS_COMMAND = 'this-is-not-expected-to-be-a-program-name' -- cgit v1.2.3 From 1bb1896ec900622e02c1ffb59db4d3f2df4a964d Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Fri, 31 Jan 2020 10:15:31 -0500 Subject: cloudinit: replace "from six import X" imports (except in util.py) (#183) --- cloudinit/cmd/devel/tests/test_logs.py | 2 +- cloudinit/cmd/devel/tests/test_render.py | 2 +- cloudinit/cmd/tests/test_clean.py | 2 +- cloudinit/cmd/tests/test_cloud_id.py | 2 +- cloudinit/cmd/tests/test_main.py | 2 +- cloudinit/cmd/tests/test_query.py | 2 +- cloudinit/cmd/tests/test_status.py | 2 +- cloudinit/config/cc_debug.py | 3 +-- cloudinit/config/cc_landscape.py | 3 +-- cloudinit/config/cc_puppet.py | 3 +-- cloudinit/config/cc_rightscale_userdata.py | 3 +-- cloudinit/config/cc_seed_random.py | 3 +-- cloudinit/config/cc_zypper_add_repo.py | 3 +-- cloudinit/config/tests/test_snap.py | 2 +- cloudinit/distros/parsers/hostname.py | 2 +- cloudinit/distros/parsers/hosts.py | 2 +- cloudinit/distros/parsers/resolv_conf.py | 2 +- cloudinit/helpers.py | 6 ++---- cloudinit/net/dhcp.py | 2 +- cloudinit/signal_handler.py | 3 +-- tests/unittests/test_data.py | 3 +-- tests/unittests/test_datasource/test_gce.py | 2 +- tests/unittests/test_datasource/test_openstack.py | 8 +++----- tests/unittests/test_distros/test_netconfig.py | 2 +- tests/unittests/test_filters/test_launch_index.py | 3 +-- tests/unittests/test_handler/test_handler_locale.py | 3 +-- tests/unittests/test_handler/test_handler_mcollective.py | 2 +- tests/unittests/test_handler/test_handler_seed_random.py | 3 +-- tests/unittests/test_handler/test_handler_set_hostname.py | 2 +- tests/unittests/test_handler/test_handler_timezone.py | 2 +- tests/unittests/test_handler/test_handler_yum_add_repo.py | 2 +- tests/unittests/test_handler/test_handler_zypper_add_repo.py | 2 +- tests/unittests/test_handler/test_schema.py | 2 +- 33 files changed, 36 insertions(+), 51 deletions(-) (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/cloudinit/cmd/devel/tests/test_logs.py b/cloudinit/cmd/devel/tests/test_logs.py index 4951797b..d2dfa8de 100644 --- a/cloudinit/cmd/devel/tests/test_logs.py +++ b/cloudinit/cmd/devel/tests/test_logs.py @@ -2,7 +2,7 @@ from datetime import datetime import os -from six import StringIO +from io import StringIO from cloudinit.cmd.devel import logs from cloudinit.sources import INSTANCE_JSON_SENSITIVE_FILE diff --git a/cloudinit/cmd/devel/tests/test_render.py b/cloudinit/cmd/devel/tests/test_render.py index 988bba03..a7fcf2ce 100644 --- a/cloudinit/cmd/devel/tests/test_render.py +++ b/cloudinit/cmd/devel/tests/test_render.py @@ -1,7 +1,7 @@ # This file is part of cloud-init. See LICENSE file for license information. -from six import StringIO import os +from io import StringIO from collections import namedtuple from cloudinit.cmd.devel import render diff --git a/cloudinit/cmd/tests/test_clean.py b/cloudinit/cmd/tests/test_clean.py index f092ab3d..13a69aa1 100644 --- a/cloudinit/cmd/tests/test_clean.py +++ b/cloudinit/cmd/tests/test_clean.py @@ -5,7 +5,7 @@ from cloudinit.util import ensure_dir, sym_link, write_file from cloudinit.tests.helpers import CiTestCase, wrap_and_call, mock from collections import namedtuple import os -from six import StringIO +from io import StringIO mypaths = namedtuple('MyPaths', 'cloud_dir') diff --git a/cloudinit/cmd/tests/test_cloud_id.py b/cloudinit/cmd/tests/test_cloud_id.py index 73738170..3f3727fd 100644 --- a/cloudinit/cmd/tests/test_cloud_id.py +++ b/cloudinit/cmd/tests/test_cloud_id.py @@ -4,7 +4,7 @@ from cloudinit import util from collections import namedtuple -from six import StringIO +from io import StringIO from cloudinit.cmd import cloud_id diff --git a/cloudinit/cmd/tests/test_main.py b/cloudinit/cmd/tests/test_main.py index 57b8fdf5..384fddc6 100644 --- a/cloudinit/cmd/tests/test_main.py +++ b/cloudinit/cmd/tests/test_main.py @@ -3,7 +3,7 @@ from collections import namedtuple import copy import os -from six import StringIO +from io import StringIO from cloudinit.cmd import main from cloudinit import safeyaml diff --git a/cloudinit/cmd/tests/test_query.py b/cloudinit/cmd/tests/test_query.py index c48605ad..6d36a4ea 100644 --- a/cloudinit/cmd/tests/test_query.py +++ b/cloudinit/cmd/tests/test_query.py @@ -1,7 +1,7 @@ # This file is part of cloud-init. See LICENSE file for license information. import errno -from six import StringIO +from io import StringIO from textwrap import dedent import os diff --git a/cloudinit/cmd/tests/test_status.py b/cloudinit/cmd/tests/test_status.py index aded8580..1ed10896 100644 --- a/cloudinit/cmd/tests/test_status.py +++ b/cloudinit/cmd/tests/test_status.py @@ -2,7 +2,7 @@ from collections import namedtuple import os -from six import StringIO +from io import StringIO from textwrap import dedent from cloudinit.atomic_helper import write_json diff --git a/cloudinit/config/cc_debug.py b/cloudinit/config/cc_debug.py index 610dbc8b..4d5a6aa2 100644 --- a/cloudinit/config/cc_debug.py +++ b/cloudinit/config/cc_debug.py @@ -28,8 +28,7 @@ location that this cloud-init has been configured with when running. """ import copy - -from six import StringIO +from io import StringIO from cloudinit import type_utils from cloudinit import util diff --git a/cloudinit/config/cc_landscape.py b/cloudinit/config/cc_landscape.py index eaf1e940..a9c04d86 100644 --- a/cloudinit/config/cc_landscape.py +++ b/cloudinit/config/cc_landscape.py @@ -56,8 +56,7 @@ The following default client config is provided, but can be overridden:: """ import os - -from six import BytesIO +from io import BytesIO from configobj import ConfigObj diff --git a/cloudinit/config/cc_puppet.py b/cloudinit/config/cc_puppet.py index b088db6e..c01f5b8f 100644 --- a/cloudinit/config/cc_puppet.py +++ b/cloudinit/config/cc_puppet.py @@ -77,11 +77,10 @@ See https://puppet.com/docs/puppet/latest/config_file_csr_attributes.html pp_preshared_key: 342thbjkt82094y0uthhor289jnqthpc2290 """ -from six import StringIO - import os import socket import yaml +from io import StringIO from cloudinit import helpers from cloudinit import util diff --git a/cloudinit/config/cc_rightscale_userdata.py b/cloudinit/config/cc_rightscale_userdata.py index bd8ee89f..a5aca038 100644 --- a/cloudinit/config/cc_rightscale_userdata.py +++ b/cloudinit/config/cc_rightscale_userdata.py @@ -50,13 +50,12 @@ user scripts configuration directory, to be run later by ``cc_scripts_user``. # import os +from urllib.parse import parse_qs from cloudinit.settings import PER_INSTANCE from cloudinit import url_helper as uhelp from cloudinit import util -from six.moves.urllib_parse import parse_qs - frequency = PER_INSTANCE MY_NAME = "cc_rightscale_userdata" diff --git a/cloudinit/config/cc_seed_random.py b/cloudinit/config/cc_seed_random.py index a5d7c73f..b65f3ed9 100644 --- a/cloudinit/config/cc_seed_random.py +++ b/cloudinit/config/cc_seed_random.py @@ -61,8 +61,7 @@ used:: import base64 import os - -from six import BytesIO +from io import BytesIO from cloudinit import log as logging from cloudinit.settings import PER_INSTANCE diff --git a/cloudinit/config/cc_zypper_add_repo.py b/cloudinit/config/cc_zypper_add_repo.py index aba26952..05855b0c 100644 --- a/cloudinit/config/cc_zypper_add_repo.py +++ b/cloudinit/config/cc_zypper_add_repo.py @@ -7,7 +7,6 @@ import configobj import os -from six import string_types from textwrap import dedent from cloudinit.config.schema import get_schema_doc @@ -110,7 +109,7 @@ def _format_repo_value(val): return 1 if val else 0 if isinstance(val, (list, tuple)): return "\n ".join([_format_repo_value(v) for v in val]) - if not isinstance(val, string_types): + if not isinstance(val, str): return str(val) return val diff --git a/cloudinit/config/tests/test_snap.py b/cloudinit/config/tests/test_snap.py index 3c472891..cbbb173d 100644 --- a/cloudinit/config/tests/test_snap.py +++ b/cloudinit/config/tests/test_snap.py @@ -1,7 +1,7 @@ # This file is part of cloud-init. See LICENSE file for license information. import re -from six import StringIO +from io import StringIO from cloudinit.config.cc_snap import ( ASSERTIONS_FILE, add_assertions, handle, maybe_install_squashfuse, diff --git a/cloudinit/distros/parsers/hostname.py b/cloudinit/distros/parsers/hostname.py index dd434ac6..e74c083c 100644 --- a/cloudinit/distros/parsers/hostname.py +++ b/cloudinit/distros/parsers/hostname.py @@ -4,7 +4,7 @@ # # This file is part of cloud-init. See LICENSE file for license information. -from six import StringIO +from io import StringIO from cloudinit.distros.parsers import chop_comment diff --git a/cloudinit/distros/parsers/hosts.py b/cloudinit/distros/parsers/hosts.py index 64444581..54e4e934 100644 --- a/cloudinit/distros/parsers/hosts.py +++ b/cloudinit/distros/parsers/hosts.py @@ -4,7 +4,7 @@ # # This file is part of cloud-init. See LICENSE file for license information. -from six import StringIO +from io import StringIO from cloudinit.distros.parsers import chop_comment diff --git a/cloudinit/distros/parsers/resolv_conf.py b/cloudinit/distros/parsers/resolv_conf.py index a62055ae..299d54b5 100644 --- a/cloudinit/distros/parsers/resolv_conf.py +++ b/cloudinit/distros/parsers/resolv_conf.py @@ -4,7 +4,7 @@ # # This file is part of cloud-init. See LICENSE file for license information. -from six import StringIO +from io import StringIO from cloudinit.distros.parsers import chop_comment from cloudinit import log as logging diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py index dcd2645e..7d2a3305 100644 --- a/cloudinit/helpers.py +++ b/cloudinit/helpers.py @@ -12,10 +12,8 @@ from time import time import contextlib import os - -from six import StringIO -from six.moves.configparser import ( - NoSectionError, NoOptionError, RawConfigParser) +from configparser import NoSectionError, NoOptionError, RawConfigParser +from io import StringIO from cloudinit.settings import (PER_INSTANCE, PER_ALWAYS, PER_ONCE, CFG_ENV_NAME) diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py index c033cc8e..19d0199c 100644 --- a/cloudinit/net/dhcp.py +++ b/cloudinit/net/dhcp.py @@ -10,6 +10,7 @@ import os import re import signal import time +from io import StringIO from cloudinit.net import ( EphemeralIPv4Network, find_fallback_nic, get_devicelist, @@ -17,7 +18,6 @@ from cloudinit.net import ( from cloudinit.net.network_state import mask_and_ipv4_to_bcast_addr as bcip from cloudinit import temp_utils from cloudinit import util -from six import StringIO LOG = logging.getLogger(__name__) diff --git a/cloudinit/signal_handler.py b/cloudinit/signal_handler.py index 12fdfe6c..9272d22d 100644 --- a/cloudinit/signal_handler.py +++ b/cloudinit/signal_handler.py @@ -9,8 +9,7 @@ import inspect import signal import sys - -from six import StringIO +from io import StringIO from cloudinit import log as logging from cloudinit import util diff --git a/tests/unittests/test_data.py b/tests/unittests/test_data.py index c59db33d..74cc26ec 100644 --- a/tests/unittests/test_data.py +++ b/tests/unittests/test_data.py @@ -5,10 +5,9 @@ import gzip import logging import os +from io import BytesIO, StringIO from unittest import mock -from six import BytesIO, StringIO - from email import encoders from email.mime.application import MIMEApplication from email.mime.base import MIMEBase diff --git a/tests/unittests/test_datasource/test_gce.py b/tests/unittests/test_datasource/test_gce.py index e9dd6e60..4afbccff 100644 --- a/tests/unittests/test_datasource/test_gce.py +++ b/tests/unittests/test_datasource/test_gce.py @@ -9,9 +9,9 @@ import httpretty import json import re from unittest import mock +from urllib.parse import urlparse from base64 import b64encode, b64decode -from six.moves.urllib_parse import urlparse from cloudinit import distros from cloudinit import helpers diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py index a731f1ed..f754556f 100644 --- a/tests/unittests/test_datasource/test_openstack.py +++ b/tests/unittests/test_datasource/test_openstack.py @@ -8,12 +8,11 @@ import copy import httpretty as hp import json import re +from io import StringIO +from urllib.parse import urlparse from cloudinit.tests import helpers as test_helpers -from six.moves.urllib.parse import urlparse -from six import StringIO, text_type - from cloudinit import helpers from cloudinit import settings from cloudinit.sources import BrokenMetadata, convert_vendordata, UNSET @@ -569,8 +568,7 @@ class TestMetadataReader(test_helpers.HttprettyTestCase): 'uuid': 'b0fa911b-69d4-4476-bbe2-1c92bff6535c'} def register(self, path, body=None, status=200): - content = (body if not isinstance(body, text_type) - else body.encode('utf-8')) + content = body if not isinstance(body, str) else body.encode('utf-8') hp.register_uri( hp.GET, self.burl + "openstack" + path, status=status, body=content) diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 5ede4d77..5562e5d5 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -2,7 +2,7 @@ import copy import os -from six import StringIO +from io import StringIO from textwrap import dedent from unittest import mock diff --git a/tests/unittests/test_filters/test_launch_index.py b/tests/unittests/test_filters/test_launch_index.py index e1a5d2c8..1492361e 100644 --- a/tests/unittests/test_filters/test_launch_index.py +++ b/tests/unittests/test_filters/test_launch_index.py @@ -1,11 +1,10 @@ # This file is part of cloud-init. See LICENSE file for license information. import copy +from itertools import filterfalse from cloudinit.tests import helpers -from six.moves import filterfalse - from cloudinit.filters import launch_index from cloudinit import user_data as ud from cloudinit import util diff --git a/tests/unittests/test_handler/test_handler_locale.py b/tests/unittests/test_handler/test_handler_locale.py index b3deb250..2b22559f 100644 --- a/tests/unittests/test_handler/test_handler_locale.py +++ b/tests/unittests/test_handler/test_handler_locale.py @@ -17,12 +17,11 @@ from cloudinit.tests import helpers as t_help from configobj import ConfigObj -from six import BytesIO - import logging import os import shutil import tempfile +from io import BytesIO from unittest import mock LOG = logging.getLogger(__name__) diff --git a/tests/unittests/test_handler/test_handler_mcollective.py b/tests/unittests/test_handler/test_handler_mcollective.py index 7eec7352..c013a538 100644 --- a/tests/unittests/test_handler/test_handler_mcollective.py +++ b/tests/unittests/test_handler/test_handler_mcollective.py @@ -10,8 +10,8 @@ import configobj import logging import os import shutil -from six import BytesIO import tempfile +from io import BytesIO LOG = logging.getLogger(__name__) diff --git a/tests/unittests/test_handler/test_handler_seed_random.py b/tests/unittests/test_handler/test_handler_seed_random.py index f60dedc2..abecc53b 100644 --- a/tests/unittests/test_handler/test_handler_seed_random.py +++ b/tests/unittests/test_handler/test_handler_seed_random.py @@ -12,8 +12,7 @@ from cloudinit.config import cc_seed_random import gzip import tempfile - -from six import BytesIO +from io import BytesIO from cloudinit import cloud from cloudinit import distros diff --git a/tests/unittests/test_handler/test_handler_set_hostname.py b/tests/unittests/test_handler/test_handler_set_hostname.py index d09ec23a..58abf51a 100644 --- a/tests/unittests/test_handler/test_handler_set_hostname.py +++ b/tests/unittests/test_handler/test_handler_set_hostname.py @@ -13,8 +13,8 @@ from configobj import ConfigObj import logging import os import shutil -from six import BytesIO import tempfile +from io import BytesIO LOG = logging.getLogger(__name__) diff --git a/tests/unittests/test_handler/test_handler_timezone.py b/tests/unittests/test_handler/test_handler_timezone.py index 27eedded..50c45363 100644 --- a/tests/unittests/test_handler/test_handler_timezone.py +++ b/tests/unittests/test_handler/test_handler_timezone.py @@ -18,8 +18,8 @@ from cloudinit.tests import helpers as t_help from configobj import ConfigObj import logging import shutil -from six import BytesIO import tempfile +from io import BytesIO LOG = logging.getLogger(__name__) diff --git a/tests/unittests/test_handler/test_handler_yum_add_repo.py b/tests/unittests/test_handler/test_handler_yum_add_repo.py index b90a3af3..0675bd8f 100644 --- a/tests/unittests/test_handler/test_handler_yum_add_repo.py +++ b/tests/unittests/test_handler/test_handler_yum_add_repo.py @@ -7,8 +7,8 @@ from cloudinit.tests import helpers import logging import shutil -from six import StringIO import tempfile +from io import StringIO LOG = logging.getLogger(__name__) diff --git a/tests/unittests/test_handler/test_handler_zypper_add_repo.py b/tests/unittests/test_handler/test_handler_zypper_add_repo.py index 72ab6c08..9685ff28 100644 --- a/tests/unittests/test_handler/test_handler_zypper_add_repo.py +++ b/tests/unittests/test_handler/test_handler_zypper_add_repo.py @@ -2,6 +2,7 @@ import glob import os +from io import StringIO from cloudinit.config import cc_zypper_add_repo from cloudinit import util @@ -10,7 +11,6 @@ from cloudinit.tests import helpers from cloudinit.tests.helpers import mock import logging -from six import StringIO LOG = logging.getLogger(__name__) diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py index e69a47a9..987a89c9 100644 --- a/tests/unittests/test_handler/test_schema.py +++ b/tests/unittests/test_handler/test_schema.py @@ -10,7 +10,7 @@ from cloudinit.tests.helpers import CiTestCase, mock, skipUnlessJsonSchema from copy import copy import os -from six import StringIO +from io import StringIO from textwrap import dedent from yaml import safe_load -- cgit v1.2.3 From 06e324ff8edb3126e5a8060757a48ceab2b1a121 Mon Sep 17 00:00:00 2001 From: Robert Schweikert Date: Mon, 3 Feb 2020 14:56:51 -0500 Subject: sysconfig: distro-specific config rendering for BOOTPROTO option (#162) - Introduce the "flavor" configuration option for the sysconfig renderer this is necessary to account for differences in the handling of the BOOTPROTO setting between distributions (lp#1858808) + Thanks to Petr Pavlu for the idea - Network config clean up for sysconfig renderer + The introduction of the "flavor" renderer configuration allows us to only write values that are pertinent for the given distro - Set the DHCPv6 client mode on SUSE (lp#1800854) Co-authored-by: Chad Smith LP: #1800854 --- cloudinit/distros/opensuse.py | 1 + cloudinit/net/sysconfig.py | 351 ++++++++++----- tests/unittests/test_distros/test_netconfig.py | 39 +- tests/unittests/test_net.py | 596 +++++++++++++++---------- 4 files changed, 606 insertions(+), 381 deletions(-) (limited to 'tests/unittests/test_distros/test_netconfig.py') diff --git a/cloudinit/distros/opensuse.py b/cloudinit/distros/opensuse.py index e41e2f7b..dd56a3f4 100644 --- a/cloudinit/distros/opensuse.py +++ b/cloudinit/distros/opensuse.py @@ -37,6 +37,7 @@ class Distro(distros.Distro): renderer_configs = { 'sysconfig': { 'control': 'etc/sysconfig/network/config', + 'flavor': 'suse', 'iface_templates': '%(base)s/network/ifcfg-%(name)s', 'netrules_path': ( 'etc/udev/rules.d/85-persistent-net-cloud-init.rules'), diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index 07668d3e..0a387377 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -1,5 +1,6 @@ # This file is part of cloud-init. See LICENSE file for license information. +import copy import io import os import re @@ -85,6 +86,9 @@ class ConfigMap(object): def __getitem__(self, key): return self._conf[key] + def get(self, key): + return self._conf.get(key) + def __contains__(self, key): return key in self._conf @@ -108,6 +112,9 @@ class ConfigMap(object): buf.write("%s=%s\n" % (key, _quote_value(value))) return buf.getvalue() + def update(self, updates): + self._conf.update(updates) + class Route(ConfigMap): """Represents a route configuration.""" @@ -268,13 +275,29 @@ class Renderer(renderer.Renderer): # s1-networkscripts-interfaces.html (or other docs for # details about this) - iface_defaults = tuple([ - ('ONBOOT', True), - ('USERCTL', False), - ('NM_CONTROLLED', False), - ('BOOTPROTO', 'none'), - ('STARTMODE', 'auto'), - ]) + iface_defaults = { + 'rhel': {'ONBOOT': True, 'USERCTL': False, 'NM_CONTROLLED': False, + 'BOOTPROTO': 'none'}, + 'suse': {'BOOTPROTO': 'static', 'STARTMODE': 'auto'}, + } + + cfg_key_maps = { + 'rhel': { + 'accept-ra': 'IPV6_FORCE_ACCEPT_RA', + 'bridge_stp': 'STP', + 'bridge_ageing': 'AGEING', + 'bridge_bridgeprio': 'PRIO', + 'mac_address': 'HWADDR', + 'mtu': 'MTU', + }, + 'suse': { + 'bridge_stp': 'BRIDGE_STP', + 'bridge_ageing': 'BRIDGE_AGEINGTIME', + 'bridge_bridgeprio': 'BRIDGE_PRIORITY', + 'mac_address': 'LLADDR', + 'mtu': 'MTU', + }, + } # If these keys exist, then their values will be used to form # a BONDING_OPTS grouping; otherwise no grouping will be set. @@ -296,12 +319,6 @@ class Renderer(renderer.Renderer): ('bond_primary_reselect', "primary_reselect=%s"), ]) - bridge_opts_keys = tuple([ - ('bridge_stp', 'STP'), - ('bridge_ageing', 'AGEING'), - ('bridge_bridgeprio', 'PRIO'), - ]) - templates = {} def __init__(self, config=None): @@ -319,65 +336,101 @@ class Renderer(renderer.Renderer): 'iface_templates': config.get('iface_templates'), 'route_templates': config.get('route_templates'), } + self.flavor = config.get('flavor', 'rhel') @classmethod - def _render_iface_shared(cls, iface, iface_cfg): - for k, v in cls.iface_defaults: - iface_cfg[k] = v + def _render_iface_shared(cls, iface, iface_cfg, flavor): + flavor_defaults = copy.deepcopy(cls.iface_defaults.get(flavor, {})) + iface_cfg.update(flavor_defaults) - for (old_key, new_key) in [('mac_address', 'HWADDR'), ('mtu', 'MTU')]: + for old_key in ('mac_address', 'mtu', 'accept-ra'): old_value = iface.get(old_key) if old_value is not None: # only set HWADDR on physical interfaces if (old_key == 'mac_address' and iface['type'] not in ['physical', 'infiniband']): continue - iface_cfg[new_key] = old_value - - if iface['accept-ra'] is not None: - iface_cfg['IPV6_FORCE_ACCEPT_RA'] = iface['accept-ra'] + new_key = cls.cfg_key_maps[flavor].get(old_key) + if new_key: + iface_cfg[new_key] = old_value @classmethod - def _render_subnets(cls, iface_cfg, subnets, has_default_route): + def _render_subnets(cls, iface_cfg, subnets, has_default_route, flavor): # setting base values - iface_cfg['BOOTPROTO'] = 'none' + if flavor == 'suse': + iface_cfg['BOOTPROTO'] = 'static' + if 'BRIDGE' in iface_cfg: + iface_cfg['BOOTPROTO'] = 'dhcp' + iface_cfg.drop('BRIDGE') + else: + iface_cfg['BOOTPROTO'] = 'none' # modifying base values according to subnets for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): mtu_key = 'MTU' subnet_type = subnet.get('type') if subnet_type == 'dhcp6' or subnet_type == 'ipv6_dhcpv6-stateful': - # TODO need to set BOOTPROTO to dhcp6 on SUSE - iface_cfg['IPV6INIT'] = True - # Configure network settings using DHCPv6 - iface_cfg['DHCPV6C'] = True + if flavor == 'suse': + # User wants dhcp for both protocols + if iface_cfg['BOOTPROTO'] == 'dhcp4': + iface_cfg['BOOTPROTO'] = 'dhcp' + else: + # Only IPv6 is DHCP, IPv4 may be static + iface_cfg['BOOTPROTO'] = 'dhcp6' + iface_cfg['DHCLIENT6_MODE'] = 'managed' + else: + iface_cfg['IPV6INIT'] = True + # Configure network settings using DHCPv6 + iface_cfg['DHCPV6C'] = True elif subnet_type == 'ipv6_dhcpv6-stateless': - iface_cfg['IPV6INIT'] = True - # Configure network settings using SLAAC from RAs and optional - # info from dhcp server using DHCPv6 - iface_cfg['IPV6_AUTOCONF'] = True - iface_cfg['DHCPV6C'] = True - # Use Information-request to get only stateless configuration - # parameters (i.e., without address). - iface_cfg['DHCPV6C_OPTIONS'] = '-S' + if flavor == 'suse': + # User wants dhcp for both protocols + if iface_cfg['BOOTPROTO'] == 'dhcp4': + iface_cfg['BOOTPROTO'] = 'dhcp' + else: + # Only IPv6 is DHCP, IPv4 may be static + iface_cfg['BOOTPROTO'] = 'dhcp6' + iface_cfg['DHCLIENT6_MODE'] = 'info' + else: + iface_cfg['IPV6INIT'] = True + # Configure network settings using SLAAC from RAs and + # optional info from dhcp server using DHCPv6 + iface_cfg['IPV6_AUTOCONF'] = True + iface_cfg['DHCPV6C'] = True + # Use Information-request to get only stateless + # configuration parameters (i.e., without address). + iface_cfg['DHCPV6C_OPTIONS'] = '-S' elif subnet_type == 'ipv6_slaac': - iface_cfg['IPV6INIT'] = True - # Configure network settings using SLAAC from RAs - iface_cfg['IPV6_AUTOCONF'] = True + if flavor == 'suse': + # User wants dhcp for both protocols + if iface_cfg['BOOTPROTO'] == 'dhcp4': + iface_cfg['BOOTPROTO'] = 'dhcp' + else: + # Only IPv6 is DHCP, IPv4 may be static + iface_cfg['BOOTPROTO'] = 'dhcp6' + iface_cfg['DHCLIENT6_MODE'] = 'info' + else: + iface_cfg['IPV6INIT'] = True + # Configure network settings using SLAAC from RAs + iface_cfg['IPV6_AUTOCONF'] = True elif subnet_type in ['dhcp4', 'dhcp']: + bootproto_in = iface_cfg['BOOTPROTO'] iface_cfg['BOOTPROTO'] = 'dhcp' + if flavor == 'suse' and subnet_type == 'dhcp4': + # If dhcp6 is already specified the user wants dhcp + # for both protocols + if bootproto_in != 'dhcp6': + # Only IPv4 is DHCP, IPv6 may be static + iface_cfg['BOOTPROTO'] = 'dhcp4' elif subnet_type in ['static', 'static6']: + # RH info # grep BOOTPROTO sysconfig.txt -A2 | head -3 # BOOTPROTO=none|bootp|dhcp # 'bootp' or 'dhcp' cause a DHCP client # to run on the device. Any other # value causes any static configuration # in the file to be applied. - # ==> the following should not be set to 'static' - # but should remain 'none' - # if iface_cfg['BOOTPROTO'] == 'none': - # iface_cfg['BOOTPROTO'] = 'static' - if subnet_is_ipv6(subnet): + if subnet_is_ipv6(subnet) and flavor != 'suse': mtu_key = 'IPV6_MTU' iface_cfg['IPV6INIT'] = True if 'mtu' in subnet: @@ -388,18 +441,31 @@ class Renderer(renderer.Renderer): 'Network config: ignoring %s device-level mtu:%s' ' because ipv4 subnet-level mtu:%s provided.', iface_cfg.name, iface_cfg[mtu_key], subnet['mtu']) - iface_cfg[mtu_key] = subnet['mtu'] + if subnet_is_ipv6(subnet): + if flavor == 'suse': + # TODO(rjschwei) write mtu setting to + # /etc/sysctl.d/ + pass + else: + iface_cfg[mtu_key] = subnet['mtu'] + else: + iface_cfg[mtu_key] = subnet['mtu'] elif subnet_type == 'manual': - # If the subnet has an MTU setting, then ONBOOT=True - # to apply the setting - iface_cfg['ONBOOT'] = mtu_key in iface_cfg + if flavor == 'suse': + LOG.debug('Unknown subnet type setting "%s"', subnet_type) + else: + # If the subnet has an MTU setting, then ONBOOT=True + # to apply the setting + iface_cfg['ONBOOT'] = mtu_key in iface_cfg else: raise ValueError("Unknown subnet type '%s' found" " for interface '%s'" % (subnet_type, iface_cfg.name)) if subnet.get('control') == 'manual': - iface_cfg['ONBOOT'] = False - iface_cfg['STARTMODE'] = 'manual' + if flavor == 'suse': + iface_cfg['STARTMODE'] = 'manual' + else: + iface_cfg['ONBOOT'] = False # set IPv4 and IPv6 static addresses ipv4_index = -1 @@ -408,13 +474,14 @@ class Renderer(renderer.Renderer): subnet_type = subnet.get('type') # metric may apply to both dhcp and static config if 'metric' in subnet: - iface_cfg['METRIC'] = subnet['metric'] - # TODO(hjensas): Including dhcp6 here is likely incorrect. DHCPv6 - # does not ever provide a default gateway, the default gateway - # come from RA's. (https://github.com/openSUSE/wicked/issues/570) - if subnet_type in ['dhcp', 'dhcp4', 'dhcp6']: - if has_default_route and iface_cfg['BOOTPROTO'] != 'none': - iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False + if flavor != 'suse': + iface_cfg['METRIC'] = subnet['metric'] + if subnet_type in ['dhcp', 'dhcp4']: + # On SUSE distros 'DHCLIENT_SET_DEFAULT_ROUTE' is a global + # setting in /etc/sysconfig/network/dhcp + if flavor != 'suse': + if has_default_route and iface_cfg['BOOTPROTO'] != 'none': + iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False continue elif subnet_type in IPV6_DYNAMIC_TYPES: continue @@ -423,14 +490,21 @@ class Renderer(renderer.Renderer): ipv6_index = ipv6_index + 1 ipv6_cidr = "%s/%s" % (subnet['address'], subnet['prefix']) if ipv6_index == 0: - iface_cfg['IPV6ADDR'] = ipv6_cidr - iface_cfg['IPADDR6'] = ipv6_cidr + if flavor == 'suse': + iface_cfg['IPADDR6'] = ipv6_cidr + else: + iface_cfg['IPV6ADDR'] = ipv6_cidr elif ipv6_index == 1: - iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr - iface_cfg['IPADDR6_0'] = ipv6_cidr + if flavor == 'suse': + iface_cfg['IPADDR6_1'] = ipv6_cidr + else: + iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr else: - iface_cfg['IPV6ADDR_SECONDARIES'] += " " + ipv6_cidr - iface_cfg['IPADDR6_%d' % ipv6_index] = ipv6_cidr + if flavor == 'suse': + iface_cfg['IPADDR6_%d' % ipv6_index] = ipv6_cidr + else: + iface_cfg['IPV6ADDR_SECONDARIES'] += \ + " " + ipv6_cidr else: ipv4_index = ipv4_index + 1 suff = "" if ipv4_index == 0 else str(ipv4_index) @@ -438,17 +512,17 @@ class Renderer(renderer.Renderer): iface_cfg['NETMASK' + suff] = \ net_prefix_to_ipv4_mask(subnet['prefix']) - if 'gateway' in subnet: + if 'gateway' in subnet and flavor != 'suse': iface_cfg['DEFROUTE'] = True if is_ipv6_addr(subnet['gateway']): iface_cfg['IPV6_DEFAULTGW'] = subnet['gateway'] else: iface_cfg['GATEWAY'] = subnet['gateway'] - if 'dns_search' in subnet: + if 'dns_search' in subnet and flavor != 'suse': iface_cfg['DOMAIN'] = ' '.join(subnet['dns_search']) - if 'dns_nameservers' in subnet: + if 'dns_nameservers' in subnet and flavor != 'suse': if len(subnet['dns_nameservers']) > 3: # per resolv.conf(5) MAXNS sets this to 3. LOG.debug("%s has %d entries in dns_nameservers. " @@ -458,7 +532,12 @@ class Renderer(renderer.Renderer): iface_cfg['DNS' + str(i)] = k @classmethod - def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): + def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets, flavor): + # TODO(rjschwei): route configuration on SUSE distro happens via + # ifroute-* files, see lp#1812117. SUSE currently carries a local + # patch in their package. + if flavor == 'suse': + return for _, subnet in enumerate(subnets, start=len(iface_cfg.children)): subnet_type = subnet.get('type') for route in subnet.get('routes', []): @@ -486,14 +565,7 @@ class Renderer(renderer.Renderer): # TODO(harlowja): add validation that no other iface has # also provided the default route? iface_cfg['DEFROUTE'] = True - # TODO(hjensas): Including dhcp6 here is likely incorrect. - # DHCPv6 does not ever provide a default gateway, the - # default gateway come from RA's. - # (https://github.com/openSUSE/wicked/issues/570) - if iface_cfg['BOOTPROTO'] in ('dhcp', 'dhcp4', 'dhcp6'): - # NOTE(hjensas): DHCLIENT_SET_DEFAULT_ROUTE is SuSE - # only. RHEL, CentOS, Fedora does not implement this - # option. + if iface_cfg['BOOTPROTO'] in ('dhcp', 'dhcp4'): iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = True if 'gateway' in route: if is_ipv6: @@ -537,7 +609,9 @@ class Renderer(renderer.Renderer): iface_cfg['BONDING_OPTS'] = " ".join(bond_opts) @classmethod - def _render_physical_interfaces(cls, network_state, iface_contents): + def _render_physical_interfaces( + cls, network_state, iface_contents, flavor + ): physical_filter = renderer.filter_by_physical for iface in network_state.iter_interfaces(physical_filter): iface_name = iface['name'] @@ -546,12 +620,15 @@ class Renderer(renderer.Renderer): route_cfg = iface_cfg.routes cls._render_subnets( - iface_cfg, iface_subnets, network_state.has_default_route + iface_cfg, iface_subnets, network_state.has_default_route, + flavor + ) + cls._render_subnet_routes( + iface_cfg, route_cfg, iface_subnets, flavor ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) @classmethod - def _render_bond_interfaces(cls, network_state, iface_contents): + def _render_bond_interfaces(cls, network_state, iface_contents, flavor): bond_filter = renderer.filter_by_type('bond') slave_filter = renderer.filter_by_attr('bond-master') for iface in network_state.iter_interfaces(bond_filter): @@ -565,17 +642,24 @@ class Renderer(renderer.Renderer): master_cfgs.extend(iface_cfg.children) for master_cfg in master_cfgs: master_cfg['BONDING_MASTER'] = True - master_cfg.kind = 'bond' + if flavor != 'suse': + master_cfg.kind = 'bond' if iface.get('mac_address'): - iface_cfg['MACADDR'] = iface.get('mac_address') + if flavor == 'suse': + iface_cfg['LLADDR'] = iface.get('mac_address') + else: + iface_cfg['MACADDR'] = iface.get('mac_address') iface_subnets = iface.get("subnets", []) route_cfg = iface_cfg.routes cls._render_subnets( - iface_cfg, iface_subnets, network_state.has_default_route + iface_cfg, iface_subnets, network_state.has_default_route, + flavor + ) + cls._render_subnet_routes( + iface_cfg, route_cfg, iface_subnets, flavor ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) # iter_interfaces on network-state is not sorted to produce # consistent numbers we need to sort. @@ -585,28 +669,44 @@ class Renderer(renderer.Renderer): if slave_iface['bond-master'] == iface_name]) for index, bond_slave in enumerate(bond_slaves): - slavestr = 'BONDING_SLAVE%s' % index + if flavor == 'suse': + slavestr = 'BONDING_SLAVE_%s' % index + else: + slavestr = 'BONDING_SLAVE%s' % index iface_cfg[slavestr] = bond_slave slave_cfg = iface_contents[bond_slave] - slave_cfg['MASTER'] = iface_name - slave_cfg['SLAVE'] = True + if flavor == 'suse': + slave_cfg['BOOTPROTO'] = 'none' + slave_cfg['STARTMODE'] = 'hotplug' + else: + slave_cfg['MASTER'] = iface_name + slave_cfg['SLAVE'] = True @classmethod - def _render_vlan_interfaces(cls, network_state, iface_contents): + def _render_vlan_interfaces(cls, network_state, iface_contents, flavor): vlan_filter = renderer.filter_by_type('vlan') for iface in network_state.iter_interfaces(vlan_filter): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] - iface_cfg['VLAN'] = True - iface_cfg['PHYSDEV'] = iface_name[:iface_name.rfind('.')] + if flavor == 'suse': + vlan_id = iface.get('vlan_id') + if vlan_id: + iface_cfg['VLAN_ID'] = vlan_id + iface_cfg['ETHERDEVICE'] = iface_name[:iface_name.rfind('.')] + else: + iface_cfg['VLAN'] = True + iface_cfg['PHYSDEV'] = iface_name[:iface_name.rfind('.')] iface_subnets = iface.get("subnets", []) route_cfg = iface_cfg.routes cls._render_subnets( - iface_cfg, iface_subnets, network_state.has_default_route + iface_cfg, iface_subnets, network_state.has_default_route, + flavor + ) + cls._render_subnet_routes( + iface_cfg, route_cfg, iface_subnets, flavor ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) @staticmethod def _render_dns(network_state, existing_dns_path=None): @@ -643,19 +743,39 @@ class Renderer(renderer.Renderer): return out @classmethod - def _render_bridge_interfaces(cls, network_state, iface_contents): + def _render_bridge_interfaces(cls, network_state, iface_contents, flavor): + bridge_key_map = { + old_k: new_k for old_k, new_k in cls.cfg_key_maps[flavor].items() + if old_k.startswith('bridge')} bridge_filter = renderer.filter_by_type('bridge') + for iface in network_state.iter_interfaces(bridge_filter): iface_name = iface['name'] iface_cfg = iface_contents[iface_name] - iface_cfg.kind = 'bridge' - for old_key, new_key in cls.bridge_opts_keys: + if flavor != 'suse': + iface_cfg.kind = 'bridge' + for old_key, new_key in bridge_key_map.items(): if old_key in iface: iface_cfg[new_key] = iface[old_key] - if iface.get('mac_address'): - iface_cfg['MACADDR'] = iface.get('mac_address') + if flavor == 'suse': + if 'BRIDGE_STP' in iface_cfg: + if iface_cfg.get('BRIDGE_STP'): + iface_cfg['BRIDGE_STP'] = 'on' + else: + iface_cfg['BRIDGE_STP'] = 'off' + if iface.get('mac_address'): + key = 'MACADDR' + if flavor == 'suse': + key = 'LLADDRESS' + iface_cfg[key] = iface.get('mac_address') + + if flavor == 'suse': + if iface.get('bridge_ports', []): + iface_cfg['BRIDGE_PORTS'] = '%s' % " ".join( + iface.get('bridge_ports') + ) # Is this the right key to get all the connected interfaces? for bridged_iface_name in iface.get('bridge_ports', []): # Ensure all bridged interfaces are correctly tagged @@ -664,17 +784,23 @@ class Renderer(renderer.Renderer): bridged_cfgs = [bridged_cfg] bridged_cfgs.extend(bridged_cfg.children) for bridge_cfg in bridged_cfgs: - bridge_cfg['BRIDGE'] = iface_name + bridge_value = iface_name + if flavor == 'suse': + bridge_value = 'yes' + bridge_cfg['BRIDGE'] = bridge_value iface_subnets = iface.get("subnets", []) route_cfg = iface_cfg.routes cls._render_subnets( - iface_cfg, iface_subnets, network_state.has_default_route + iface_cfg, iface_subnets, network_state.has_default_route, + flavor + ) + cls._render_subnet_routes( + iface_cfg, route_cfg, iface_subnets, flavor ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) @classmethod - def _render_ib_interfaces(cls, network_state, iface_contents): + def _render_ib_interfaces(cls, network_state, iface_contents, flavor): ib_filter = renderer.filter_by_type('infiniband') for iface in network_state.iter_interfaces(ib_filter): iface_name = iface['name'] @@ -683,12 +809,15 @@ class Renderer(renderer.Renderer): iface_subnets = iface.get("subnets", []) route_cfg = iface_cfg.routes cls._render_subnets( - iface_cfg, iface_subnets, network_state.has_default_route + iface_cfg, iface_subnets, network_state.has_default_route, + flavor + ) + cls._render_subnet_routes( + iface_cfg, route_cfg, iface_subnets, flavor ) - cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) @classmethod - def _render_sysconfig(cls, base_sysconf_dir, network_state, + def _render_sysconfig(cls, base_sysconf_dir, network_state, flavor, templates=None): '''Given state, return /etc/sysconfig files + contents''' if not templates: @@ -699,13 +828,17 @@ class Renderer(renderer.Renderer): continue iface_name = iface['name'] iface_cfg = NetInterface(iface_name, base_sysconf_dir, templates) - cls._render_iface_shared(iface, iface_cfg) + if flavor == 'suse': + iface_cfg.drop('DEVICE') + # If type detection fails it is considered a bug in SUSE + iface_cfg.drop('TYPE') + cls._render_iface_shared(iface, iface_cfg, flavor) iface_contents[iface_name] = iface_cfg - cls._render_physical_interfaces(network_state, iface_contents) - cls._render_bond_interfaces(network_state, iface_contents) - cls._render_vlan_interfaces(network_state, iface_contents) - cls._render_bridge_interfaces(network_state, iface_contents) - cls._render_ib_interfaces(network_state, iface_contents) + cls._render_physical_interfaces(network_state, iface_contents, flavor) + cls._render_bond_interfaces(network_state, iface_contents, flavor) + cls._render_vlan_interfaces(network_state, iface_contents, flavor) + cls._render_bridge_interfaces(network_state, iface_contents, flavor) + cls._render_ib_interfaces(network_state, iface_contents, flavor) contents = {} for iface_name, iface_cfg in iface_contents.items(): if iface_cfg or iface_cfg.children: @@ -727,7 +860,7 @@ class Renderer(renderer.Renderer): file_mode = 0o644 base_sysconf_dir = util.target_path(target, self.sysconf_dir) for path, data in self._render_sysconfig(base_sysconf_dir, - network_state, + network_state, self.flavor, templates=templates).items(): util.write_file(path, data, file_mode) if self.dns_path: diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 5562e5d5..ccf66161 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -485,7 +485,6 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): NETMASK=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -494,7 +493,6 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): DEVICE=eth1 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -513,13 +511,11 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): BOOTPROTO=none DEFROUTE=yes DEVICE=eth0 - IPADDR6=2607:f0d0:1002:0011::2/64 IPV6ADDR=2607:f0d0:1002:0011::2/64 IPV6INIT=yes IPV6_DEFAULTGW=2607:f0d0:1002:0011::1 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -528,7 +524,6 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): DEVICE=eth1 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -573,26 +568,14 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): """Opensuse uses apply_network_config and renders sysconfig""" expected_cfgs = { self.ifcfg_path('eth0'): dedent("""\ - BOOTPROTO=none - DEFROUTE=yes - DEVICE=eth0 - GATEWAY=192.168.1.254 + BOOTPROTO=static IPADDR=192.168.1.5 NETMASK=255.255.255.0 - NM_CONTROLLED=no - ONBOOT=yes STARTMODE=auto - TYPE=Ethernet - USERCTL=no """), self.ifcfg_path('eth1'): dedent("""\ - BOOTPROTO=dhcp - DEVICE=eth1 - NM_CONTROLLED=no - ONBOOT=yes + BOOTPROTO=dhcp4 STARTMODE=auto - TYPE=Ethernet - USERCTL=no """), } self._apply_and_verify(self.distro.apply_network_config, @@ -603,27 +586,13 @@ class TestNetCfgDistroOpensuse(TestNetCfgDistroBase): """Opensuse uses apply_network_config and renders sysconfig w/ipv6""" expected_cfgs = { self.ifcfg_path('eth0'): dedent("""\ - BOOTPROTO=none - DEFROUTE=yes - DEVICE=eth0 + BOOTPROTO=static IPADDR6=2607:f0d0:1002:0011::2/64 - IPV6ADDR=2607:f0d0:1002:0011::2/64 - IPV6INIT=yes - IPV6_DEFAULTGW=2607:f0d0:1002:0011::1 - NM_CONTROLLED=no - ONBOOT=yes STARTMODE=auto - TYPE=Ethernet - USERCTL=no """), self.ifcfg_path('eth1'): dedent("""\ - BOOTPROTO=dhcp - DEVICE=eth1 - NM_CONTROLLED=no - ONBOOT=yes + BOOTPROTO=dhcp4 STARTMODE=auto - TYPE=Ethernet - USERCTL=no """), } self._apply_and_verify(self.distro.apply_network_config, diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 001ad010..bedd05fe 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -489,18 +489,11 @@ OS_SAMPLES = [ """ # Created by cloud-init on instance boot automatically, do not edit. # -BOOTPROTO=none -DEFROUTE=yes -DEVICE=eth0 -GATEWAY=172.19.3.254 -HWADDR=fa:16:3e:ed:9a:59 +BOOTPROTO=static IPADDR=172.19.1.34 +LLADDR=fa:16:3e:ed:9a:59 NETMASK=255.255.252.0 -NM_CONTROLLED=no -ONBOOT=yes STARTMODE=auto -TYPE=Ethernet -USERCTL=no """.lstrip()), ('etc/resolv.conf', """ @@ -532,7 +525,6 @@ IPADDR=172.19.1.34 NETMASK=255.255.252.0 NM_CONTROLLED=no ONBOOT=yes -STARTMODE=auto TYPE=Ethernet USERCTL=no """.lstrip()), @@ -591,20 +583,13 @@ dns = none """ # Created by cloud-init on instance boot automatically, do not edit. # -BOOTPROTO=none -DEFROUTE=yes -DEVICE=eth0 -GATEWAY=172.19.3.254 -HWADDR=fa:16:3e:ed:9a:59 +BOOTPROTO=static IPADDR=172.19.1.34 IPADDR1=10.0.0.10 +LLADDR=fa:16:3e:ed:9a:59 NETMASK=255.255.252.0 NETMASK1=255.255.255.0 -NM_CONTROLLED=no -ONBOOT=yes STARTMODE=auto -TYPE=Ethernet -USERCTL=no """.lstrip()), ('etc/resolv.conf', """ @@ -638,7 +623,6 @@ NETMASK=255.255.252.0 NETMASK1=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes -STARTMODE=auto TYPE=Ethernet USERCTL=no """.lstrip()), @@ -717,25 +701,14 @@ dns = none """ # Created by cloud-init on instance boot automatically, do not edit. # -BOOTPROTO=none -DEFROUTE=yes -DEVICE=eth0 -GATEWAY=172.19.3.254 -HWADDR=fa:16:3e:ed:9a:59 +BOOTPROTO=static IPADDR=172.19.1.34 IPADDR6=2001:DB8::10/64 -IPADDR6_0=2001:DB9::10/64 +IPADDR6_1=2001:DB9::10/64 IPADDR6_2=2001:DB10::10/64 -IPV6ADDR=2001:DB8::10/64 -IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" -IPV6INIT=yes -IPV6_DEFAULTGW=2001:DB8::1 +LLADDR=fa:16:3e:ed:9a:59 NETMASK=255.255.252.0 -NM_CONTROLLED=no -ONBOOT=yes STARTMODE=auto -TYPE=Ethernet -USERCTL=no """.lstrip()), ('etc/resolv.conf', """ @@ -764,9 +737,6 @@ DEVICE=eth0 GATEWAY=172.19.3.254 HWADDR=fa:16:3e:ed:9a:59 IPADDR=172.19.1.34 -IPADDR6=2001:DB8::10/64 -IPADDR6_0=2001:DB9::10/64 -IPADDR6_2=2001:DB10::10/64 IPV6ADDR=2001:DB8::10/64 IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" IPV6INIT=yes @@ -774,7 +744,6 @@ IPV6_DEFAULTGW=2001:DB8::1 NETMASK=255.255.252.0 NM_CONTROLLED=no ONBOOT=yes -STARTMODE=auto TYPE=Ethernet USERCTL=no """.lstrip()), @@ -884,14 +853,25 @@ NETWORK_CONFIGS = { via: 65.61.151.37 set-name: eth99 """).rstrip(' '), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-eth1': textwrap.dedent("""\ + BOOTPROTO=static + LLADDR=cf:d6:af:48:e8:80 + STARTMODE=auto"""), + 'ifcfg-eth99': textwrap.dedent("""\ + BOOTPROTO=dhcp4 + LLADDR=c0:d6:9f:2c:e8:80 + IPADDR=192.168.21.3 + NETMASK=255.255.255.0 + STARTMODE=auto"""), + }, + 'expected_sysconfig_rhel': { 'ifcfg-eth1': textwrap.dedent("""\ BOOTPROTO=none DEVICE=eth1 HWADDR=cf:d6:af:48:e8:80 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no"""), 'ifcfg-eth99': textwrap.dedent("""\ @@ -909,7 +889,6 @@ NETWORK_CONFIGS = { METRIC=10000 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no"""), }, @@ -963,6 +942,12 @@ NETWORK_CONFIGS = { dhcp4: true dhcp6: true """).rstrip(' '), + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp + DHCLIENT6_MODE=managed + STARTMODE=auto""") + }, 'yaml': textwrap.dedent("""\ version: 1 config: @@ -1013,18 +998,26 @@ NETWORK_CONFIGS = { address: 2001:1::1/64 mtu: 1500 """).rstrip(' '), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=static + IPADDR=192.168.14.2 + IPADDR6=2001:1::1/64 + NETMASK=255.255.255.0 + STARTMODE=auto + MTU=9000 + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-iface0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=iface0 IPADDR=192.168.14.2 - IPADDR6=2001:1::1/64 IPV6ADDR=2001:1::1/64 IPV6INIT=yes NETMASK=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no MTU=9000 @@ -1032,6 +1025,23 @@ NETWORK_CONFIGS = { """), }, }, + 'v6_and_v4': { + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp + DHCLIENT6_MODE=managed + STARTMODE=auto""") + }, + 'yaml': textwrap.dedent("""\ + version: 1 + config: + - type: 'physical' + name: 'iface0' + subnets: + - type: dhcp6 + - type: dhcp4 + """).rstrip(' '), + }, 'dhcpv6_only': { 'expected_eni': textwrap.dedent("""\ auto lo @@ -1055,7 +1065,14 @@ NETWORK_CONFIGS = { subnets: - {'type': 'dhcp6'} """).rstrip(' '), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp6 + DHCLIENT6_MODE=managed + STARTMODE=auto + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-iface0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=iface0 @@ -1064,7 +1081,6 @@ NETWORK_CONFIGS = { DEVICE=iface0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -1103,7 +1119,14 @@ NETWORK_CONFIGS = { dhcp6: true accept-ra: true """).rstrip(' '), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp6 + DHCLIENT6_MODE=managed + STARTMODE=auto + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-iface0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=iface0 @@ -1113,7 +1136,6 @@ NETWORK_CONFIGS = { DEVICE=iface0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -1152,7 +1174,14 @@ NETWORK_CONFIGS = { dhcp6: true accept-ra: false """).rstrip(' '), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp6 + DHCLIENT6_MODE=managed + STARTMODE=auto + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-iface0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=iface0 @@ -1162,7 +1191,6 @@ NETWORK_CONFIGS = { DEVICE=iface0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -1192,7 +1220,14 @@ NETWORK_CONFIGS = { subnets: - {'type': 'ipv6_slaac'} """).rstrip(' '), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp6 + DHCLIENT6_MODE=info + STARTMODE=auto + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-iface0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=iface0 @@ -1201,7 +1236,6 @@ NETWORK_CONFIGS = { DEVICE=iface0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -1231,7 +1265,14 @@ NETWORK_CONFIGS = { subnets: - {'type': 'ipv6_dhcpv6-stateless'} """).rstrip(' '), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp6 + DHCLIENT6_MODE=info + STARTMODE=auto + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-iface0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=iface0 @@ -1242,7 +1283,6 @@ NETWORK_CONFIGS = { DEVICE=iface0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -1273,7 +1313,14 @@ NETWORK_CONFIGS = { - {'type': 'ipv6_dhcpv6-stateful'} accept-ra: true """).rstrip(' '), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp6 + DHCLIENT6_MODE=managed + STARTMODE=auto + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-iface0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=iface0 @@ -1283,7 +1330,6 @@ NETWORK_CONFIGS = { DEVICE=iface0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -1478,7 +1524,80 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true - sacchromyces.maas - brettanomyces.maas """).rstrip(' '), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-bond0': textwrap.dedent("""\ + BONDING_MASTER=yes + BONDING_OPTS="mode=active-backup """ + """xmit_hash_policy=layer3+4 """ + """miimon=100" + BONDING_SLAVE_0=eth1 + BONDING_SLAVE_1=eth2 + BOOTPROTO=dhcp6 + DHCLIENT6_MODE=managed + LLADDR=aa:bb:cc:dd:ee:ff + STARTMODE=auto"""), + 'ifcfg-bond0.200': textwrap.dedent("""\ + BOOTPROTO=dhcp4 + ETHERDEVICE=bond0 + STARTMODE=auto + VLAN_ID=200"""), + 'ifcfg-br0': textwrap.dedent("""\ + BRIDGE_AGEINGTIME=250 + BOOTPROTO=static + IPADDR=192.168.14.2 + IPADDR6=2001:1::1/64 + LLADDRESS=bb:bb:bb:bb:bb:aa + NETMASK=255.255.255.0 + BRIDGE_PRIORITY=22 + BRIDGE_PORTS='eth3 eth4' + STARTMODE=auto + BRIDGE_STP=off"""), + 'ifcfg-eth0': textwrap.dedent("""\ + BOOTPROTO=static + LLADDR=c0:d6:9f:2c:e8:80 + STARTMODE=auto"""), + 'ifcfg-eth0.101': textwrap.dedent("""\ + BOOTPROTO=static + IPADDR=192.168.0.2 + IPADDR1=192.168.2.10 + MTU=1500 + NETMASK=255.255.255.0 + NETMASK1=255.255.255.0 + ETHERDEVICE=eth0 + STARTMODE=auto + VLAN_ID=101"""), + 'ifcfg-eth1': textwrap.dedent("""\ + BOOTPROTO=none + LLADDR=aa:d6:9f:2c:e8:80 + STARTMODE=hotplug"""), + 'ifcfg-eth2': textwrap.dedent("""\ + BOOTPROTO=none + LLADDR=c0:bb:9f:2c:e8:80 + STARTMODE=hotplug"""), + 'ifcfg-eth3': textwrap.dedent("""\ + BOOTPROTO=static + BRIDGE=yes + LLADDR=66:bb:9f:2c:e8:80 + STARTMODE=auto"""), + 'ifcfg-eth4': textwrap.dedent("""\ + BOOTPROTO=static + BRIDGE=yes + LLADDR=98:bb:9f:2c:e8:80 + STARTMODE=auto"""), + 'ifcfg-eth5': textwrap.dedent("""\ + BOOTPROTO=dhcp + LLADDR=98:bb:9f:2c:e8:8a + STARTMODE=manual"""), + 'ifcfg-ib0': textwrap.dedent("""\ + BOOTPROTO=static + LLADDR=a0:00:02:20:fe:80:00:00:00:00:00:00:ec:0d:9a:03:00:15:e2:c1 + IPADDR=192.168.200.7 + MTU=9000 + NETMASK=255.255.255.0 + STARTMODE=auto + TYPE=InfiniBand"""), + }, + 'expected_sysconfig_rhel': { 'ifcfg-bond0': textwrap.dedent("""\ BONDING_MASTER=yes BONDING_OPTS="mode=active-backup """ @@ -1493,7 +1612,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true MACADDR=aa:bb:cc:dd:ee:ff NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Bond USERCTL=no"""), 'ifcfg-bond0.200': textwrap.dedent("""\ @@ -1503,7 +1621,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true NM_CONTROLLED=no ONBOOT=yes PHYSDEV=bond0 - STARTMODE=auto TYPE=Ethernet USERCTL=no VLAN=yes"""), @@ -1513,7 +1630,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true DEFROUTE=yes DEVICE=br0 IPADDR=192.168.14.2 - IPADDR6=2001:1::1/64 IPV6ADDR=2001:1::1/64 IPV6INIT=yes IPV6_DEFAULTGW=2001:4800:78ff:1b::1 @@ -1522,7 +1638,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true NM_CONTROLLED=no ONBOOT=yes PRIO=22 - STARTMODE=auto STP=no TYPE=Bridge USERCTL=no"""), @@ -1532,7 +1647,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true HWADDR=c0:d6:9f:2c:e8:80 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no"""), 'ifcfg-eth0.101': textwrap.dedent("""\ @@ -1551,7 +1665,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true NM_CONTROLLED=no ONBOOT=yes PHYSDEV=eth0 - STARTMODE=auto TYPE=Ethernet USERCTL=no VLAN=yes"""), @@ -1562,7 +1675,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true MASTER=bond0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto SLAVE=yes TYPE=Ethernet USERCTL=no"""), @@ -1573,7 +1685,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true MASTER=bond0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto SLAVE=yes TYPE=Ethernet USERCTL=no"""), @@ -1584,7 +1695,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true HWADDR=66:bb:9f:2c:e8:80 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no"""), 'ifcfg-eth4': textwrap.dedent("""\ @@ -1594,7 +1704,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true HWADDR=98:bb:9f:2c:e8:80 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no"""), 'ifcfg-eth5': textwrap.dedent("""\ @@ -1604,7 +1713,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true HWADDR=98:bb:9f:2c:e8:8a NM_CONTROLLED=no ONBOOT=no - STARTMODE=manual TYPE=Ethernet USERCTL=no"""), 'ifcfg-ib0': textwrap.dedent("""\ @@ -1616,7 +1724,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true NETMASK=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=InfiniBand USERCTL=no"""), }, @@ -2012,58 +2119,29 @@ iface bond0 inet6 static """fail_over_mac=active """ """primary=bond0s0 """ """primary_reselect=always" - BONDING_SLAVE0=bond0s0 - BONDING_SLAVE1=bond0s1 - BOOTPROTO=none - DEFROUTE=yes - DEVICE=bond0 - GATEWAY=192.168.0.1 - MACADDR=aa:bb:cc:dd:e8:ff + BONDING_SLAVE_0=bond0s0 + BONDING_SLAVE_1=bond0s1 + BOOTPROTO=static + LLADDR=aa:bb:cc:dd:e8:ff IPADDR=192.168.0.2 IPADDR1=192.168.1.2 IPADDR6=2001:1::1/92 - IPV6ADDR=2001:1::1/92 - IPV6INIT=yes MTU=9000 NETMASK=255.255.255.0 NETMASK1=255.255.255.0 - NM_CONTROLLED=no - ONBOOT=yes STARTMODE=auto - TYPE=Bond - USERCTL=no """), 'ifcfg-bond0s0': textwrap.dedent("""\ BOOTPROTO=none - DEVICE=bond0s0 - HWADDR=aa:bb:cc:dd:e8:00 - MASTER=bond0 - NM_CONTROLLED=no - ONBOOT=yes - SLAVE=yes - STARTMODE=auto - TYPE=Ethernet - USERCTL=no - """), - 'ifroute-bond0': textwrap.dedent("""\ - ADDRESS0=10.1.3.0 - GATEWAY0=192.168.0.3 - NETMASK0=255.255.255.0 + LLADDR=aa:bb:cc:dd:e8:00 + STARTMODE=hotplug """), 'ifcfg-bond0s1': textwrap.dedent("""\ BOOTPROTO=none - DEVICE=bond0s1 - HWADDR=aa:bb:cc:dd:e8:01 - MASTER=bond0 - NM_CONTROLLED=no - ONBOOT=yes - SLAVE=yes - STARTMODE=auto - TYPE=Ethernet - USERCTL=no + LLADDR=aa:bb:cc:dd:e8:01 + STARTMODE=hotplug """), }, - 'expected_sysconfig_rhel': { 'ifcfg-bond0': textwrap.dedent("""\ BONDING_MASTER=yes @@ -2082,7 +2160,6 @@ iface bond0 inet6 static MACADDR=aa:bb:cc:dd:e8:ff IPADDR=192.168.0.2 IPADDR1=192.168.1.2 - IPADDR6=2001:1::1/92 IPV6ADDR=2001:1::1/92 IPV6INIT=yes MTU=9000 @@ -2090,7 +2167,6 @@ iface bond0 inet6 static NETMASK1=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Bond USERCTL=no """), @@ -2102,7 +2178,6 @@ iface bond0 inet6 static NM_CONTROLLED=no ONBOOT=yes SLAVE=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -2125,7 +2200,6 @@ iface bond0 inet6 static NM_CONTROLLED=no ONBOOT=yes SLAVE=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -2156,14 +2230,32 @@ iface bond0 inet6 static netmask: '::' network: '::' """), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + # TODO RJS: unknown proper BOOTPROTO setting ask Marius + 'ifcfg-en0': textwrap.dedent("""\ + BOOTPROTO=static + LLADDR=aa:bb:cc:dd:e8:00 + STARTMODE=auto"""), + 'ifcfg-en0.99': textwrap.dedent("""\ + BOOTPROTO=static + IPADDR=192.168.2.2 + IPADDR1=192.168.1.2 + IPADDR6=2001:1::bbbb/96 + MTU=2222 + NETMASK=255.255.255.0 + NETMASK1=255.255.255.0 + STARTMODE=auto + ETHERDEVICE=en0 + VLAN_ID=99 + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-en0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=en0 HWADDR=aa:bb:cc:dd:e8:00 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no"""), 'ifcfg-en0.99': textwrap.dedent("""\ @@ -2173,7 +2265,6 @@ iface bond0 inet6 static GATEWAY=192.168.1.1 IPADDR=192.168.2.2 IPADDR1=192.168.1.2 - IPADDR6=2001:1::bbbb/96 IPV6ADDR=2001:1::bbbb/96 IPV6INIT=yes IPV6_DEFAULTGW=2001:1::1 @@ -2183,7 +2274,6 @@ iface bond0 inet6 static NM_CONTROLLED=no ONBOOT=yes PHYSDEV=en0 - STARTMODE=auto TYPE=Ethernet USERCTL=no VLAN=yes"""), @@ -2216,7 +2306,32 @@ iface bond0 inet6 static subnets: - type: static address: 192.168.2.2/24"""), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-br0': textwrap.dedent("""\ + BOOTPROTO=static + IPADDR=192.168.2.2 + NETMASK=255.255.255.0 + STARTMODE=auto + BRIDGE_STP=off + BRIDGE_PRIORITY=22 + BRIDGE_PORTS='eth0 eth1' + """), + 'ifcfg-eth0': textwrap.dedent("""\ + BOOTPROTO=static + BRIDGE=yes + LLADDR=52:54:00:12:34:00 + IPADDR6=2001:1::100/96 + STARTMODE=auto + """), + 'ifcfg-eth1': textwrap.dedent("""\ + BOOTPROTO=static + BRIDGE=yes + LLADDR=52:54:00:12:34:01 + IPADDR6=2001:1::101/96 + STARTMODE=auto + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-br0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=br0 @@ -2225,7 +2340,6 @@ iface bond0 inet6 static NM_CONTROLLED=no ONBOOT=yes PRIO=22 - STARTMODE=auto STP=no TYPE=Bridge USERCTL=no @@ -2235,12 +2349,10 @@ iface bond0 inet6 static BRIDGE=br0 DEVICE=eth0 HWADDR=52:54:00:12:34:00 - IPADDR6=2001:1::100/96 IPV6ADDR=2001:1::100/96 IPV6INIT=yes NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -2249,12 +2361,10 @@ iface bond0 inet6 static BRIDGE=br0 DEVICE=eth1 HWADDR=52:54:00:12:34:01 - IPADDR6=2001:1::101/96 IPV6ADDR=2001:1::101/96 IPV6INIT=yes NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -2320,7 +2430,27 @@ iface bond0 inet6 static macaddress: 52:54:00:12:34:ff set-name: eth2 """), - 'expected_sysconfig': { + 'expected_sysconfig_opensuse': { + 'ifcfg-eth0': textwrap.dedent("""\ + BOOTPROTO=static + LLADDR=52:54:00:12:34:00 + IPADDR=192.168.1.2 + NETMASK=255.255.255.0 + STARTMODE=manual + """), + 'ifcfg-eth1': textwrap.dedent("""\ + BOOTPROTO=static + LLADDR=52:54:00:12:34:aa + MTU=1480 + STARTMODE=auto + """), + 'ifcfg-eth2': textwrap.dedent("""\ + BOOTPROTO=static + LLADDR=52:54:00:12:34:ff + STARTMODE=manual + """), + }, + 'expected_sysconfig_rhel': { 'ifcfg-eth0': textwrap.dedent("""\ BOOTPROTO=none DEVICE=eth0 @@ -2329,7 +2459,6 @@ iface bond0 inet6 static NETMASK=255.255.255.0 NM_CONTROLLED=no ONBOOT=no - STARTMODE=manual TYPE=Ethernet USERCTL=no """), @@ -2340,7 +2469,6 @@ iface bond0 inet6 static MTU=1480 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -2350,7 +2478,6 @@ iface bond0 inet6 static HWADDR=52:54:00:12:34:ff NM_CONTROLLED=no ONBOOT=no - STARTMODE=manual TYPE=Ethernet USERCTL=no """), @@ -2681,7 +2808,7 @@ class TestRhelSysConfigRendering(CiTestCase): header = ('# Created by cloud-init on instance boot automatically, ' 'do not edit.\n#\n') - expected_name = 'expected_sysconfig' + expected_name = 'expected_sysconfig_rhel' def _get_renderer(self): distro_cls = distros.fetch('rhel') @@ -2768,7 +2895,6 @@ DEVICE=eth1000 HWADDR=07-1c-c6-75-a4-be NM_CONTROLLED=no ONBOOT=yes -STARTMODE=auto TYPE=Ethernet USERCTL=no """.lstrip() @@ -2890,7 +3016,6 @@ IPADDR=10.0.2.15 NETMASK=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes -STARTMODE=auto TYPE=Ethernet USERCTL=no """ @@ -2922,7 +3047,6 @@ MTU=1500 NETMASK=255.255.240.0 NM_CONTROLLED=no ONBOOT=yes -STARTMODE=auto TYPE=Ethernet USERCTL=no """ @@ -2937,7 +3061,6 @@ HWADDR=fa:16:3e:b1:ca:29 MTU=9000 NM_CONTROLLED=no ONBOOT=yes -STARTMODE=auto TYPE=Ethernet USERCTL=no """ @@ -2963,7 +3086,6 @@ BOOTPROTO=dhcp DEVICE=eth0 NM_CONTROLLED=no ONBOOT=yes -STARTMODE=auto TYPE=Ethernet USERCTL=no """ @@ -2972,10 +3094,9 @@ USERCTL=no self.assertEqual(resolvconf_content, found['/etc/resolv.conf']) def test_bond_config(self): - expected_name = 'expected_sysconfig_rhel' entry = NETWORK_CONFIGS['bond'] found = self._render_and_read(network_config=yaml.load(entry['yaml'])) - self._compare_files_to_expected(entry[expected_name], found) + self._compare_files_to_expected(entry[self.expected_name], found) self._assert_headers(found) def test_vlan_config(self): @@ -3163,14 +3284,12 @@ USERCTL=no GATEWAY=192.168.42.1 HWADDR=52:54:00:ab:cd:ef IPADDR=192.168.42.100 - IPADDR6=2001:db8::100/32 IPV6ADDR=2001:db8::100/32 IPV6INIT=yes IPV6_DEFAULTGW=2001:db8::1 NETMASK=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -3196,7 +3315,6 @@ USERCTL=no DEVICE=eno1 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -3209,7 +3327,6 @@ USERCTL=no NM_CONTROLLED=no ONBOOT=yes PHYSDEV=eno1 - STARTMODE=auto TYPE=Ethernet USERCTL=no VLAN=yes @@ -3240,7 +3357,6 @@ USERCTL=no NETMASK=255.255.255.192 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Bond USERCTL=no """), @@ -3252,7 +3368,6 @@ USERCTL=no NM_CONTROLLED=no ONBOOT=yes SLAVE=yes - STARTMODE=auto TYPE=Bond USERCTL=no """), @@ -3264,7 +3379,6 @@ USERCTL=no NM_CONTROLLED=no ONBOOT=yes SLAVE=yes - STARTMODE=auto TYPE=Bond USERCTL=no """) @@ -3288,7 +3402,6 @@ USERCTL=no METRIC=100 NM_CONTROLLED=no ONBOOT=yes - STARTMODE=auto TYPE=Ethernet USERCTL=no """), @@ -3311,7 +3424,7 @@ class TestOpenSuseSysConfigRendering(CiTestCase): header = ('# Created by cloud-init on instance boot automatically, ' 'do not edit.\n#\n') - expected_name = 'expected_sysconfig' + expected_name = 'expected_sysconfig_opensuse' def _get_renderer(self): distro_cls = distros.fetch('opensuse') @@ -3383,92 +3496,89 @@ class TestOpenSuseSysConfigRendering(CiTestCase): expected_content = """ # Created by cloud-init on instance boot automatically, do not edit. # -BOOTPROTO=dhcp -DEVICE=eth1000 -HWADDR=07-1c-c6-75-a4-be -NM_CONTROLLED=no -ONBOOT=yes +BOOTPROTO=dhcp4 +LLADDR=07-1c-c6-75-a4-be STARTMODE=auto -TYPE=Ethernet -USERCTL=no """.lstrip() self.assertEqual(expected_content, content) - def test_multiple_ipv4_default_gateways(self): - """ValueError is raised when duplicate ipv4 gateways exist.""" - net_json = { - "services": [{"type": "dns", "address": "172.19.0.12"}], - "networks": [{ - "network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", - "type": "ipv4", "netmask": "255.255.252.0", - "link": "tap1a81968a-79", - "routes": [{ - "netmask": "0.0.0.0", - "network": "0.0.0.0", - "gateway": "172.19.3.254", - }, { - "netmask": "0.0.0.0", # A second default gateway - "network": "0.0.0.0", - "gateway": "172.20.3.254", - }], - "ip_address": "172.19.1.34", "id": "network0" - }], - "links": [ - { - "ethernet_mac_address": "fa:16:3e:ed:9a:59", - "mtu": None, "type": "bridge", "id": - "tap1a81968a-79", - "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" - }, - ], - } - macs = {'fa:16:3e:ed:9a:59': 'eth0'} - render_dir = self.tmp_dir() - network_cfg = openstack.convert_net_json(net_json, known_macs=macs) - ns = network_state.parse_net_config_data(network_cfg, - skip_broken=False) - renderer = self._get_renderer() - with self.assertRaises(ValueError): - renderer.render_network_state(ns, target=render_dir) - self.assertEqual([], os.listdir(render_dir)) - - def test_multiple_ipv6_default_gateways(self): - """ValueError is raised when duplicate ipv6 gateways exist.""" - net_json = { - "services": [{"type": "dns", "address": "172.19.0.12"}], - "networks": [{ - "network_id": "public-ipv6", - "type": "ipv6", "netmask": "", - "link": "tap1a81968a-79", - "routes": [{ - "gateway": "2001:DB8::1", - "netmask": "::", - "network": "::" - }, { - "gateway": "2001:DB9::1", - "netmask": "::", - "network": "::" - }], - "ip_address": "2001:DB8::10", "id": "network1" - }], - "links": [ - { - "ethernet_mac_address": "fa:16:3e:ed:9a:59", - "mtu": None, "type": "bridge", "id": - "tap1a81968a-79", - "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" - }, - ], - } - macs = {'fa:16:3e:ed:9a:59': 'eth0'} - render_dir = self.tmp_dir() - network_cfg = openstack.convert_net_json(net_json, known_macs=macs) - ns = network_state.parse_net_config_data(network_cfg, - skip_broken=False) - renderer = self._get_renderer() - with self.assertRaises(ValueError): - renderer.render_network_state(ns, target=render_dir) - self.assertEqual([], os.listdir(render_dir)) + # TODO(rjschwei): re-enable test once route writing is implemented + # for SUSE distros +# def test_multiple_ipv4_default_gateways(self): +# """ValueError is raised when duplicate ipv4 gateways exist.""" +# net_json = { +# "services": [{"type": "dns", "address": "172.19.0.12"}], +# "networks": [{ +# "network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", +# "type": "ipv4", "netmask": "255.255.252.0", +# "link": "tap1a81968a-79", +# "routes": [{ +# "netmask": "0.0.0.0", +# "network": "0.0.0.0", +# "gateway": "172.19.3.254", +# }, { +# "netmask": "0.0.0.0", # A second default gateway +# "network": "0.0.0.0", +# "gateway": "172.20.3.254", +# }], +# "ip_address": "172.19.1.34", "id": "network0" +# }], +# "links": [ +# { +# "ethernet_mac_address": "fa:16:3e:ed:9a:59", +# "mtu": None, "type": "bridge", "id": +# "tap1a81968a-79", +# "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" +# }, +# ], +# } +# macs = {'fa:16:3e:ed:9a:59': 'eth0'} +# render_dir = self.tmp_dir() +# network_cfg = openstack.convert_net_json(net_json, known_macs=macs) +# ns = network_state.parse_net_config_data(network_cfg, +# skip_broken=False) +# renderer = self._get_renderer() +# with self.assertRaises(ValueError): +# renderer.render_network_state(ns, target=render_dir) +# self.assertEqual([], os.listdir(render_dir)) +# +# def test_multiple_ipv6_default_gateways(self): +# """ValueError is raised when duplicate ipv6 gateways exist.""" +# net_json = { +# "services": [{"type": "dns", "address": "172.19.0.12"}], +# "networks": [{ +# "network_id": "public-ipv6", +# "type": "ipv6", "netmask": "", +# "link": "tap1a81968a-79", +# "routes": [{ +# "gateway": "2001:DB8::1", +# "netmask": "::", +# "network": "::" +# }, { +# "gateway": "2001:DB9::1", +# "netmask": "::", +# "network": "::" +# }], +# "ip_address": "2001:DB8::10", "id": "network1" +# }], +# "links": [ +# { +# "ethernet_mac_address": "fa:16:3e:ed:9a:59", +# "mtu": None, "type": "bridge", "id": +# "tap1a81968a-79", +# "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" +# }, +# ], +# } +# macs = {'fa:16:3e:ed:9a:59': 'eth0'} +# render_dir = self.tmp_dir() +# network_cfg = openstack.convert_net_json(net_json, known_macs=macs) +# ns = network_state.parse_net_config_data(network_cfg, +# skip_broken=False) +# renderer = self._get_renderer() +# with self.assertRaises(ValueError): +# renderer.render_network_state(ns, target=render_dir) +# self.assertEqual([], os.listdir(render_dir)) def test_openstack_rendering_samples(self): for os_sample in OS_SAMPLES: @@ -3501,18 +3611,11 @@ USERCTL=no expected = """\ # Created by cloud-init on instance boot automatically, do not edit. # -BOOTPROTO=none -DEFROUTE=yes -DEVICE=interface0 -GATEWAY=10.0.2.2 -HWADDR=52:54:00:12:34:00 +BOOTPROTO=static IPADDR=10.0.2.15 +LLADDR=52:54:00:12:34:00 NETMASK=255.255.255.0 -NM_CONTROLLED=no -ONBOOT=yes STARTMODE=auto -TYPE=Ethernet -USERCTL=no """ self.assertEqual(expected, found[nspath + 'ifcfg-interface0']) # The configuration has no nameserver information make sure we @@ -3537,12 +3640,7 @@ USERCTL=no # Created by cloud-init on instance boot automatically, do not edit. # BOOTPROTO=dhcp -DEVICE=eth0 -NM_CONTROLLED=no -ONBOOT=yes STARTMODE=auto -TYPE=Ethernet -USERCTL=no """ self.assertEqual(expected, found[nspath + 'ifcfg-eth0']) # a dhcp only config should not modify resolv.conf @@ -3613,6 +3711,30 @@ USERCTL=no self._compare_files_to_expected(entry[self.expected_name], found) self._assert_headers(found) + def test_simple_render_ipv6_slaac(self): + entry = NETWORK_CONFIGS['ipv6_slaac'] + found = self._render_and_read(network_config=yaml.load(entry['yaml'])) + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + + def test_dhcpv6_stateless_config(self): + entry = NETWORK_CONFIGS['dhcpv6_stateless'] + found = self._render_and_read(network_config=yaml.load(entry['yaml'])) + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + + def test_render_v4_and_v6(self): + entry = NETWORK_CONFIGS['v4_and_v6'] + found = self._render_and_read(network_config=yaml.load(entry['yaml'])) + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + + def test_render_v6_and_v4(self): + entry = NETWORK_CONFIGS['v6_and_v4'] + found = self._render_and_read(network_config=yaml.load(entry['yaml'])) + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + class TestEniNetRendering(CiTestCase): -- cgit v1.2.3