summaryrefslogtreecommitdiff
path: root/tests/unittests
diff options
context:
space:
mode:
authorRyan Harper <ryan.harper@canonical.com>2017-03-19 08:39:01 -0500
committerScott Moser <smoser@brickies.net>2017-03-20 15:59:03 -0400
commitef18b8ac4cf7e3dfd98830fbdb298380a192a0fc (patch)
tree19806d975057906806bd4e62b795e77a7a6af3c4 /tests/unittests
parent9040e78feb7c1bcf3a1dab0ee163efaa0d21612c (diff)
downloadvyos-cloud-init-ef18b8ac4cf7e3dfd98830fbdb298380a192a0fc.tar.gz
vyos-cloud-init-ef18b8ac4cf7e3dfd98830fbdb298380a192a0fc.zip
cloudinit.net: add network config v2 parsing and rendering
Network configuration version 2 format is implemented in a package called netplan (nplan)[1] which allows consolidated network config for multiple network controllers. - Add a new netplan renderer - Update default policy, placing eni and sysconfig first This requires explicit policy to enable netplan over eni on systems which have both (Yakkety, Zesty, UC16) - Allow any network state (parsed from any format cloud-init supports) to render to v2 if system supports netplan. - Move eni's _subnet_is_ipv6 to common code for use by other renderers - Make sysconfig renderer always emit /etc/syconfig/network configuration - Update cloud-init.service systemd unit to also wait on systemd-networkd-wait-online.service 1. https://lists.ubuntu.com/archives/ubuntu-devel/2016-July/039464.html
Diffstat (limited to 'tests/unittests')
-rw-r--r--tests/unittests/test_distros/test_netconfig.py351
-rw-r--r--tests/unittests/test_net.py301
2 files changed, 648 insertions, 4 deletions
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index bde3bb50..b89b74ff 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -17,6 +17,7 @@ from ..helpers import TestCase
from cloudinit import distros
from cloudinit.distros.parsers.sys_conf import SysConf
from cloudinit import helpers
+from cloudinit.net import eni
from cloudinit import settings
from cloudinit import util
@@ -28,10 +29,10 @@ 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
+ netmask 255.255.255.0
+ network 192.168.0.0
auto eth1
iface eth1 inet dhcp
@@ -67,6 +68,100 @@ iface eth1 inet6 static
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_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
+# /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
+ broadcast 192.168.1.0
+ gateway 192.168.1.254
+ netmask 255.255.255.0
+
+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': '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.
+# 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/255.255.255.0
+ gateway4: 192.168.1.254
+ eth1:
+ dhcp4: true
+"""
+
+V2_NET_CFG = {
+ 'ethernets': {
+ 'eth7': {
+ 'addresses': ['192.168.1.5/255.255.255.0'],
+ '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.
+# 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:
+ eth7:
+ addresses:
+ - 192.168.1.5/255.255.255.0
+ gateway4: 192.168.1.254
+ eth9:
+ dhcp4: true
+"""
+
class WriteBuffer(object):
def __init__(self):
@@ -83,12 +178,14 @@ class WriteBuffer(object):
class TestNetCfgDistro(TestCase):
- def _get_distro(self, dname):
+ 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, paths)
+ return cls(dname, cfg.get('system_info'), paths)
def test_simple_write_ub(self):
ub_distro = self._get_distro('ubuntu')
@@ -116,6 +213,107 @@ class TestNetCfgDistro(TestCase):
self.assertEqual(str(write_buf).strip(), BASE_NET_CFG.strip())
self.assertEqual(write_buf.mode, 0o644)
+ def test_apply_network_config_eni_ub(self):
+ ub_distro = self._get_distro('ubuntu')
+ with ExitStack() as mocks:
+ write_bufs = {}
+
+ def replace_write(filename, content, mode=0o644, omode="wb"):
+ buf = WriteBuffer()
+ buf.mode = mode
+ buf.omode = omode
+ buf.write(content)
+ write_bufs[filename] = buf
+
+ # eni availability checks
+ mocks.enter_context(
+ mock.patch.object(util, 'which', return_value=True))
+ mocks.enter_context(
+ mock.patch.object(eni, 'available', return_value=True))
+ mocks.enter_context(
+ mock.patch.object(util, 'ensure_dir'))
+ mocks.enter_context(
+ mock.patch.object(util, 'write_file', replace_write))
+ mocks.enter_context(
+ mock.patch.object(os.path, 'isfile', return_value=False))
+
+ ub_distro.apply_network_config(V1_NET_CFG, False)
+
+ self.assertEqual(len(write_bufs), 2)
+ eni_name = '/etc/network/interfaces.d/50-cloud-init.cfg'
+ self.assertIn(eni_name, write_bufs)
+ write_buf = write_bufs[eni_name]
+ self.assertEqual(str(write_buf).strip(), V1_NET_CFG_OUTPUT.strip())
+ self.assertEqual(write_buf.mode, 0o644)
+
+ def test_apply_network_config_v1_to_netplan_ub(self):
+ renderers = ['netplan']
+ ub_distro = self._get_distro('ubuntu', renderers=renderers)
+ with ExitStack() as mocks:
+ write_bufs = {}
+
+ def replace_write(filename, content, mode=0o644, omode="wb"):
+ buf = WriteBuffer()
+ buf.mode = mode
+ buf.omode = omode
+ buf.write(content)
+ write_bufs[filename] = buf
+
+ mocks.enter_context(
+ mock.patch.object(util, 'which', return_value=True))
+ mocks.enter_context(
+ mock.patch.object(util, 'write_file', replace_write))
+ mocks.enter_context(
+ mock.patch.object(util, 'ensure_dir'))
+ mocks.enter_context(
+ mock.patch.object(util, 'subp', return_value=(0, 0)))
+ mocks.enter_context(
+ mock.patch.object(os.path, 'isfile', return_value=False))
+
+ ub_distro.apply_network_config(V1_NET_CFG, False)
+
+ self.assertEqual(len(write_bufs), 1)
+ netplan_name = '/etc/netplan/50-cloud-init.yaml'
+ self.assertIn(netplan_name, write_bufs)
+ write_buf = write_bufs[netplan_name]
+ self.assertEqual(str(write_buf).strip(),
+ V1_TO_V2_NET_CFG_OUTPUT.strip())
+ self.assertEqual(write_buf.mode, 0o644)
+
+ def test_apply_network_config_v2_passthrough_ub(self):
+ renderers = ['netplan']
+ ub_distro = self._get_distro('ubuntu', renderers=renderers)
+ with ExitStack() as mocks:
+ write_bufs = {}
+
+ def replace_write(filename, content, mode=0o644, omode="wb"):
+ buf = WriteBuffer()
+ buf.mode = mode
+ buf.omode = omode
+ buf.write(content)
+ write_bufs[filename] = buf
+
+ mocks.enter_context(
+ mock.patch.object(util, 'which', return_value=True))
+ mocks.enter_context(
+ mock.patch.object(util, 'write_file', replace_write))
+ mocks.enter_context(
+ mock.patch.object(util, 'ensure_dir'))
+ mocks.enter_context(
+ mock.patch.object(util, 'subp', return_value=(0, 0)))
+ mocks.enter_context(
+ mock.patch.object(os.path, 'isfile', return_value=False))
+
+ ub_distro.apply_network_config(V2_NET_CFG, False)
+
+ self.assertEqual(len(write_bufs), 1)
+ netplan_name = '/etc/netplan/50-cloud-init.yaml'
+ self.assertIn(netplan_name, write_bufs)
+ write_buf = write_bufs[netplan_name]
+ self.assertEqual(str(write_buf).strip(),
+ V2_TO_V2_NET_CFG_OUTPUT.strip())
+ self.assertEqual(write_buf.mode, 0o644)
+
def assertCfgEquals(self, blob1, blob2):
b1 = dict(SysConf(blob1.strip().splitlines()))
b2 = dict(SysConf(blob2.strip().splitlines()))
@@ -195,6 +393,79 @@ NETWORKING=yes
self.assertCfgEquals(expected_buf, str(write_buf))
self.assertEqual(write_buf.mode, 0o644)
+ def test_apply_network_config_rh(self):
+ renderers = ['sysconfig']
+ rh_distro = self._get_distro('rhel', renderers=renderers)
+
+ write_bufs = {}
+
+ def replace_write(filename, content, mode=0o644, omode="wb"):
+ buf = WriteBuffer()
+ buf.mode = mode
+ buf.omode = omode
+ buf.write(content)
+ write_bufs[filename] = buf
+
+ with ExitStack() as mocks:
+ # sysconfig availability checks
+ mocks.enter_context(
+ mock.patch.object(util, 'which', return_value=True))
+ mocks.enter_context(
+ mock.patch.object(util, 'write_file', replace_write))
+ mocks.enter_context(
+ mock.patch.object(util, 'load_file', return_value=''))
+ mocks.enter_context(
+ mock.patch.object(os.path, 'isfile', return_value=True))
+
+ rh_distro.apply_network_config(V1_NET_CFG, False)
+
+ self.assertEqual(len(write_bufs), 5)
+
+ # eth0
+ self.assertIn('/etc/sysconfig/network-scripts/ifcfg-eth0',
+ write_bufs)
+ write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-eth0']
+ expected_buf = '''
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+BOOTPROTO=static
+DEVICE=eth0
+IPADDR=192.168.1.5
+NETMASK=255.255.255.0
+NM_CONTROLLED=no
+ONBOOT=yes
+TYPE=Ethernet
+USERCTL=no
+'''
+ self.assertCfgEquals(expected_buf, str(write_buf))
+ self.assertEqual(write_buf.mode, 0o644)
+
+ # eth1
+ self.assertIn('/etc/sysconfig/network-scripts/ifcfg-eth1',
+ write_bufs)
+ write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-eth1']
+ expected_buf = '''
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+BOOTPROTO=dhcp
+DEVICE=eth1
+NM_CONTROLLED=no
+ONBOOT=yes
+TYPE=Ethernet
+USERCTL=no
+'''
+ self.assertCfgEquals(expected_buf, str(write_buf))
+ self.assertEqual(write_buf.mode, 0o644)
+
+ self.assertIn('/etc/sysconfig/network', write_bufs)
+ write_buf = write_bufs['/etc/sysconfig/network']
+ expected_buf = '''
+# Created by cloud-init v. 0.7
+NETWORKING=yes
+'''
+ self.assertCfgEquals(expected_buf, str(write_buf))
+ self.assertEqual(write_buf.mode, 0o644)
+
def test_write_ipv6_rhel(self):
rh_distro = self._get_distro('rhel')
@@ -274,6 +545,78 @@ IPV6_AUTOCONF=no
self.assertCfgEquals(expected_buf, str(write_buf))
self.assertEqual(write_buf.mode, 0o644)
+ def test_apply_network_config_ipv6_rh(self):
+ renderers = ['sysconfig']
+ rh_distro = self._get_distro('rhel', renderers=renderers)
+
+ write_bufs = {}
+
+ def replace_write(filename, content, mode=0o644, omode="wb"):
+ buf = WriteBuffer()
+ buf.mode = mode
+ buf.omode = omode
+ buf.write(content)
+ write_bufs[filename] = buf
+
+ with ExitStack() as mocks:
+ mocks.enter_context(
+ mock.patch.object(util, 'which', return_value=True))
+ mocks.enter_context(
+ mock.patch.object(util, 'write_file', replace_write))
+ mocks.enter_context(
+ mock.patch.object(util, 'load_file', return_value=''))
+ mocks.enter_context(
+ mock.patch.object(os.path, 'isfile', return_value=True))
+
+ rh_distro.apply_network_config(V1_NET_CFG_IPV6, False)
+
+ self.assertEqual(len(write_bufs), 5)
+
+ self.assertIn('/etc/sysconfig/network-scripts/ifcfg-eth0',
+ write_bufs)
+ write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-eth0']
+ expected_buf = '''
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+BOOTPROTO=static
+DEVICE=eth0
+IPV6ADDR=2607:f0d0:1002:0011::2
+IPV6INIT=yes
+NETMASK=64
+NM_CONTROLLED=no
+ONBOOT=yes
+TYPE=Ethernet
+USERCTL=no
+'''
+ self.assertCfgEquals(expected_buf, str(write_buf))
+ self.assertEqual(write_buf.mode, 0o644)
+ self.assertIn('/etc/sysconfig/network-scripts/ifcfg-eth1',
+ write_bufs)
+ write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-eth1']
+ expected_buf = '''
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+BOOTPROTO=dhcp
+DEVICE=eth1
+NM_CONTROLLED=no
+ONBOOT=yes
+TYPE=Ethernet
+USERCTL=no
+'''
+ self.assertCfgEquals(expected_buf, str(write_buf))
+ self.assertEqual(write_buf.mode, 0o644)
+
+ self.assertIn('/etc/sysconfig/network', write_bufs)
+ write_buf = write_bufs['/etc/sysconfig/network']
+ expected_buf = '''
+# Created by cloud-init v. 0.7
+NETWORKING=yes
+NETWORKING_IPV6=yes
+IPV6_AUTOCONF=no
+'''
+ self.assertCfgEquals(expected_buf, str(write_buf))
+ self.assertEqual(write_buf.mode, 0o644)
+
def test_simple_write_freebsd(self):
fbsd_distro = self._get_distro('freebsd')
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 902204a0..4f07d804 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -3,6 +3,7 @@
from cloudinit import net
from cloudinit.net import cmdline
from cloudinit.net import eni
+from cloudinit.net import netplan
from cloudinit.net import network_state
from cloudinit.net import renderers
from cloudinit.net import sysconfig
@@ -408,6 +409,41 @@ NETWORK_CONFIGS = {
post-up route add default gw 65.61.151.37 || true
pre-down route del default gw 65.61.151.37 || true
""").rstrip(' '),
+ 'expected_netplan': textwrap.dedent("""
+ network:
+ version: 2
+ ethernets:
+ eth1:
+ match:
+ macaddress: cf:d6:af:48:e8:80
+ nameservers:
+ addresses:
+ - 1.2.3.4
+ - 5.6.7.8
+ search:
+ - wark.maas
+ set-name: eth1
+ eth99:
+ addresses:
+ - 192.168.21.3/24
+ dhcp4: true
+ match:
+ macaddress: c0:d6:9f:2c:e8:80
+ nameservers:
+ addresses:
+ - 8.8.8.8
+ - 8.8.4.4
+ - 1.2.3.4
+ - 5.6.7.8
+ search:
+ - barley.maas
+ - sach.maas
+ - wark.maas
+ routes:
+ - to: 0.0.0.0/0.0.0.0
+ via: 65.61.151.37
+ set-name: eth99
+ """).rstrip(' '),
'yaml': textwrap.dedent("""
version: 1
config:
@@ -450,6 +486,14 @@ NETWORK_CONFIGS = {
# control-alias iface0
iface iface0 inet6 dhcp
""").rstrip(' '),
+ 'expected_netplan': textwrap.dedent("""
+ network:
+ version: 2
+ ethernets:
+ iface0:
+ dhcp4: true
+ dhcp6: true
+ """).rstrip(' '),
'yaml': textwrap.dedent("""\
version: 1
config:
@@ -524,6 +568,126 @@ iface eth0.101 inet static
post-up route add -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
"""),
+ 'expected_netplan': textwrap.dedent("""
+ network:
+ version: 2
+ ethernets:
+ eth0:
+ match:
+ macaddress: c0:d6:9f:2c:e8:80
+ nameservers:
+ addresses:
+ - 8.8.8.8
+ - 4.4.4.4
+ - 8.8.4.4
+ search:
+ - barley.maas
+ - wark.maas
+ - foobar.maas
+ set-name: eth0
+ eth1:
+ match:
+ macaddress: aa:d6:9f:2c:e8:80
+ nameservers:
+ addresses:
+ - 8.8.8.8
+ - 4.4.4.4
+ - 8.8.4.4
+ search:
+ - barley.maas
+ - wark.maas
+ - foobar.maas
+ set-name: eth1
+ eth2:
+ match:
+ macaddress: c0:bb:9f:2c:e8:80
+ nameservers:
+ addresses:
+ - 8.8.8.8
+ - 4.4.4.4
+ - 8.8.4.4
+ search:
+ - barley.maas
+ - wark.maas
+ - foobar.maas
+ set-name: eth2
+ eth3:
+ match:
+ macaddress: 66:bb:9f:2c:e8:80
+ nameservers:
+ addresses:
+ - 8.8.8.8
+ - 4.4.4.4
+ - 8.8.4.4
+ search:
+ - barley.maas
+ - wark.maas
+ - foobar.maas
+ set-name: eth3
+ eth4:
+ match:
+ macaddress: 98:bb:9f:2c:e8:80
+ nameservers:
+ addresses:
+ - 8.8.8.8
+ - 4.4.4.4
+ - 8.8.4.4
+ search:
+ - barley.maas
+ - wark.maas
+ - foobar.maas
+ set-name: eth4
+ eth5:
+ dhcp4: true
+ match:
+ macaddress: 98:bb:9f:2c:e8:8a
+ nameservers:
+ addresses:
+ - 8.8.8.8
+ - 4.4.4.4
+ - 8.8.4.4
+ search:
+ - barley.maas
+ - wark.maas
+ - foobar.maas
+ set-name: eth5
+ bonds:
+ bond0:
+ dhcp6: true
+ interfaces:
+ - eth1
+ - eth2
+ parameters:
+ mode: active-backup
+ bridges:
+ br0:
+ addresses:
+ - 192.168.14.2/24
+ - 2001:1::1/64
+ interfaces:
+ - eth3
+ - eth4
+ vlans:
+ bond0.200:
+ dhcp4: true
+ id: 200
+ link: bond0
+ eth0.101:
+ addresses:
+ - 192.168.0.2/24
+ - 192.168.2.10/24
+ gateway4: 192.168.0.1
+ id: 101
+ link: eth0
+ nameservers:
+ addresses:
+ - 192.168.0.10
+ - 10.23.23.134
+ search:
+ - barley.maas
+ - sacchromyces.maas
+ - brettanomyces.maas
+ """).rstrip(' '),
'yaml': textwrap.dedent("""
version: 1
config:
@@ -808,6 +972,99 @@ iface eth0 inet dhcp
expected, dir2dict(tmp_dir)['/etc/network/interfaces'])
+class TestNetplanNetRendering(CiTestCase):
+
+ @mock.patch("cloudinit.net.sys_dev_path")
+ @mock.patch("cloudinit.net.read_sys_net")
+ @mock.patch("cloudinit.net.get_devicelist")
+ def test_default_generation(self, mock_get_devicelist,
+ mock_read_sys_net,
+ mock_sys_dev_path):
+ tmp_dir = self.tmp_dir()
+ _setup_test(tmp_dir, mock_get_devicelist,
+ mock_read_sys_net, mock_sys_dev_path)
+
+ network_cfg = net.generate_fallback_config()
+ ns = network_state.parse_net_config_data(network_cfg,
+ skip_broken=False)
+
+ render_dir = os.path.join(tmp_dir, "render")
+ os.makedirs(render_dir)
+
+ render_target = 'netplan.yaml'
+ renderer = netplan.Renderer(
+ {'netplan_path': render_target, 'postcmds': False})
+ renderer.render_network_state(render_dir, ns)
+
+ self.assertTrue(os.path.exists(os.path.join(render_dir,
+ render_target)))
+ with open(os.path.join(render_dir, render_target)) as fh:
+ contents = fh.read()
+ print(contents)
+
+ expected = """
+network:
+ version: 2
+ ethernets:
+ eth1000:
+ dhcp4: true
+ match:
+ macaddress: 07-1c-c6-75-a4-be
+ set-name: eth1000
+"""
+ self.assertEqual(expected.lstrip(), contents.lstrip())
+
+
+class TestNetplanPostcommands(CiTestCase):
+ mycfg = {
+ 'config': [{"type": "physical", "name": "eth0",
+ "mac_address": "c0:d6:9f:2c:e8:80",
+ "subnets": [{"type": "dhcp"}]}],
+ 'version': 1}
+
+ @mock.patch.object(netplan.Renderer, '_netplan_generate')
+ @mock.patch.object(netplan.Renderer, '_net_setup_link')
+ def test_netplan_render_calls_postcmds(self, mock_netplan_generate,
+ mock_net_setup_link):
+ tmp_dir = self.tmp_dir()
+ ns = network_state.parse_net_config_data(self.mycfg,
+ skip_broken=False)
+
+ render_dir = os.path.join(tmp_dir, "render")
+ os.makedirs(render_dir)
+
+ render_target = 'netplan.yaml'
+ renderer = netplan.Renderer(
+ {'netplan_path': render_target, 'postcmds': True})
+ renderer.render_network_state(render_dir, ns)
+
+ mock_netplan_generate.assert_called_with(run=True)
+ mock_net_setup_link.assert_called_with(run=True)
+
+ @mock.patch.object(netplan, "get_devicelist")
+ @mock.patch('cloudinit.util.subp')
+ def test_netplan_postcmds(self, mock_subp, mock_devlist):
+ mock_devlist.side_effect = [['lo']]
+ tmp_dir = self.tmp_dir()
+ ns = network_state.parse_net_config_data(self.mycfg,
+ skip_broken=False)
+
+ render_dir = os.path.join(tmp_dir, "render")
+ os.makedirs(render_dir)
+
+ render_target = 'netplan.yaml'
+ renderer = netplan.Renderer(
+ {'netplan_path': render_target, 'postcmds': True})
+ renderer.render_network_state(render_dir, ns)
+
+ expected = [
+ mock.call(['netplan', 'generate'], capture=True),
+ mock.call(['udevadm', 'test-builtin', 'net_setup_link',
+ '/sys/class/net/lo'], capture=True),
+ ]
+ mock_subp.assert_has_calls(expected)
+
+
class TestEniNetworkStateToEni(CiTestCase):
mycfg = {
'config': [{"type": "physical", "name": "eth0",
@@ -953,6 +1210,50 @@ class TestCmdlineReadKernelConfig(CiTestCase):
self.assertEqual(found['config'], expected)
+class TestNetplanRoundTrip(CiTestCase):
+ def _render_and_read(self, network_config=None, state=None,
+ netplan_path=None, dir=None):
+ if dir is None:
+ dir = self.tmp_dir()
+
+ if network_config:
+ ns = network_state.parse_net_config_data(network_config)
+ elif state:
+ ns = state
+ else:
+ raise ValueError("Expected data or state, got neither")
+
+ if netplan_path is None:
+ netplan_path = 'etc/netplan/50-cloud-init.yaml'
+
+ renderer = netplan.Renderer(
+ config={'netplan_path': netplan_path})
+
+ renderer.render_network_state(dir, ns)
+ return dir2dict(dir)
+
+ def testsimple_render_small_netplan(self):
+ entry = NETWORK_CONFIGS['small']
+ files = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self.assertEqual(
+ entry['expected_netplan'].splitlines(),
+ files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+
+ def testsimple_render_v4_and_v6(self):
+ entry = NETWORK_CONFIGS['v4_and_v6']
+ files = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self.assertEqual(
+ entry['expected_netplan'].splitlines(),
+ files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+
+ def testsimple_render_all(self):
+ entry = NETWORK_CONFIGS['all']
+ files = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self.assertEqual(
+ entry['expected_netplan'].splitlines(),
+ files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+
+
class TestEniRoundTrip(CiTestCase):
def _render_and_read(self, network_config=None, state=None, eni_path=None,
links_prefix=None, netrules_path=None, dir=None):