diff --git a/ansible/roles/network-debian/tasks/main.yml b/ansible/roles/network-debian/tasks/main.yml index ffbcdc3b7..bd084431e 100644 --- a/ansible/roles/network-debian/tasks/main.yml +++ b/ansible/roles/network-debian/tasks/main.yml @@ -41,3 +41,9 @@ systemd_networkd_cleanup: true systemd_networkd_cleanup_patterns: - "{{ networkd_prefix }}*" + +- name: Ensure udev is triggered on links changes + become: true + command: "udevadm trigger --verbose --subsystem-match=net --action=add" + changed_when: false + when: network_interfaces | networkd_links | length diff --git a/doc/source/configuration/reference/network.rst b/doc/source/configuration/reference/network.rst index f091c6f28..f13578026 100644 --- a/doc/source/configuration/reference/network.rst +++ b/doc/source/configuration/reference/network.rst @@ -429,6 +429,34 @@ To configure a network called ``example`` with an Ethernet interface on example_interface: eth0 +Advanced: Configuring (Renaming) Ethernet Interfaces System Name +---------------------------------------------------------------- + +The name of the Ethernet interface may be explicitly configured by binding +known MAC address of the specific interface to its name by setting the +``macaddress`` attribute for a network. + +.. warning:: + + Supported only on Ubuntu/Debian operating systems. + +To configure a network called ``example`` with known MAC address +``aa:bb:cc:dd:ee:ff`` and rename it from a system name (might be ``eth0``, +``ens3``, or any other name) to the ``lan0`` (new name): + +.. code-block:: yaml + :caption: ``inventory/group_vars//network-interfaces`` + + example_interface: lan0 + example_macaddress: "aa:bb:cc:dd:ee:ff" + +.. warning:: + + The network interface must be down before changing its name. See + `issue `__ in the systemd + project. So the configured node reboot might be required right after the + ``seed host configure`` or ``overcloud host configure`` Kayobe commands. + .. _configuring-bridge-interfaces: Configuring Bridge Interfaces diff --git a/kayobe/plugins/filter/networkd.py b/kayobe/plugins/filter/networkd.py index ff338aaf2..aafe4ded9 100644 --- a/kayobe/plugins/filter/networkd.py +++ b/kayobe/plugins/filter/networkd.py @@ -479,6 +479,35 @@ def _veth_peer_network(context, veth, inventory_hostname): return _filter_options(config) +def _ether_link(context, name, inventory_hostname): + """Return a networkd link configuration for a ether. + + :param context: a Jinja2 Context object. + :param name: name of the network. + :param inventory_hostname: Ansible inventory hostname. + """ + config = [] + + device = networks.net_interface(context, name, inventory_hostname) + macaddress = networks.net_macaddress(context, name, inventory_hostname) + + if macaddress is not None: + config = [ + { + 'Match': [ + {'PermanentMACAddress': macaddress}, + ], + }, + { + 'Link': [ + {'Name': device}, + ], + } + ] + + return _filter_options(config) + + def _add_to_result(result, prefix, device, config): """Add configuration for an interface to a filter result. @@ -561,8 +590,20 @@ def networkd_links(context, names, inventory_hostname=None): :param inventory_hostname: Ansible inventory hostname. :returns: a dict representation of networkd link configuration. """ - # NOTE(mgoddard): We do not currently support link configuration. - return {} + # Prefix for configuration file names. + prefix = utils.get_hostvar(context, "networkd_prefix", inventory_hostname) + + result = {} + + # only ethers + for name in networks.net_select_ethers(context, names, inventory_hostname): + device = networks.get_and_validate_interface(context, name, + inventory_hostname) + ether_link = _ether_link(context, name, inventory_hostname) + if ether_link: + _add_to_result(result, prefix, device, ether_link) + + return result @jinja2.pass_context diff --git a/kayobe/plugins/filter/networks.py b/kayobe/plugins/filter/networks.py index 51b63ddba..f949d7982 100644 --- a/kayobe/plugins/filter/networks.py +++ b/kayobe/plugins/filter/networks.py @@ -274,6 +274,11 @@ def net_mtu(context, name, inventory_hostname=None): return mtu +@jinja2.pass_context +def net_macaddress(context, name, inventory_hostname=None): + return net_attr(context, name, 'macaddress', inventory_hostname) + + @jinja2.pass_context def net_bridge_stp(context, name, inventory_hostname=None): """Return the Spanning Tree Protocol (STP) state for a bridge. @@ -394,6 +399,7 @@ def net_interface_obj(context, name, inventory_hostname=None, names=None): netmask = None vlan = net_vlan(context, name, inventory_hostname) mtu = net_mtu(context, name, inventory_hostname) + macaddress = net_macaddress(context, name, inventory_hostname) # NOTE(priteau): do not pass MTU for VLAN interfaces on bridges when it is # identical to the parent bridge, to work around a NetworkManager bug. @@ -433,6 +439,7 @@ def net_interface_obj(context, name, inventory_hostname=None, names=None): 'gateway': gateway, 'vlan': vlan, 'mtu': mtu, + 'macaddress': macaddress, 'route': routes, 'rules': rules, 'bootproto': bootproto or 'static', @@ -789,6 +796,7 @@ def get_filters(): 'net_neutron_gateway': net_neutron_gateway, 'net_vlan': net_vlan, 'net_mtu': net_mtu, + 'net_macaddress': net_macaddress, 'net_routes': net_routes, 'net_rules': net_rules, 'net_physical_network': net_physical_network, diff --git a/kayobe/tests/unit/plugins/filter/test_networkd.py b/kayobe/tests/unit/plugins/filter/test_networkd.py index 37fb272c4..ffcedecca 100644 --- a/kayobe/tests/unit/plugins/filter/test_networkd.py +++ b/kayobe/tests/unit/plugins/filter/test_networkd.py @@ -33,6 +33,7 @@ class BaseNetworkdTest(unittest.TestCase): "net1_interface": "eth0", "net1_cidr": "1.2.3.0/24", "net1_ips": {"test-host": "1.2.3.4"}, + "net1_macaddress": "aa:bb:cc:dd:ee:ff", # net2: VLAN on eth0.2 with VLAN 2 on interface eth0. "net2_interface": "eth0.2", "net2_vlan": 2, @@ -351,9 +352,27 @@ class TestNetworkdNetDevs(BaseNetworkdTest): class TestNetworkdLinks(BaseNetworkdTest): def test_empty(self): - links = networkd.networkd_links(self.context, ['net1']) + links = networkd.networkd_links(self.context, ['net2']) self.assertEqual({}, links) + def test_link_name(self): + links = networkd.networkd_links(self.context, ['net1']) + expected = { + "50-kayobe-eth0": [ + { + "Match": [ + {"PermanentMACAddress": "aa:bb:cc:dd:ee:ff"} + ] + }, + { + "Link": [ + {"Name": "eth0"}, + ] + }, + ] + } + self.assertEqual(expected, links) + class TestNetworkdNetworks(BaseNetworkdTest): diff --git a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py index 22cceb429..cf5424f3a 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py +++ b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py @@ -5,7 +5,6 @@ import ipaddress import os -import time import distro import pytest @@ -54,17 +53,18 @@ def test_network_bridge(host): interface = host.interface('br0') assert interface.exists assert '192.168.36.1' in interface.addresses - stp_status = host.file('/sys/class/net/br0/bridge/stp_state').content_string.strip() + state_file = "/sys/class/net/br0/bridge/stp_state" + stp_status = host.file(state_file).content_string.strip() assert '0' == stp_status ports = ['dummy3', 'dummy4'] sys_ports = host.check_output('ls -1 /sys/class/net/br0/brif') assert sys_ports == "\n".join(ports) for port in ports: - interface = host.interface(port) - assert interface.exists - v4_addresses = [a for a in interface.addresses - if ipaddress.ip_address(a).version == '4'] - assert not v4_addresses + interface = host.interface(port) + assert interface.exists + v4_addresses = [a for a in interface.addresses + if ipaddress.ip_address(a).version == '4'] + assert not v4_addresses def test_network_bridge_vlan(host): @@ -84,9 +84,9 @@ def test_network_bond(host): slaves = set(['dummy5', 'dummy6']) assert sys_slaves == slaves for slave in slaves: - interface = host.interface(slave) - assert interface.exists - assert not interface.addresses + interface = host.interface(slave) + assert interface.exists + assert not interface.addresses def test_network_bond_vlan(host): @@ -99,8 +99,9 @@ def test_network_bond_vlan(host): def test_network_bridge_no_ip(host): interface = host.interface('br1') assert interface.exists - assert not '192.168.40.1' in interface.addresses - stp_status = host.file('/sys/class/net/br1/bridge/stp_state').content_string.strip() + assert '192.168.40.1' not in interface.addresses + state_file = "/sys/class/net/br1/bridge/stp_state" + stp_status = host.file(state_file).content_string.strip() assert '1' == stp_status @@ -113,13 +114,13 @@ def test_network_systemd_vlan(host): def test_additional_user_account(host): - user = host.user("kayobe-test-user") - assert user.name == "kayobe-test-user" - assert user.group == "kayobe-test-user" - assert set(user.groups) == {"kayobe-test-user", "stack"} - assert user.gecos == "Kayobe test user" - with host.sudo(): - assert user.password == 'kayobe-test-user-password' + user = host.user("kayobe-test-user") + assert user.name == "kayobe-test-user" + assert user.group == "kayobe-test-user" + assert set(user.groups) == {"kayobe-test-user", "stack"} + assert user.gecos == "Kayobe test user" + with host.sudo(): + assert user.password == 'kayobe-test-user-password' def test_software_RAID(host): @@ -229,7 +230,8 @@ def test_apt_auth(host): @pytest.mark.parametrize('repo', ["appstream", "baseos", "extras", "epel"]) -@pytest.mark.skipif(not _is_dnf_mirror(), reason="DNF OpenDev mirror only for CentOS 8") +@pytest.mark.skipif(not _is_dnf_mirror(), + reason="DNF OpenDev mirror only for CentOS 8") def test_dnf_local_package_mirrors(host, repo): # Depends on SITE_MIRROR_FQDN environment variable. assert os.getenv('SITE_MIRROR_FQDN') @@ -256,7 +258,7 @@ def test_dnf_automatic(host): @pytest.mark.skipif(not _is_dnf(), - reason="tuned profile setting only supported on CentOS/Rocky") + reason="tuned profiles only supported on CentOS/Rocky") def test_tuned_profile_is_active(host): tuned_output = host.check_output("tuned-adm active") assert "throughput-performance" in tuned_output diff --git a/releasenotes/notes/add-support-link-configuration-a80e8579fc8b783f.yaml b/releasenotes/notes/add-support-link-configuration-a80e8579fc8b783f.yaml new file mode 100644 index 000000000..27f5dcde2 --- /dev/null +++ b/releasenotes/notes/add-support-link-configuration-a80e8579fc8b783f.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added initial support for systemd-networkd link configuration, now you can + configure and rename the name of a network interface if you know the MAC + address of the interface.