From 65e01b463cee0bdb8c8b415e78abfcc3262aad89 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Thu, 19 Jan 2017 17:31:34 -0500 Subject: tests: remove executable bit on test_net, so it runs, and fix it. The test_user_data_normalize and test_net files had gotten the executable bit set, and thus are skipped by nose by default. We could set run with the --exe flag, but they should not have gotten this way. Other changes here: * replace TempDirTestCase with CiTestCase, which has some nice tmp_dir() and tmp_path() functions. Going forward the intent is to have CiTestCase be the base test case for tests. * test_net: switch to CiTestCase and fix usage that was silently broken, because of exe bit. * populate_dir: return the list of files that it writes rather than having no return value. * CiTestCase: * support tmp_path("foo") that returns a full path to 'foo' under a tmpdir. * add tmp_dir() to get a temp dir and clean up. --- tests/unittests/test_net.py | 53 +++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 33 deletions(-) mode change 100755 => 100644 tests/unittests/test_net.py (limited to 'tests/unittests/test_net.py') diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py old mode 100755 new mode 100644 index 1090282a..2c2bde96 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -8,11 +8,10 @@ from cloudinit.net import sysconfig from cloudinit.sources.helpers import openstack from cloudinit import util +from .helpers import CiTestCase from .helpers import dir2dict from .helpers import mock from .helpers import populate_dir -from .helpers import TempDirTestCase -from .helpers import TestCase import base64 import copy @@ -20,8 +19,6 @@ import gzip import io import json import os -import shutil -import tempfile import textwrap import yaml @@ -478,7 +475,7 @@ def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net, mock_sys_dev_path.side_effect = sys_dev_path -class TestSysConfigRendering(TestCase): +class TestSysConfigRendering(CiTestCase): @mock.patch("cloudinit.net.sys_dev_path") @mock.patch("cloudinit.net.read_sys_net") @@ -486,8 +483,7 @@ class TestSysConfigRendering(TestCase): def test_default_generation(self, mock_get_devicelist, mock_read_sys_net, mock_sys_dev_path): - tmp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, tmp_dir) + tmp_dir = self.tmp_dir() _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net, mock_sys_dev_path) @@ -518,9 +514,7 @@ USERCTL=no self.assertEqual(expected_content, content) def test_openstack_rendering_samples(self): - tmp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, tmp_dir) - render_dir = os.path.join(tmp_dir, "render") + render_dir = self.tmp_dir() for os_sample in OS_SAMPLES: ex_input = os_sample['in_data'] ex_mac_addrs = os_sample['in_macs'] @@ -535,7 +529,7 @@ USERCTL=no self.assertEqual(expected_content, fh.read()) -class TestEniNetRendering(TestCase): +class TestEniNetRendering(CiTestCase): @mock.patch("cloudinit.net.sys_dev_path") @mock.patch("cloudinit.net.read_sys_net") @@ -543,8 +537,7 @@ class TestEniNetRendering(TestCase): def test_default_generation(self, mock_get_devicelist, mock_read_sys_net, mock_sys_dev_path): - tmp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, tmp_dir) + tmp_dir = self.tmp_dir() _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net, mock_sys_dev_path) @@ -576,7 +569,7 @@ iface eth1000 inet dhcp self.assertEqual(expected.lstrip(), contents.lstrip()) -class TestEniNetworkStateToEni(TestCase): +class TestEniNetworkStateToEni(CiTestCase): mycfg = { 'config': [{"type": "physical", "name": "eth0", "mac_address": "c0:d6:9f:2c:e8:80", @@ -607,7 +600,7 @@ class TestEniNetworkStateToEni(TestCase): self.assertNotIn("hwaddress", rendered) -class TestCmdlineConfigParsing(TestCase): +class TestCmdlineConfigParsing(CiTestCase): simple_cfg = { 'config': [{"type": "physical", "name": "eth0", "mac_address": "c0:d6:9f:2c:e8:80", @@ -665,7 +658,7 @@ class TestCmdlineConfigParsing(TestCase): self.assertEqual(found, self.simple_cfg) -class TestCmdlineReadKernelConfig(TempDirTestCase): +class TestCmdlineReadKernelConfig(CiTestCase): macs = { 'eth0': '14:02:ec:42:48:00', 'eno1': '14:02:ec:42:48:01', @@ -673,8 +666,7 @@ class TestCmdlineReadKernelConfig(TempDirTestCase): def test_ip_cmdline_read_kernel_cmdline_ip(self): content = {'net-eth0.conf': DHCP_CONTENT_1} - populate_dir(self.tmp, content) - files = [os.path.join(self.tmp, k) for k in content.keys()] + files = sorted(populate_dir(self.tmp_dir(), content)) found = cmdline.read_kernel_cmdline_config( files=files, cmdline='foo ip=dhcp', mac_addrs=self.macs) exp1 = copy.deepcopy(DHCP_EXPECTED_1) @@ -684,8 +676,7 @@ class TestCmdlineReadKernelConfig(TempDirTestCase): def test_ip_cmdline_read_kernel_cmdline_ip6(self): content = {'net6-eno1.conf': DHCP6_CONTENT_1} - populate_dir(self.tmp, content) - files = [os.path.join(self.tmp, k) for k in content.keys()] + files = sorted(populate_dir(self.tmp_dir(), content)) found = cmdline.read_kernel_cmdline_config( files=files, cmdline='foo ip6=dhcp root=/dev/sda', mac_addrs=self.macs) @@ -701,8 +692,7 @@ class TestCmdlineReadKernelConfig(TempDirTestCase): def test_ip_cmdline_read_kernel_cmdline_none(self): # if there is no ip= or ip6= on cmdline, return value should be None content = {'net6-eno1.conf': DHCP6_CONTENT_1} - populate_dir(self.tmp, content) - files = [os.path.join(self.tmp, k) for k in content.keys()] + files = sorted(populate_dir(self.tmp_dir(), content)) found = cmdline.read_kernel_cmdline_config( files=files, cmdline='foo root=/dev/sda', mac_addrs=self.macs) self.assertEqual(found, None) @@ -710,8 +700,7 @@ class TestCmdlineReadKernelConfig(TempDirTestCase): def test_ip_cmdline_both_ip_ip6(self): content = {'net-eth0.conf': DHCP_CONTENT_1, 'net6-eth0.conf': DHCP6_CONTENT_1.replace('eno1', 'eth0')} - populate_dir(self.tmp, content) - files = [os.path.join(self.tmp, k) for k in sorted(content.keys())] + 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) @@ -725,14 +714,12 @@ class TestCmdlineReadKernelConfig(TempDirTestCase): self.assertEqual(found['config'], expected) -class TestEniRoundTrip(TestCase): - def setUp(self): - super(TestCase, self).setUp() - self.tmp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, self.tmp_dir) - +class TestEniRoundTrip(CiTestCase): def _render_and_read(self, network_config=None, state=None, eni_path=None, - links_prefix=None, netrules_path=None): + links_prefix=None, netrules_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: @@ -747,8 +734,8 @@ class TestEniRoundTrip(TestCase): config={'eni_path': eni_path, 'links_path_prefix': links_prefix, 'netrules_path': netrules_path}) - renderer.render_network_state(self.tmp_dir, ns) - return dir2dict(self.tmp_dir) + renderer.render_network_state(dir, ns) + return dir2dict(dir) def testsimple_convert_and_render(self): network_config = eni.convert_eni_data(EXAMPLE_ENI) -- cgit v1.2.3 From 2de1c247e285cce0b25ab70abdc56ccd41019c27 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Wed, 25 Jan 2017 15:45:40 -0600 Subject: Fix eni rendering of multiple IPs per interface The iface:alias syntax for eni rendering is brittle with ipv6. Replace it with using multiple iface stanzas with the same iface name which is supported. Side-effect is that one can no longer do 'ifup $iface:$alias' but requires instead use of ip address {add|delete} instead. LP: #1657940 --- cloudinit/net/eni.py | 33 ++++++++++++++++++-------------- tests/unittests/test_net.py | 46 +++++++++++++++++++++++++++++++++------------ 2 files changed, 53 insertions(+), 26 deletions(-) (limited to 'tests/unittests/test_net.py') diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index b06ffac9..5b249f1f 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -90,8 +90,6 @@ def _iface_add_attrs(iface, index): def _iface_start_entry(iface, index, render_hwaddress=False): fullname = iface['name'] - if index != 0: - fullname += ":%s" % index control = iface['control'] if control == "auto": @@ -113,6 +111,16 @@ def _iface_start_entry(iface, index, render_hwaddress=False): return lines +def _subnet_is_ipv6(subnet): + # 'static6' or 'dhcp6' + if subnet['type'].endswith('6'): + # This is a request for DHCPv6. + return True + elif subnet['type'] == 'static' and ":" in subnet['address']: + return True + return False + + def _parse_deb_config_data(ifaces, contents, src_dir, src_path): """Parses the file contents, placing result into ifaces. @@ -354,21 +362,23 @@ class Renderer(renderer.Renderer): sections = [] subnets = iface.get('subnets', {}) if subnets: - for index, subnet in zip(range(0, len(subnets)), subnets): + for index, subnet in enumerate(subnets): iface['index'] = index iface['mode'] = subnet['type'] iface['control'] = subnet.get('control', 'auto') subnet_inet = 'inet' - if iface['mode'].endswith('6'): - # This is a request for DHCPv6. - subnet_inet += '6' - elif iface['mode'] == 'static' and ":" in subnet['address']: - # This is a static IPv6 address. + if _subnet_is_ipv6(subnet): subnet_inet += '6' iface['inet'] = subnet_inet - if iface['mode'].startswith('dhcp'): + if subnet['type'].startswith('dhcp'): iface['mode'] = 'dhcp' + # do not emit multiple 'auto $IFACE' lines as older (precise) + # ifupdown complains + if True in ["auto %s" % (iface['name']) in line + for line in sections]: + iface['control'] = 'alias' + lines = list( _iface_start_entry( iface, index, render_hwaddress=render_hwaddress) + @@ -378,11 +388,6 @@ class Renderer(renderer.Renderer): for route in subnet.get('routes', []): lines.extend(self._render_route(route, indent=" ")) - if len(subnets) > 1 and index == 0: - tmpl = " post-up ifup %s:%s\n" - for i in range(1, len(subnets)): - lines.append(tmpl % (iface['name'], i)) - sections.append(lines) else: # ifenslave docs say to auto the slave devices diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 2c2bde96..b77d277a 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -219,11 +219,9 @@ NETWORK_CONFIGS = { auto eth99 iface eth99 inet dhcp - post-up ifup eth99:1 - - auto eth99:1 - iface eth99:1 inet static + # control-alias eth99 + iface eth99 inet static address 192.168.21.3/24 dns-nameservers 8.8.8.8 8.8.4.4 dns-search barley.maas sach.maas @@ -261,6 +259,27 @@ NETWORK_CONFIGS = { - wark.maas """), }, + 'v4_and_v6': { + 'expected_eni': textwrap.dedent("""\ + auto lo + iface lo inet loopback + + auto iface0 + iface iface0 inet dhcp + + # control-alias iface0 + iface iface0 inet6 dhcp + """).rstrip(' '), + 'yaml': textwrap.dedent("""\ + version: 1 + config: + - type: 'physical' + name: 'iface0' + subnets: + - {'type': 'dhcp4'} + - {'type': 'dhcp6'} + """).rstrip(' '), + }, 'all': { 'expected_eni': ("""\ auto lo @@ -298,11 +317,9 @@ iface br0 inet static address 192.168.14.2/24 bridge_ports eth3 eth4 bridge_stp off - post-up ifup br0:1 - -auto br0:1 -iface br0:1 inet6 static +# control-alias br0 +iface br0 inet6 static address 2001:1::1/64 auto bond0.200 @@ -319,11 +336,9 @@ iface eth0.101 inet static mtu 1500 vlan-raw-device eth0 vlan_id 101 - post-up ifup eth0.101:1 - -auto eth0.101:1 -iface eth0.101:1 inet static +# control-alias eth0.101 +iface eth0.101 inet static address 192.168.2.10/24 post-up route add -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true @@ -758,6 +773,13 @@ class TestEniRoundTrip(CiTestCase): entry['expected_eni'].splitlines(), files['/etc/network/interfaces'].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_eni'].splitlines(), + files['/etc/network/interfaces'].splitlines()) + def test_routes_rendered(self): # as reported in bug 1649652 conf = [ -- cgit v1.2.3 From f81d6c7bde2af206d449de593b35773068270c84 Mon Sep 17 00:00:00 2001 From: Lars Kellogg-Stedman Date: Fri, 17 Feb 2017 08:55:05 -0500 Subject: net: correct errors in cloudinit/net/sysconfig.py There were some logic errors in sysconfig.py that appear to be the result of accidentally typing "iface" where it should have been "iface_cfg". This patch corrects those problems so that the module can run successfully. LP: #1665441 Resolves: rhbz#1389530 --- cloudinit/net/sysconfig.py | 4 +-- tests/unittests/test_net.py | 87 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 3 deletions(-) (limited to 'tests/unittests/test_net.py') diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index 9be74070..19e220ae 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -283,10 +283,10 @@ class Renderer(renderer.Renderer): cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0]) elif len(iface_subnets) > 1: for i, iface_subnet in enumerate(iface_subnets, - start=len(iface.children)): + start=len(iface_cfg.children)): iface_sub_cfg = iface_cfg.copy() iface_sub_cfg.name = "%s:%s" % (iface_name, i) - iface.children.append(iface_sub_cfg) + iface_cfg.children.append(iface_sub_cfg) cls._render_subnet(iface_sub_cfg, route_cfg, iface_subnet) @classmethod diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index b77d277a..1b6288d4 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -159,6 +159,91 @@ NETMASK0=0.0.0.0 ; Created by cloud-init on instance boot automatically, do not edit. ; nameserver 172.19.0.12 +""".lstrip()), + ('etc/udev/rules.d/70-persistent-net.rules', + "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', + 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))] + }, + { + 'in_data': { + "services": [{"type": "dns", "address": "172.19.0.12"}], + "networks": [{ + "network_id": "public-ipv4", + "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", + }], + "ip_address": "172.19.1.34", "id": "network0" + },{ + "network_id": "private-ipv4", + "type": "ipv4", "netmask": "255.255.255.0", + "link": "tap1a81968a-79", + "routes": [], + "ip_address": "10.0.0.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" + }, + ], + }, + 'in_macs': { + 'fa:16:3e:ed:9a:59': 'eth0', + }, + 'out_sysconfig': [ + ('etc/sysconfig/network-scripts/ifcfg-eth0', + """ +# Created by cloud-init on instance boot automatically, do not edit. +# +BOOTPROTO=none +DEVICE=eth0 +HWADDR=fa:16:3e:ed:9a:59 +NM_CONTROLLED=no +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +""".lstrip()), + ('etc/sysconfig/network-scripts/ifcfg-eth0:0', + """ +# Created by cloud-init on instance boot automatically, do not edit. +# +BOOTPROTO=static +DEFROUTE=yes +DEVICE=eth0:0 +GATEWAY=172.19.3.254 +HWADDR=fa:16:3e:ed:9a:59 +IPADDR=172.19.1.34 +NETMASK=255.255.252.0 +NM_CONTROLLED=no +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +""".lstrip()), + ('etc/sysconfig/network-scripts/ifcfg-eth0:1', + """ +# Created by cloud-init on instance boot automatically, do not edit. +# +BOOTPROTO=static +DEVICE=eth0:1 +HWADDR=fa:16:3e:ed:9a:59 +IPADDR=10.0.0.10 +NETMASK=255.255.255.0 +NM_CONTROLLED=no +ONBOOT=yes +TYPE=Ethernet +USERCTL=no +""".lstrip()), + ('etc/resolv.conf', + """ +; Created by cloud-init on instance boot automatically, do not edit. +; +nameserver 172.19.0.12 """.lstrip()), ('etc/udev/rules.d/70-persistent-net.rules', "".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', @@ -529,8 +614,8 @@ USERCTL=no self.assertEqual(expected_content, content) def test_openstack_rendering_samples(self): - render_dir = self.tmp_dir() for os_sample in OS_SAMPLES: + render_dir = self.tmp_dir() ex_input = os_sample['in_data'] ex_mac_addrs = os_sample['in_macs'] network_cfg = openstack.convert_net_json( -- cgit v1.2.3 From da25385d0613b373c5746761748782ca1e157d10 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 17 Feb 2017 12:05:38 -0500 Subject: flake8: fix flake8 complaints in previous commit. --- cloudinit/net/sysconfig.py | 6 +++--- tests/unittests/test_net.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'tests/unittests/test_net.py') diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index 19e220ae..6e7739fb 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -282,12 +282,12 @@ class Renderer(renderer.Renderer): if len(iface_subnets) == 1: cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0]) elif len(iface_subnets) > 1: - for i, iface_subnet in enumerate(iface_subnets, - start=len(iface_cfg.children)): + for i, isubnet in enumerate(iface_subnets, + start=len(iface_cfg.children)): iface_sub_cfg = iface_cfg.copy() iface_sub_cfg.name = "%s:%s" % (iface_name, i) iface_cfg.children.append(iface_sub_cfg) - cls._render_subnet(iface_sub_cfg, route_cfg, iface_subnet) + cls._render_subnet(iface_sub_cfg, route_cfg, isubnet) @classmethod def _render_bond_interfaces(cls, network_state, iface_contents): diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 1b6288d4..4b03ff72 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -177,7 +177,7 @@ nameserver 172.19.0.12 "gateway": "172.19.3.254", }], "ip_address": "172.19.1.34", "id": "network0" - },{ + }, { "network_id": "private-ipv4", "type": "ipv4", "netmask": "255.255.255.0", "link": "tap1a81968a-79", -- cgit v1.2.3