From 8439b191ec2f336d544cab86dba2860f969cd5b8 Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Tue, 15 Sep 2020 18:00:00 +0200 Subject: network: Fix type and respect name when rendering vlan in sysconfig. (#541) Prior to this change, vlans were rendered in sysconfig with 'TYPE=Ethernet', and incorrectly rendered the PHYSDEV based on the name of the vlan device rather than the 'link' provided in the network config. The change here fixes: * rendering of TYPE=Ethernet for a vlan * adds a warning if the configured device name is not supported per the RHEL 7 docs "11.5. Naming Scheme for VLAN Interfaces" LP: #1788915 LP: #1826608 RHBZ: #1861871 --- tests/unittests/test_net.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'tests/unittests/test_net.py') diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 54cc8469..207e47bb 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -1633,7 +1633,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true NM_CONTROLLED=no ONBOOT=yes PHYSDEV=bond0 - TYPE=Ethernet USERCTL=no VLAN=yes"""), 'ifcfg-br0': textwrap.dedent("""\ @@ -1677,7 +1676,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true NM_CONTROLLED=no ONBOOT=yes PHYSDEV=eth0 - TYPE=Ethernet USERCTL=no VLAN=yes"""), 'ifcfg-eth1': textwrap.dedent("""\ @@ -2286,7 +2284,6 @@ iface bond0 inet6 static NM_CONTROLLED=no ONBOOT=yes PHYSDEV=en0 - TYPE=Ethernet USERCTL=no VLAN=yes"""), }, @@ -3339,7 +3336,6 @@ USERCTL=no NM_CONTROLLED=no ONBOOT=yes PHYSDEV=eno1 - TYPE=Ethernet USERCTL=no VLAN=yes """) -- cgit v1.2.3 From f50f635afe5da4e8971ec36719bdc9249cda2d0e Mon Sep 17 00:00:00 2001 From: dermotbradley Date: Tue, 27 Oct 2020 20:54:30 +0000 Subject: Update network config docs to clarify MAC address quoting (#623) Also update MAC addresses used in testcases to remove quotes where not required and add single quotes where quotes are required. --- doc/rtd/topics/network-config-format-v1.rst | 36 ++++++++++++++++++-------- doc/rtd/topics/network-config-format-v2.rst | 13 ++++++++-- tests/unittests/test_net.py | 40 ++++++++++++++--------------- 3 files changed, 57 insertions(+), 32 deletions(-) (limited to 'tests/unittests/test_net.py') diff --git a/doc/rtd/topics/network-config-format-v1.rst b/doc/rtd/topics/network-config-format-v1.rst index dfbde514..92e81897 100644 --- a/doc/rtd/topics/network-config-format-v1.rst +++ b/doc/rtd/topics/network-config-format-v1.rst @@ -64,6 +64,14 @@ structure. The MAC Address is a device unique identifier that most Ethernet-based network devices possess. Specifying a MAC Address is optional. +.. note:: + + MAC addresses must be strings. As MAC addresses which consist of only the + digits 0-9 (i.e. no hex a-f) can be interpreted as a base 60 integer per + the `YAML 1.1 spec`_ it is best practice to quote all MAC addresses to ensure + they are parsed as strings regardless of value. + +.. _YAML 1.1 spec: https://yaml.org/type/int.html .. note:: @@ -91,7 +99,7 @@ packet- or frame-based network. Specifying ``mtu`` is optional. # Simple network adapter - type: physical name: interface0 - mac_address: 00:11:22:33:44:55 + mac_address: '00:11:22:33:44:55' # Second nic with Jumbo frames - type: physical name: jumbo0 @@ -124,6 +132,14 @@ bond interfaces. Specifying a MAC Address is optional. If ``mac_address`` is not present, then the bond will use one of the MAC Address values from one of the bond interfaces. +.. note:: + + MAC addresses must be strings. As MAC addresses which consist of only the + digits 0-9 (i.e. no hex a-f) can be interpreted as a base 60 integer per + the `YAML 1.1 spec`_ it is best practice to quote all MAC addresses to ensure + they are parsed as strings regardless of value. + +.. _YAML 1.1 spec: https://yaml.org/type/int.html **bond_interfaces**: ** @@ -194,7 +210,7 @@ Valid ``params`` keys are: # Simple network adapter - type: physical name: interface0 - mac_address: 00:11:22:33:44:55 + mac_address: '00:11:22:33:44:55' # 10G pair - type: physical name: gbe0 @@ -246,7 +262,7 @@ Valid keys are: # Simple network adapter - type: physical name: interface0 - mac_address: 00:11:22:33:44:55 + mac_address: '00:11:22:33:44:55' # Second nic with Jumbo frames - type: physical name: jumbo0 @@ -303,7 +319,7 @@ packet- or frame-based network. Specifying ``mtu`` is optional. # Physical interfaces. - type: physical name: eth0 - mac_address: "c0:d6:9f:2c:e8:80" + mac_address: c0:d6:9f:2c:e8:80 # VLAN interface. - type: vlan name: eth0.101 @@ -327,7 +343,7 @@ the following keys: config: - type: physical name: interface0 - mac_address: 00:11:22:33:44:55 + mac_address: '00:11:22:33:44:55' subnets: - type: static address: 192.168.23.14/27 @@ -358,7 +374,7 @@ has the following keys: config: - type: physical name: interface0 - mac_address: 00:11:22:33:44:55 + mac_address: '00:11:22:33:44:55' subnets: - type: static address: 192.168.23.14/24 @@ -410,7 +426,7 @@ the subnet dictionary. config: - type: physical name: interface0 - mac_address: 00:11:22:33:44:55 + mac_address: '00:11:22:33:44:55' subnets: - type: dhcp @@ -422,7 +438,7 @@ the subnet dictionary. config: - type: physical name: interface0 - mac_address: 00:11:22:33:44:55 + mac_address: '00:11:22:33:44:55' subnets: - type: static address: 192.168.23.14/27 @@ -443,7 +459,7 @@ using the static subnet configuration. config: - type: physical name: interface0 - mac_address: 00:11:22:33:44:55 + mac_address: '00:11:22:33:44:55' subnets: - type: dhcp - type: static @@ -462,7 +478,7 @@ using the static subnet configuration. config: - type: physical name: interface0 - mac_address: 00:11:22:33:44:55 + mac_address: '00:11:22:33:44:55' subnets: - type: dhcp - type: static diff --git a/doc/rtd/topics/network-config-format-v2.rst b/doc/rtd/topics/network-config-format-v2.rst index c93e29be..aa17bef5 100644 --- a/doc/rtd/topics/network-config-format-v2.rst +++ b/doc/rtd/topics/network-config-format-v2.rst @@ -94,7 +94,16 @@ NetworkManager does not. **macaddress**: *<(scalar)>* -Device's MAC address in the form "XX:XX:XX:XX:XX:XX". Globs are not allowed. +Device's MAC address in the form XX:XX:XX:XX:XX:XX. Globs are not allowed. + +.. note:: + + MAC addresses must be strings. As MAC addresses which consist of only the + digits 0-9 (i.e. no hex a-f) can be interpreted as a base 60 integer per + the `YAML 1.1 spec`_ it is best practice to quote all MAC addresses to ensure + they are parsed as strings regardless of value. + +.. _YAML 1.1 spec: https://yaml.org/type/int.html **driver**: *<(scalar)>* @@ -458,7 +467,7 @@ This is a complex example which shows most available features: :: # opaque ID for physical interfaces, only referred to by other stanzas id0: match: - macaddress: 00:11:22:33:44:55 + macaddress: '00:11:22:33:44:55' wakeonlan: true dhcp4: true addresses: diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 207e47bb..642e60cc 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -910,7 +910,7 @@ NETWORK_CONFIGS = { # Physical interfaces. - type: physical name: eth99 - mac_address: "c0:d6:9f:2c:e8:80" + mac_address: c0:d6:9f:2c:e8:80 subnets: - type: dhcp4 - type: static @@ -926,7 +926,7 @@ NETWORK_CONFIGS = { metric: 10000 - type: physical name: eth1 - mac_address: "cf:d6:af:48:e8:80" + mac_address: cf:d6:af:48:e8:80 - type: nameserver address: - 1.2.3.4 @@ -1743,26 +1743,26 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true # Physical interfaces. - type: physical name: eth0 - mac_address: "c0:d6:9f:2c:e8:80" + mac_address: c0:d6:9f:2c:e8:80 - type: physical name: eth1 - mac_address: "aa:d6:9f:2c:e8:80" + mac_address: aa:d6:9f:2c:e8:80 - type: physical name: eth2 - mac_address: "c0:bb:9f:2c:e8:80" + mac_address: c0:bb:9f:2c:e8:80 - type: physical name: eth3 - mac_address: "66:bb:9f:2c:e8:80" + mac_address: 66:bb:9f:2c:e8:80 - type: physical name: eth4 - mac_address: "98:bb:9f:2c:e8:80" + mac_address: 98:bb:9f:2c:e8:80 # specify how ifupdown should treat iface # control is one of ['auto', 'hotplug', 'manual'] # with manual meaning ifup/ifdown should not affect the iface # useful for things like iscsi root + dhcp - type: physical name: eth5 - mac_address: "98:bb:9f:2c:e8:8a" + mac_address: 98:bb:9f:2c:e8:8a subnets: - type: dhcp control: manual @@ -1793,7 +1793,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true name: bond0 # if 'mac_address' is omitted, the MAC is taken from # the first slave. - mac_address: "aa:bb:cc:dd:ee:ff" + mac_address: aa:bb:cc:dd:ee:ff bond_interfaces: - eth1 - eth2 @@ -1888,13 +1888,13 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true config: - type: physical name: bond0s0 - mac_address: "aa:bb:cc:dd:e8:00" + mac_address: aa:bb:cc:dd:e8:00 - type: physical name: bond0s1 - mac_address: "aa:bb:cc:dd:e8:01" + mac_address: aa:bb:cc:dd:e8:01 - type: bond name: bond0 - mac_address: "aa:bb:cc:dd:e8:ff" + mac_address: aa:bb:cc:dd:e8:ff mtu: 9000 bond_interfaces: - bond0s0 @@ -2042,12 +2042,12 @@ iface bond0 inet6 static eth0: match: driver: "virtio_net" - macaddress: "aa:bb:cc:dd:e8:00" + macaddress: aa:bb:cc:dd:e8:00 vf0: set-name: vf0 match: driver: "e1000" - macaddress: "aa:bb:cc:dd:e8:01" + macaddress: aa:bb:cc:dd:e8:01 bonds: bond0: addresses: @@ -2221,7 +2221,7 @@ iface bond0 inet6 static config: - type: physical name: en0 - mac_address: "aa:bb:cc:dd:e8:00" + mac_address: aa:bb:cc:dd:e8:00 - type: vlan mtu: 2222 name: en0.99 @@ -2294,13 +2294,13 @@ iface bond0 inet6 static config: - type: physical name: eth0 - mac_address: "52:54:00:12:34:00" + mac_address: '52:54:00:12:34:00' subnets: - type: static address: 2001:1::100/96 - type: physical name: eth1 - mac_address: "52:54:00:12:34:01" + mac_address: '52:54:00:12:34:01' subnets: - type: static address: 2001:1::101/96 @@ -2385,7 +2385,7 @@ iface bond0 inet6 static config: - type: physical name: eth0 - mac_address: "52:54:00:12:34:00" + mac_address: '52:54:00:12:34:00' subnets: - type: static address: 192.168.1.2/24 @@ -2393,12 +2393,12 @@ iface bond0 inet6 static - type: physical name: eth1 mtu: 1480 - mac_address: "52:54:00:12:34:aa" + mac_address: 52:54:00:12:34:aa subnets: - type: manual - type: physical name: eth2 - mac_address: "52:54:00:12:34:ff" + mac_address: 52:54:00:12:34:ff subnets: - type: manual control: manual -- cgit v1.2.3 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 +- cloudinit/net/sysconfig.py | 4 ++ cloudinit/sources/helpers/openstack.py | 8 ++- tests/unittests/test_distros/test_netconfig.py | 2 + tests/unittests/test_net.py | 98 ++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 2 deletions(-) (limited to 'tests/unittests/test_net.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', [])] diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index e9337b12..b0eecc44 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -463,6 +463,10 @@ class Renderer(renderer.Renderer): iface_cfg[mtu_key] = subnet['mtu'] else: iface_cfg[mtu_key] = subnet['mtu'] + + if subnet_is_ipv6(subnet) and flavor == 'rhel': + iface_cfg['IPV6_FORCE_ACCEPT_RA'] = False + iface_cfg['IPV6_AUTOCONF'] = False elif subnet_type == 'manual': if flavor == 'suse': LOG.debug('Unknown subnet type setting "%s"', subnet_type) diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py index 65e020c5..3e6365f1 100644 --- a/cloudinit/sources/helpers/openstack.py +++ b/cloudinit/sources/helpers/openstack.py @@ -602,11 +602,17 @@ def convert_net_json(network_json=None, known_macs=None): elif network['type'] in ['ipv6_slaac', 'ipv6_dhcpv6-stateless', 'ipv6_dhcpv6-stateful']: subnet.update({'type': network['type']}) - elif network['type'] in ['ipv4', 'ipv6']: + elif network['type'] in ['ipv4', 'static']: subnet.update({ 'type': 'static', 'address': network.get('ip_address'), }) + elif network['type'] in ['ipv6', 'static6']: + cfg.update({'accept-ra': False}) + subnet.update({ + 'type': 'static6', + 'address': network.get('ip_address'), + }) # Enable accept_ra for stateful and legacy ipv6_dhcp types if network['type'] in ['ipv6_dhcpv6-stateful', 'ipv6_dhcp']: diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 3f3fe3eb..a1df066a 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -514,7 +514,9 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase): 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 diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index 642e60cc..5af82e20 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -752,7 +752,9 @@ IPADDR=172.19.1.34 IPV6ADDR=2001:DB8::10/64 IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" IPV6INIT=yes +IPV6_AUTOCONF=no IPV6_DEFAULTGW=2001:DB8::1 +IPV6_FORCE_ACCEPT_RA=no NETMASK=255.255.252.0 NM_CONTROLLED=no ONBOOT=yes @@ -1027,6 +1029,8 @@ NETWORK_CONFIGS = { IPADDR=192.168.14.2 IPV6ADDR=2001:1::1/64 IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no NETMASK=255.255.255.0 NM_CONTROLLED=no ONBOOT=yes @@ -1253,6 +1257,33 @@ NETWORK_CONFIGS = { """), }, }, + 'static6': { + 'yaml': textwrap.dedent("""\ + version: 1 + config: + - type: 'physical' + name: 'iface0' + accept-ra: 'no' + subnets: + - type: 'static6' + address: 2001:1::1/64 + """).rstrip(' '), + 'expected_sysconfig_rhel': { + 'ifcfg-iface0': textwrap.dedent("""\ + BOOTPROTO=none + DEVICE=iface0 + IPV6ADDR=2001:1::1/64 + IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no + DEVICE=iface0 + NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no + """), + }, + }, 'dhcpv6_stateless': { 'expected_eni': textwrap.dedent("""\ auto lo @@ -1643,6 +1674,8 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true IPADDR=192.168.14.2 IPV6ADDR=2001:1::1/64 IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no IPV6_DEFAULTGW=2001:4800:78ff:1b::1 MACADDR=bb:bb:bb:bb:bb:aa NETMASK=255.255.255.0 @@ -2172,6 +2205,8 @@ iface bond0 inet6 static IPADDR1=192.168.1.2 IPV6ADDR=2001:1::1/92 IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no MTU=9000 NETMASK=255.255.255.0 NETMASK1=255.255.255.0 @@ -2277,6 +2312,8 @@ iface bond0 inet6 static IPADDR1=192.168.1.2 IPV6ADDR=2001:1::bbbb/96 IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no IPV6_DEFAULTGW=2001:1::1 MTU=2222 NETMASK=255.255.255.0 @@ -2360,6 +2397,8 @@ iface bond0 inet6 static HWADDR=52:54:00:12:34:00 IPV6ADDR=2001:1::100/96 IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no NM_CONTROLLED=no ONBOOT=yes TYPE=Ethernet @@ -2372,6 +2411,8 @@ iface bond0 inet6 static HWADDR=52:54:00:12:34:01 IPV6ADDR=2001:1::101/96 IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no NM_CONTROLLED=no ONBOOT=yes TYPE=Ethernet @@ -3178,6 +3219,61 @@ USERCTL=no self._compare_files_to_expected(entry[self.expected_name], found) self._assert_headers(found) + def test_stattic6_from_json(self): + net_json = { + "services": [{"type": "dns", "address": "172.19.0.12"}], + "networks": [{ + "network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", + "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", + }, { + "netmask": "0.0.0.0", # A second default gateway + "network": "0.0.0.0", + "gateway": "172.20.3.254", + }], + "ip_address": "172.19.1.34", "id": "network0" + }, { + "network_id": "mgmt", + "netmask": "ffff:ffff:ffff:ffff::", + "link": "interface1", + "mode": "link-local", + "routes": [], + "ip_address": "fe80::c096:67ff:fe5c:6e84", + "type": "static6", + "id": "network1", + "services": [], + "accept-ra": "false" + }], + "links": [ + { + "ethernet_mac_address": "fa:16:3e:ed:9a:59", + "mtu": None, "type": "bridge", "id": + "tap1a81968a-79", + "vif_id": "1a81968a-797a-400f-8a80-567f997eb93f" + }, + ], + } + macs = {'fa:16:3e:ed:9a:59': 'eth0'} + render_dir = self.tmp_dir() + network_cfg = openstack.convert_net_json(net_json, known_macs=macs) + ns = network_state.parse_net_config_data(network_cfg, + skip_broken=False) + renderer = self._get_renderer() + with self.assertRaises(ValueError): + renderer.render_network_state(ns, target=render_dir) + self.assertEqual([], os.listdir(render_dir)) + + def test_static6_from_yaml(self): + entry = NETWORK_CONFIGS['static6'] + found = self._render_and_read(network_config=yaml.load( + entry['yaml'])) + self._compare_files_to_expected(entry[self.expected_name], found) + self._assert_headers(found) + def test_dhcpv6_reject_ra_config_v2(self): entry = NETWORK_CONFIGS['dhcpv6_reject_ra'] found = self._render_and_read(network_config=yaml.load( @@ -3295,6 +3391,8 @@ USERCTL=no IPADDR=192.168.42.100 IPV6ADDR=2001:db8::100/32 IPV6INIT=yes + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no IPV6_DEFAULTGW=2001:db8::1 NETMASK=255.255.255.0 NM_CONTROLLED=no -- 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 'tests/unittests/test_net.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