summaryrefslogtreecommitdiff
path: root/tests/unittests/distros/test_netconfig.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unittests/distros/test_netconfig.py')
-rw-r--r--tests/unittests/distros/test_netconfig.py1013
1 files changed, 1013 insertions, 0 deletions
diff --git a/tests/unittests/distros/test_netconfig.py b/tests/unittests/distros/test_netconfig.py
new file mode 100644
index 00000000..a25be481
--- /dev/null
+++ b/tests/unittests/distros/test_netconfig.py
@@ -0,0 +1,1013 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+import copy
+import os
+import re
+from io import StringIO
+from textwrap import dedent
+from unittest import mock
+
+from cloudinit import distros, helpers, safeyaml, settings, subp, util
+from cloudinit.distros.parsers.sys_conf import SysConf
+from tests.unittests.helpers import FilesystemMockingTestCase, dir2dict
+
+BASE_NET_CFG = """
+auto lo
+iface lo inet loopback
+
+auto eth0
+iface eth0 inet static
+ address 192.168.1.5
+ broadcast 192.168.1.0
+ gateway 192.168.1.254
+ netmask 255.255.255.0
+ network 192.168.0.0
+
+auto eth1
+iface eth1 inet dhcp
+"""
+
+BASE_NET_CFG_FROM_V2 = """
+auto lo
+iface lo inet loopback
+
+auto eth0
+iface eth0 inet static
+ address 192.168.1.5/24
+ gateway 192.168.1.254
+
+auto eth1
+iface eth1 inet dhcp
+"""
+
+BASE_NET_CFG_IPV6 = """
+auto lo
+iface lo inet loopback
+
+auto eth0
+iface eth0 inet static
+ address 192.168.1.5
+ netmask 255.255.255.0
+ network 192.168.0.0
+ broadcast 192.168.1.0
+ gateway 192.168.1.254
+
+iface eth0 inet6 static
+ address 2607:f0d0:1002:0011::2
+ netmask 64
+ gateway 2607:f0d0:1002:0011::1
+
+iface eth1 inet static
+ address 192.168.1.6
+ netmask 255.255.255.0
+ network 192.168.0.0
+ broadcast 192.168.1.0
+ gateway 192.168.1.254
+
+iface eth1 inet6 static
+ address 2607:f0d0:1002:0011::3
+ netmask 64
+ gateway 2607:f0d0:1002:0011::1
+"""
+
+V1_NET_CFG = {
+ "config": [
+ {
+ "name": "eth0",
+ "subnets": [
+ {
+ "address": "192.168.1.5",
+ "broadcast": "192.168.1.0",
+ "gateway": "192.168.1.254",
+ "netmask": "255.255.255.0",
+ "type": "static",
+ }
+ ],
+ "type": "physical",
+ },
+ {
+ "name": "eth1",
+ "subnets": [{"control": "auto", "type": "dhcp4"}],
+ "type": "physical",
+ },
+ ],
+ "version": 1,
+}
+
+V1_NET_CFG_WITH_DUPS = """\
+# same value in interface specific dns and global dns
+# should produce single entry in network file
+version: 1
+config:
+ - type: physical
+ name: eth0
+ subnets:
+ - type: static
+ address: 192.168.0.102/24
+ dns_nameservers: [1.2.3.4]
+ dns_search: [test.com]
+ interface: eth0
+ - type: nameserver
+ address: [1.2.3.4]
+ search: [test.com]
+"""
+
+V1_NET_CFG_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 inet static
+ address 192.168.1.5/24
+ broadcast 192.168.1.0
+ gateway 192.168.1.254
+
+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": "static6",
+ }
+ ],
+ "type": "physical",
+ },
+ {
+ "name": "eth1",
+ "subnets": [{"control": "auto", "type": "dhcp4"}],
+ "type": "physical",
+ },
+ ],
+ "version": 1,
+}
+
+
+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 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:
+ - 192.168.1.5/24
+ gateway4: 192.168.1.254
+ eth1:
+ 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": {"addresses": ["192.168.1.5/24"], "gateway4": "192.168.1.254"},
+ "eth9": {"dhcp4": True},
+ },
+ "version": 2,
+}
+
+
+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 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:
+ ethernets:
+ eth7:
+ addresses:
+ - 192.168.1.5/24
+ gateway4: 192.168.1.254
+ eth9:
+ dhcp4: true
+ version: 2
+"""
+
+
+class WriteBuffer(object):
+ def __init__(self):
+ self.buffer = StringIO()
+ self.mode = None
+ self.omode = None
+
+ def write(self, text):
+ self.buffer.write(text)
+
+ def __str__(self):
+ return self.buffer.getvalue()
+
+
+class TestNetCfgDistroBase(FilesystemMockingTestCase):
+ def setUp(self):
+ super(TestNetCfgDistroBase, self).setUp()
+ self.add_patch("cloudinit.util.system_is_snappy", "m_snappy")
+
+ def _get_distro(self, dname, renderers=None):
+ cls = distros.fetch(dname)
+ cfg = settings.CFG_BUILTIN
+ cfg["system_info"]["distro"] = dname
+ if renderers:
+ cfg["system_info"]["network"] = {"renderers": renderers}
+ paths = helpers.Paths({})
+ return cls(dname, cfg.get("system_info"), paths)
+
+ def assertCfgEquals(self, blob1, blob2):
+ b1 = dict(SysConf(blob1.strip().splitlines()))
+ b2 = dict(SysConf(blob2.strip().splitlines()))
+ self.assertEqual(b1, b2)
+ for (k, v) in b1.items():
+ self.assertIn(k, b2)
+ for (k, v) in b2.items():
+ self.assertIn(k, b1)
+ for (k, v) in b1.items():
+ self.assertEqual(v, b2[k])
+
+
+class TestNetCfgDistroFreeBSD(TestNetCfgDistroBase):
+ 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
+"""
+
+ 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(),
+ )
+
+ @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
+"""
+
+ 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(),
+ )
+
+ @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",
+ }
+
+ 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):
+ def setUp(self):
+ super(TestNetCfgDistroUbuntuEni, self).setUp()
+ self.distro = self._get_distro("ubuntu", renderers=["eni"])
+
+ def eni_path(self):
+ return "/etc/network/interfaces.d/50-cloud-init.cfg"
+
+ def _apply_and_verify_eni(
+ 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.eni.available") as m_avail:
+ m_avail.return_value = True
+ 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 test_apply_network_config_eni_ub(self):
+ expected_cfgs = {
+ self.eni_path(): V1_NET_CFG_OUTPUT,
+ }
+ # ub_distro.apply_network_config(V1_NET_CFG, False)
+ self._apply_and_verify_eni(
+ self.distro.apply_network_config,
+ 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):
+ super(TestNetCfgDistroUbuntuNetplan, self).setUp()
+ self.distro = self._get_distro("ubuntu", renderers=["netplan"])
+ self.devlist = ["eth0", "lo"]
+
+ def _apply_and_verify_netplan(
+ 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.netplan.available", return_value=True):
+ with mock.patch(
+ "cloudinit.net.netplan.get_devicelist",
+ return_value=self.devlist,
+ ):
+ 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 netplan_path(self):
+ return "/etc/netplan/50-cloud-init.yaml"
+
+ def test_apply_network_config_v1_to_netplan_ub(self):
+ expected_cfgs = {
+ self.netplan_path(): V1_TO_V2_NET_CFG_OUTPUT,
+ }
+
+ # ub_distro.apply_network_config(V1_NET_CFG, False)
+ self._apply_and_verify_netplan(
+ self.distro.apply_network_config,
+ 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,
+ }
+ # ub_distro.apply_network_config(V2_NET_CFG, False)
+ self._apply_and_verify_netplan(
+ self.distro.apply_network_config,
+ V2_NET_CFG,
+ expected_cfgs=expected_cfgs.copy(),
+ )
+
+
+class TestNetCfgDistroRedhat(TestNetCfgDistroBase):
+ def setUp(self):
+ super(TestNetCfgDistroRedhat, self).setUp()
+ self.distro = self._get_distro("rhel", renderers=["sysconfig"])
+
+ def ifcfg_path(self, ifname):
+ return "/etc/sysconfig/network-scripts/ifcfg-%s" % ifname
+
+ def control_path(self):
+ return "/etc/sysconfig/network"
+
+ def _apply_and_verify(
+ 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.sysconfig.available") as m_avail:
+ m_avail.return_value = True
+ with self.reRooted(tmpd) as tmpd:
+ apply_fn(config, bringup)
+
+ results = dir2dict(tmpd)
+ for cfgpath, expected in expected_cfgs.items():
+ self.assertCfgEquals(expected, results[cfgpath])
+ self.assertEqual(0o644, get_mode(cfgpath, tmpd))
+
+ def test_apply_network_config_rh(self):
+ expected_cfgs = {
+ self.ifcfg_path("eth0"): dedent(
+ """\
+ BOOTPROTO=none
+ DEFROUTE=yes
+ DEVICE=eth0
+ GATEWAY=192.168.1.254
+ IPADDR=192.168.1.5
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """
+ ),
+ self.ifcfg_path("eth1"): dedent(
+ """\
+ BOOTPROTO=dhcp
+ DEVICE=eth1
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """
+ ),
+ self.control_path(): dedent(
+ """\
+ NETWORKING=yes
+ """
+ ),
+ }
+ # rh_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(),
+ )
+
+ def test_apply_network_config_ipv6_rh(self):
+ expected_cfgs = {
+ self.ifcfg_path("eth0"): dedent(
+ """\
+ BOOTPROTO=none
+ DEFROUTE=yes
+ DEVICE=eth0
+ IPV6ADDR=2607:f0d0:1002:0011::2/64
+ IPV6INIT=yes
+ IPV6_AUTOCONF=no
+ IPV6_DEFAULTGW=2607:f0d0:1002:0011::1
+ IPV6_FORCE_ACCEPT_RA=no
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """
+ ),
+ self.ifcfg_path("eth1"): dedent(
+ """\
+ BOOTPROTO=dhcp
+ DEVICE=eth1
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """
+ ),
+ self.control_path(): dedent(
+ """\
+ NETWORKING=yes
+ NETWORKING_IPV6=yes
+ IPV6_AUTOCONF=no
+ """
+ ),
+ }
+ # rh_distro.apply_network_config(V1_NET_CFG_IPV6, False)
+ self._apply_and_verify(
+ self.distro.apply_network_config,
+ V1_NET_CFG_IPV6,
+ expected_cfgs=expected_cfgs.copy(),
+ )
+
+ def test_vlan_render_unsupported(self):
+ """Render officially unsupported vlan names."""
+ cfg = {
+ "version": 2,
+ "ethernets": {
+ "eth0": {
+ "addresses": ["192.10.1.2/24"],
+ "match": {"macaddress": "00:16:3e:60:7c:df"},
+ }
+ },
+ "vlans": {
+ "infra0": {
+ "addresses": ["10.0.1.2/16"],
+ "id": 1001,
+ "link": "eth0",
+ }
+ },
+ }
+ expected_cfgs = {
+ self.ifcfg_path("eth0"): dedent(
+ """\
+ BOOTPROTO=none
+ DEVICE=eth0
+ HWADDR=00:16:3e:60:7c:df
+ IPADDR=192.10.1.2
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """
+ ),
+ self.ifcfg_path("infra0"): dedent(
+ """\
+ BOOTPROTO=none
+ DEVICE=infra0
+ IPADDR=10.0.1.2
+ NETMASK=255.255.0.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PHYSDEV=eth0
+ USERCTL=no
+ VLAN=yes
+ """
+ ),
+ self.control_path(): dedent(
+ """\
+ NETWORKING=yes
+ """
+ ),
+ }
+ self._apply_and_verify(
+ self.distro.apply_network_config, cfg, expected_cfgs=expected_cfgs
+ )
+
+ def test_vlan_render(self):
+ cfg = {
+ "version": 2,
+ "ethernets": {"eth0": {"addresses": ["192.10.1.2/24"]}},
+ "vlans": {
+ "eth0.1001": {
+ "addresses": ["10.0.1.2/16"],
+ "id": 1001,
+ "link": "eth0",
+ }
+ },
+ }
+ expected_cfgs = {
+ self.ifcfg_path("eth0"): dedent(
+ """\
+ BOOTPROTO=none
+ DEVICE=eth0
+ IPADDR=192.10.1.2
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """
+ ),
+ self.ifcfg_path("eth0.1001"): dedent(
+ """\
+ BOOTPROTO=none
+ DEVICE=eth0.1001
+ IPADDR=10.0.1.2
+ NETMASK=255.255.0.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PHYSDEV=eth0
+ USERCTL=no
+ VLAN=yes
+ """
+ ),
+ self.control_path(): dedent(
+ """\
+ NETWORKING=yes
+ """
+ ),
+ }
+ self._apply_and_verify(
+ self.distro.apply_network_config, cfg, expected_cfgs=expected_cfgs
+ )
+
+
+class TestNetCfgDistroOpensuse(TestNetCfgDistroBase):
+ def setUp(self):
+ super(TestNetCfgDistroOpensuse, self).setUp()
+ self.distro = self._get_distro("opensuse", renderers=["sysconfig"])
+
+ def ifcfg_path(self, ifname):
+ return "/etc/sysconfig/network/ifcfg-%s" % ifname
+
+ def _apply_and_verify(
+ 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.sysconfig.available") as m_avail:
+ m_avail.return_value = True
+ with self.reRooted(tmpd) as tmpd:
+ apply_fn(config, bringup)
+
+ results = dir2dict(tmpd)
+ for cfgpath, expected in expected_cfgs.items():
+ self.assertCfgEquals(expected, results[cfgpath])
+ self.assertEqual(0o644, get_mode(cfgpath, tmpd))
+
+ def test_apply_network_config_opensuse(self):
+ """Opensuse uses apply_network_config and renders sysconfig"""
+ expected_cfgs = {
+ self.ifcfg_path("eth0"): dedent(
+ """\
+ BOOTPROTO=static
+ IPADDR=192.168.1.5
+ NETMASK=255.255.255.0
+ STARTMODE=auto
+ """
+ ),
+ self.ifcfg_path("eth1"): dedent(
+ """\
+ BOOTPROTO=dhcp4
+ STARTMODE=auto
+ """
+ ),
+ }
+ self._apply_and_verify(
+ self.distro.apply_network_config,
+ V1_NET_CFG,
+ expected_cfgs=expected_cfgs.copy(),
+ )
+
+ def test_apply_network_config_ipv6_opensuse(self):
+ """Opensuse uses apply_network_config and renders sysconfig w/ipv6"""
+ expected_cfgs = {
+ self.ifcfg_path("eth0"): dedent(
+ """\
+ BOOTPROTO=static
+ IPADDR6=2607:f0d0:1002:0011::2/64
+ STARTMODE=auto
+ """
+ ),
+ self.ifcfg_path("eth1"): dedent(
+ """\
+ BOOTPROTO=dhcp4
+ STARTMODE=auto
+ """
+ ),
+ }
+ self._apply_and_verify(
+ self.distro.apply_network_config,
+ V1_NET_CFG_IPV6,
+ 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
+ """
+ ),
+ }
+
+ with mock.patch(
+ "cloudinit.net.netplan.get_devicelist", return_value=[]
+ ):
+ self._apply_and_verify(
+ self.distro.apply_network_config,
+ V1_NET_CFG,
+ expected_cfgs=expected_cfgs.copy(),
+ with_netplan=True,
+ )
+
+
+class TestNetCfgDistroPhoton(TestNetCfgDistroBase):
+ def setUp(self):
+ super(TestNetCfgDistroPhoton, self).setUp()
+ self.distro = self._get_distro("photon", renderers=["networkd"])
+
+ def create_conf_dict(self, contents):
+ content_dict = {}
+ for line in contents:
+ if line:
+ line = line.strip()
+ if line and re.search(r"^\[(.+)\]$", line):
+ content_dict[line] = []
+ key = line
+ elif line:
+ assert key
+ content_dict[key].append(line)
+
+ return content_dict
+
+ def compare_dicts(self, actual, expected):
+ for k, v in actual.items():
+ self.assertEqual(sorted(expected[k]), sorted(v))
+
+ def _apply_and_verify(
+ 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.networkd.available") as m_avail:
+ m_avail.return_value = True
+ with self.reRooted(tmpd) as tmpd:
+ apply_fn(config, bringup)
+
+ results = dir2dict(tmpd)
+ for cfgpath, expected in expected_cfgs.items():
+ actual = self.create_conf_dict(results[cfgpath].splitlines())
+ self.compare_dicts(actual, expected)
+ self.assertEqual(0o644, get_mode(cfgpath, tmpd))
+
+ def nwk_file_path(self, ifname):
+ return "/etc/systemd/network/10-cloud-init-%s.network" % ifname
+
+ def net_cfg_1(self, ifname):
+ ret = (
+ """\
+ [Match]
+ Name=%s
+ [Network]
+ DHCP=no
+ [Address]
+ Address=192.168.1.5/24
+ [Route]
+ Gateway=192.168.1.254"""
+ % ifname
+ )
+ return ret
+
+ def net_cfg_2(self, ifname):
+ ret = (
+ """\
+ [Match]
+ Name=%s
+ [Network]
+ DHCP=ipv4"""
+ % ifname
+ )
+ return ret
+
+ def test_photon_network_config_v1(self):
+ tmp = self.net_cfg_1("eth0").splitlines()
+ expected_eth0 = self.create_conf_dict(tmp)
+
+ tmp = self.net_cfg_2("eth1").splitlines()
+ expected_eth1 = self.create_conf_dict(tmp)
+
+ expected_cfgs = {
+ self.nwk_file_path("eth0"): expected_eth0,
+ self.nwk_file_path("eth1"): expected_eth1,
+ }
+
+ self._apply_and_verify(
+ self.distro.apply_network_config, V1_NET_CFG, expected_cfgs.copy()
+ )
+
+ def test_photon_network_config_v2(self):
+ tmp = self.net_cfg_1("eth7").splitlines()
+ expected_eth7 = self.create_conf_dict(tmp)
+
+ tmp = self.net_cfg_2("eth9").splitlines()
+ expected_eth9 = self.create_conf_dict(tmp)
+
+ expected_cfgs = {
+ self.nwk_file_path("eth7"): expected_eth7,
+ self.nwk_file_path("eth9"): expected_eth9,
+ }
+
+ self._apply_and_verify(
+ self.distro.apply_network_config, V2_NET_CFG, expected_cfgs.copy()
+ )
+
+ def test_photon_network_config_v1_with_duplicates(self):
+ expected = """\
+ [Match]
+ Name=eth0
+ [Network]
+ DHCP=no
+ DNS=1.2.3.4
+ Domains=test.com
+ [Address]
+ Address=192.168.0.102/24"""
+
+ net_cfg = safeyaml.load(V1_NET_CFG_WITH_DUPS)
+
+ expected = self.create_conf_dict(expected.splitlines())
+ expected_cfgs = {
+ self.nwk_file_path("eth0"): expected,
+ }
+
+ self._apply_and_verify(
+ self.distro.apply_network_config, net_cfg, expected_cfgs.copy()
+ )
+
+
+def get_mode(path, target=None):
+ return os.stat(subp.target_path(target, path)).st_mode & 0o777
+
+
+# vi: ts=4 expandtab