From b46e4a8cff667c8441622089cf7d57aeb88220cd Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Thu, 29 Oct 2020 15:05:42 +0100 Subject: Explicit set IPV6_AUTOCONF and IPV6_FORCE_ACCEPT_RA on static6 (#634) The static and static6 subnet types for network_data.json were being ignored by the Openstack handler, this would cause the code to break and not function properly. As of today, if a static6 configuration is chosen, the interface will still eventually be available to receive router advertisements or be set from NetworkManager to wait for them and cycle the interface in negative case. It is safe to assume that if the interface is manually configured to use static ipv6 address, there's no need to wait for router advertisements. This patch will set automatically IPV6_AUTOCONF and IPV6_FORCE_ACCEPT_RA both to "no" in this case. This patch fixes the specific behavior only for RHEL flavor and sysconfig renderer. It also introduces new unit tests for the specific case as well as adjusts some existent tests to be compatible with the new options. This patch also addresses this problem by assigning the appropriate subnet type for each case on the openstack handler. rhbz: #1889635 rhbz: #1889635 Signed-off-by: Eduardo Otubo otubo@redhat.com --- cloudinit/net/network_state.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'cloudinit/net/network_state.py') diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index b2f7d31e..d9e7fd58 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -820,7 +820,8 @@ def _normalize_subnet(subnet): if subnet.get('type') in ('static', 'static6'): normal_subnet.update( - _normalize_net_keys(normal_subnet, address_keys=('address',))) + _normalize_net_keys(normal_subnet, address_keys=( + 'address', 'ip_address',))) normal_subnet['routes'] = [_normalize_route(r) for r in subnet.get('routes', [])] -- cgit v1.2.3 From 57349eb7df1c422d9e9558e54b201c85778997ae Mon Sep 17 00:00:00 2001 From: dermotbradley Date: Mon, 9 Nov 2020 17:24:55 +0000 Subject: Make wakeonlan Network Config v2 setting actually work (#626) Add code so that specifying "wakeonlan: true" actually results in relevant configuration entry appearing in /etc/network/interfaces, Netplan, and sysconfig for RHEL and OpenSuse. Add testcases for the above. --- cloudinit/net/eni.py | 4 ++ cloudinit/net/network_state.py | 6 +- cloudinit/net/sysconfig.py | 5 ++ tests/unittests/test_net.py | 143 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) (limited to 'cloudinit/net/network_state.py') diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index 13c041f3..0074691b 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -401,6 +401,10 @@ class Renderer(renderer.Renderer): sections = [] subnets = iface.get('subnets', {}) accept_ra = iface.pop('accept-ra', None) + ethernet_wol = iface.pop('wakeonlan', None) + if ethernet_wol: + # Specify WOL setting 'g' for using "Magic Packet" + iface['ethernet-wol'] = 'g' if subnets: for index, subnet in enumerate(subnets): ipv4_subnet_mtu = None diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index d9e7fd58..e8bf9e39 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -369,6 +369,9 @@ class NetworkStateInterpreter(metaclass=CommandHandlerMeta): accept_ra = command.get('accept-ra', None) if accept_ra is not None: accept_ra = util.is_true(accept_ra) + wakeonlan = command.get('wakeonlan', None) + if wakeonlan is not None: + wakeonlan = util.is_true(wakeonlan) iface.update({ 'name': command.get('name'), 'type': command.get('type'), @@ -379,7 +382,8 @@ class NetworkStateInterpreter(metaclass=CommandHandlerMeta): 'address': None, 'gateway': None, 'subnets': subnets, - 'accept-ra': accept_ra + 'accept-ra': accept_ra, + 'wakeonlan': wakeonlan, }) self._network_state['interfaces'].update({command.get('name'): iface}) self.dump_network_state() diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index b0eecc44..a930e612 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -367,6 +367,11 @@ class Renderer(renderer.Renderer): if new_key: iface_cfg[new_key] = old_value + # only set WakeOnLan for physical interfaces + if ('wakeonlan' in iface and iface['wakeonlan'] and + iface['type'] == 'physical'): + iface_cfg['ETHTOOL_OPTS'] = 'wol g' + @classmethod def _render_subnets(cls, iface_cfg, subnets, has_default_route, flavor): # setting base values diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 5af82e20..70453683 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -1378,6 +1378,89 @@ NETWORK_CONFIGS = { """), }, }, + 'wakeonlan_disabled': { + 'expected_eni': textwrap.dedent("""\ + auto lo + iface lo inet loopback + + auto iface0 + iface iface0 inet dhcp + """).rstrip(' '), + 'expected_netplan': textwrap.dedent(""" + network: + ethernets: + iface0: + dhcp4: true + wakeonlan: false + version: 2 + """), + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp4 + STARTMODE=auto + """), + }, + 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp + DEVICE=iface0 + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no + """), + }, + 'yaml_v2': textwrap.dedent("""\ + version: 2 + ethernets: + iface0: + dhcp4: true + wakeonlan: false + """).rstrip(' '), + }, + 'wakeonlan_enabled': { + 'expected_eni': textwrap.dedent("""\ + auto lo + iface lo inet loopback + + auto iface0 + iface iface0 inet dhcp + ethernet-wol g + """).rstrip(' '), + 'expected_netplan': textwrap.dedent(""" + network: + ethernets: + iface0: + dhcp4: true + wakeonlan: true + version: 2 + """), + 'expected_sysconfig_opensuse': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp4 + ETHTOOL_OPTS="wol g" + STARTMODE=auto + """), + }, + 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=dhcp + DEVICE=iface0 + ETHTOOL_OPTS="wol g" + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no + """), + }, + 'yaml_v2': textwrap.dedent("""\ + version: 2 + ethernets: + iface0: + dhcp4: true + wakeonlan: true + """).rstrip(' '), + }, 'all': { 'expected_eni': ("""\ auto lo @@ -3293,6 +3376,20 @@ USERCTL=no self._compare_files_to_expected(entry[self.expected_name], found) self._assert_headers(found) + def test_wakeonlan_disabled_config_v2(self): + entry = NETWORK_CONFIGS['wakeonlan_disabled'] + found = self._render_and_read(network_config=yaml.load( + entry['yaml_v2'])) + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + + def test_wakeonlan_enabled_config_v2(self): + entry = NETWORK_CONFIGS['wakeonlan_enabled'] + found = self._render_and_read(network_config=yaml.load( + entry['yaml_v2'])) + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + def test_check_ifcfg_rh(self): """ifcfg-rh plugin is added NetworkManager.conf if conf present.""" render_dir = self.tmp_dir() @@ -3829,6 +3926,20 @@ STARTMODE=auto self._compare_files_to_expected(entry[self.expected_name], found) self._assert_headers(found) + def test_wakeonlan_disabled_config_v2(self): + entry = NETWORK_CONFIGS['wakeonlan_disabled'] + found = self._render_and_read(network_config=yaml.load( + entry['yaml_v2'])) + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + + def test_wakeonlan_enabled_config_v2(self): + entry = NETWORK_CONFIGS['wakeonlan_enabled'] + found = self._render_and_read(network_config=yaml.load( + entry['yaml_v2'])) + 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'])) @@ -4478,6 +4589,22 @@ class TestNetplanRoundTrip(CiTestCase): entry['expected_netplan'].splitlines(), files['/etc/netplan/50-cloud-init.yaml'].splitlines()) + def testsimple_wakeonlan_disabled_config_v2(self): + entry = NETWORK_CONFIGS['wakeonlan_disabled'] + files = self._render_and_read(network_config=yaml.load( + entry['yaml_v2'])) + self.assertEqual( + entry['expected_netplan'].splitlines(), + files['/etc/netplan/50-cloud-init.yaml'].splitlines()) + + def testsimple_wakeonlan_enabled_config_v2(self): + entry = NETWORK_CONFIGS['wakeonlan_enabled'] + files = self._render_and_read(network_config=yaml.load( + entry['yaml_v2'])) + 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'])) @@ -4645,6 +4772,22 @@ class TestEniRoundTrip(CiTestCase): entry['expected_eni'].splitlines(), files['/etc/network/interfaces'].splitlines()) + def testsimple_wakeonlan_disabled_config_v2(self): + entry = NETWORK_CONFIGS['wakeonlan_disabled'] + files = self._render_and_read(network_config=yaml.load( + entry['yaml_v2'])) + self.assertEqual( + entry['expected_eni'].splitlines(), + files['/etc/network/interfaces'].splitlines()) + + def testsimple_wakeonlan_enabled_config_v2(self): + entry = NETWORK_CONFIGS['wakeonlan_enabled'] + files = self._render_and_read(network_config=yaml.load( + entry['yaml_v2'])) + self.assertEqual( + entry['expected_eni'].splitlines(), + files['/etc/network/interfaces'].splitlines()) + def testsimple_render_manual(self): """Test rendering of 'manual' for 'type' and 'control'. -- cgit v1.2.3