From b7b7331b9c308d8e1cb0b3dfd4398e6e7cb1b60f Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Fri, 16 Mar 2018 14:00:15 -0400 Subject: netplan: render bridge port-priority values Update netplan renderer to write out bridge port-priority values now that netplan supports the feature. LP: #1735821 --- tests/unittests/test_net.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'tests/unittests/test_net.py') diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index ac33e8ef..9cf11f2e 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -758,6 +758,9 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true path-cost: eth3: 50 eth4: 75 + port-priority: + eth3: 28 + eth4: 14 priority: 22 stp: false routes: -- cgit v1.2.3 From de34dc7c467b318b2d04d065f8d752c7a530e155 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 16 Mar 2018 15:43:27 -0600 Subject: net: recognize iscsi root cases without ip= on kernel command line. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When 'ip=' or 'ip6=' is found on the kernel command line, cloud-init will consider read network config from /run/net-*.conf files. There are some iscsi-root scenarios where initramfs configures networking but the ip= parameter is not present. 2 such cases are:  a.) static config in /etc/iscsi/iscsi.initramfs (copied into the initramfs)  b.) iBft This changes cloud-init to consider initramfs provided networking information if:  * there are /run/net-* files and  * (ip= or ip6 is on the command line) or open-iscsi.interface file exists. LP: #1752391 --- cloudinit/net/cmdline.py | 24 ++++++++++++++++-- cloudinit/tests/helpers.py | 9 +++++-- tests/unittests/test_net.py | 61 ++++++++++++++++++++++++++++++++------------- 3 files changed, 72 insertions(+), 22 deletions(-) (limited to 'tests/unittests/test_net.py') diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py index 7b2cc9db..9e9fe0fe 100755 --- a/cloudinit/net/cmdline.py +++ b/cloudinit/net/cmdline.py @@ -9,12 +9,15 @@ import base64 import glob import gzip import io +import os from . import get_devicelist from . import read_sys_net_safe from cloudinit import util +_OPEN_ISCSI_INTERFACE_FILE = "/run/initramfs/open-iscsi.interface" + def _klibc_to_config_entry(content, mac_addrs=None): """Convert a klibc written shell content file to a 'config' entry @@ -103,9 +106,13 @@ def _klibc_to_config_entry(content, mac_addrs=None): return name, iface +def _get_klibc_net_cfg_files(): + return glob.glob('/run/net-*.conf') + glob.glob('/run/net6-*.conf') + + def config_from_klibc_net_cfg(files=None, mac_addrs=None): if files is None: - files = glob.glob('/run/net-*.conf') + glob.glob('/run/net6-*.conf') + files = _get_klibc_net_cfg_files() entries = [] names = {} @@ -160,10 +167,23 @@ def _b64dgz(b64str, gzipped="try"): return _decomp_gzip(blob, strict=gzipped != "try") +def _is_initramfs_netconfig(files, cmdline): + if files: + if 'ip=' in cmdline or 'ip6=' in cmdline: + return True + if os.path.exists(_OPEN_ISCSI_INTERFACE_FILE): + # iBft can configure networking without ip= + return True + return False + + def read_kernel_cmdline_config(files=None, mac_addrs=None, cmdline=None): if cmdline is None: cmdline = util.get_cmdline() + if files is None: + files = _get_klibc_net_cfg_files() + if 'network-config=' in cmdline: data64 = None for tok in cmdline.split(): @@ -172,7 +192,7 @@ def read_kernel_cmdline_config(files=None, mac_addrs=None, cmdline=None): if data64: return util.load_yaml(_b64dgz(data64)) - if 'ip=' not in cmdline and 'ip6=' not in cmdline: + if not _is_initramfs_netconfig(files, cmdline): return None if mac_addrs is None: diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py index a2e10536..999b1d7c 100644 --- a/cloudinit/tests/helpers.py +++ b/cloudinit/tests/helpers.py @@ -283,10 +283,15 @@ class FilesystemMockingTestCase(ResourceUsingTestCase): def patchOS(self, new_root): patch_funcs = { os.path: [('isfile', 1), ('exists', 1), - ('islink', 1), ('isdir', 1)], + ('islink', 1), ('isdir', 1), ('lexists', 1)], os: [('listdir', 1), ('mkdir', 1), - ('lstat', 1), ('symlink', 2)], + ('lstat', 1), ('symlink', 2)] } + + if hasattr(os, 'scandir'): + # py27 does not have scandir + patch_funcs[os].append(('scandir', 1)) + for (mod, funcs) in patch_funcs.items(): for f, nargs in funcs: func = getattr(mod, f) diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 9cf11f2e..84a0eabf 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -12,10 +12,8 @@ from cloudinit.sources.helpers import openstack from cloudinit import temp_utils from cloudinit import util -from cloudinit.tests.helpers import CiTestCase -from cloudinit.tests.helpers import dir2dict -from cloudinit.tests.helpers import mock -from cloudinit.tests.helpers import populate_dir +from cloudinit.tests.helpers import ( + CiTestCase, FilesystemMockingTestCase, dir2dict, mock, populate_dir) import base64 import copy @@ -2186,27 +2184,49 @@ class TestCmdlineConfigParsing(CiTestCase): self.assertEqual(found, self.simple_cfg) -class TestCmdlineReadKernelConfig(CiTestCase): +class TestCmdlineReadKernelConfig(FilesystemMockingTestCase): macs = { 'eth0': '14:02:ec:42:48:00', 'eno1': '14:02:ec:42:48:01', } - def test_ip_cmdline_read_kernel_cmdline_ip(self): - content = {'net-eth0.conf': DHCP_CONTENT_1} - files = sorted(populate_dir(self.tmp_dir(), content)) + def test_ip_cmdline_without_ip(self): + content = {'/run/net-eth0.conf': DHCP_CONTENT_1, + cmdline._OPEN_ISCSI_INTERFACE_FILE: "eth0\n"} + exp1 = copy.deepcopy(DHCP_EXPECTED_1) + exp1['mac_address'] = self.macs['eth0'] + + root = self.tmp_dir() + populate_dir(root, content) + self.reRoot(root) + found = cmdline.read_kernel_cmdline_config( - files=files, cmdline='foo ip=dhcp', mac_addrs=self.macs) + cmdline='foo root=/root/bar', mac_addrs=self.macs) + self.assertEqual(found['version'], 1) + self.assertEqual(found['config'], [exp1]) + + def test_ip_cmdline_read_kernel_cmdline_ip(self): + content = {'/run/net-eth0.conf': DHCP_CONTENT_1} exp1 = copy.deepcopy(DHCP_EXPECTED_1) exp1['mac_address'] = self.macs['eth0'] + + root = self.tmp_dir() + populate_dir(root, content) + self.reRoot(root) + + found = cmdline.read_kernel_cmdline_config( + cmdline='foo ip=dhcp', mac_addrs=self.macs) self.assertEqual(found['version'], 1) self.assertEqual(found['config'], [exp1]) def test_ip_cmdline_read_kernel_cmdline_ip6(self): - content = {'net6-eno1.conf': DHCP6_CONTENT_1} - files = sorted(populate_dir(self.tmp_dir(), content)) + content = {'/run/net6-eno1.conf': DHCP6_CONTENT_1} + root = self.tmp_dir() + populate_dir(root, content) + self.reRoot(root) + found = cmdline.read_kernel_cmdline_config( - files=files, cmdline='foo ip6=dhcp root=/dev/sda', + cmdline='foo ip6=dhcp root=/dev/sda', mac_addrs=self.macs) self.assertEqual( found, @@ -2226,18 +2246,23 @@ class TestCmdlineReadKernelConfig(CiTestCase): self.assertIsNone(found) def test_ip_cmdline_both_ip_ip6(self): - content = {'net-eth0.conf': DHCP_CONTENT_1, - 'net6-eth0.conf': DHCP6_CONTENT_1.replace('eno1', 'eth0')} - files = sorted(populate_dir(self.tmp_dir(), content)) - found = cmdline.read_kernel_cmdline_config( - files=files, cmdline='foo ip=dhcp ip6=dhcp', mac_addrs=self.macs) - + content = { + '/run/net-eth0.conf': DHCP_CONTENT_1, + '/run/net6-eth0.conf': DHCP6_CONTENT_1.replace('eno1', 'eth0')} eth0 = copy.deepcopy(DHCP_EXPECTED_1) eth0['mac_address'] = self.macs['eth0'] eth0['subnets'].append( {'control': 'manual', 'type': 'dhcp6', 'netmask': '64', 'dns_nameservers': ['2001:67c:1562:8010::2:1']}) expected = [eth0] + + root = self.tmp_dir() + populate_dir(root, content) + self.reRoot(root) + + found = cmdline.read_kernel_cmdline_config( + cmdline='foo ip=dhcp ip6=dhcp', mac_addrs=self.macs) + self.assertEqual(found['version'], 1) self.assertEqual(found['config'], expected) -- cgit v1.2.3 From d29eeccd2c422b8eb3b053fc13ca966ed6d74c78 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Thu, 22 Mar 2018 09:55:27 -0600 Subject: Handle global dns entries in netplan In network config v1 format, there are dns values which are not bound to a specific interface and do not map to the per-interface format in netplan. To handle this case we render netplan configuration that duplicates the DNS configuration on any interface that has a static network config. We avoiding interfaces which have DHCP configuration which may provide conflicting DNS values. LP: #1750884 --- cloudinit/net/netplan.py | 25 ++++++--------- tests/unittests/test_net.py | 75 ++++++++------------------------------------- 2 files changed, 21 insertions(+), 79 deletions(-) (limited to 'tests/unittests/test_net.py') diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py index 6bee3d37..63443484 100644 --- a/cloudinit/net/netplan.py +++ b/cloudinit/net/netplan.py @@ -336,22 +336,15 @@ class Renderer(renderer.Renderer): _extract_addresses(ifcfg, vlan) vlans.update({ifname: vlan}) - # inject global nameserver values under each physical interface - if nameservers: - for _eth, cfg in ethernets.items(): - nscfg = cfg.get('nameservers', {}) - addresses = nscfg.get('addresses', []) - addresses += nameservers - nscfg.update({'addresses': addresses}) - cfg.update({'nameservers': nscfg}) - - if searchdomains: - for _eth, cfg in ethernets.items(): - nscfg = cfg.get('nameservers', {}) - search = nscfg.get('search', []) - search += searchdomains - nscfg.update({'search': search}) - cfg.update({'nameservers': nscfg}) + # inject global nameserver values under each all interface which + # has addresses and do not already have a DNS configuration + if nameservers or searchdomains: + nscfg = {'addresses': nameservers, 'search': searchdomains} + for section in [ethernets, wifis, bonds, bridges, vlans]: + for _name, cfg in section.items(): + if 'nameservers' in cfg or 'addresses' not in cfg: + continue + cfg.update({'nameservers': nscfg}) # workaround yaml dictionary key sorting when dumping def _render_section(name, section): diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 84a0eabf..c12a487a 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -393,12 +393,6 @@ NETWORK_CONFIGS = { 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: @@ -410,12 +404,9 @@ NETWORK_CONFIGS = { 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 via: 65.61.151.37 @@ -654,81 +645,27 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true 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: @@ -748,6 +685,15 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true interfaces: - eth3 - eth4 + nameservers: + addresses: + - 8.8.8.8 + - 4.4.4.4 + - 8.8.4.4 + search: + - barley.maas + - wark.maas + - foobar.maas parameters: ageing-time: 250 forward-delay: 1 @@ -2334,6 +2280,9 @@ class TestNetplanRoundTrip(CiTestCase): def testsimple_render_all(self): entry = NETWORK_CONFIGS['all'] files = self._render_and_read(network_config=yaml.load(entry['yaml'])) + print(entry['expected_netplan']) + print('-- expected ^ | v rendered --') + print(files['/etc/netplan/50-cloud-init.yaml']) self.assertEqual( entry['expected_netplan'].splitlines(), files['/etc/netplan/50-cloud-init.yaml'].splitlines()) -- cgit v1.2.3