diff --git a/library/network_connections.py b/library/network_connections.py index 588013b..24cef1f 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -95,6 +95,7 @@ ABSENT_STATE = "absent" DEFAULT_ACTIVATION_TIMEOUT = 90 DEFAULT_TIMEOUT = 10 +NULL_MAC = "00:00:00:00:00:00" class CheckMode: @@ -166,6 +167,20 @@ class SysUtil: c = SysUtil._sysctl_read("/sys/class/net/" + ifname + "/address") return Util.mac_norm(c.strip()) + @staticmethod + def _link_read_bond_port_perm_hwaddr(ifname): + filename = os.path.join( + "/sys/class/net", + ifname, + # wokeignore:rule=slave + "bonding_slave", + "perm_hwaddr", + ) + if not os.path.exists(filename): + return None + c = SysUtil._sysctl_read(filename) + return Util.mac_norm(c.strip()) + @staticmethod def _link_read_permaddress(ifname): return ethtool.get_perm_addr(ifname) @@ -186,6 +201,13 @@ class SysUtil: "ifname": ifname, "address": SysUtil._link_read_address(ifname), "perm-address": SysUtil._link_read_permaddress(ifname), + # When an interface is added as a port of a bonding device, its MAC + # address might change, we need to retrieve and preserve the original + # MAC address to ensure the user-provided interface name and MAC match + # correctly. + "bond-port-perm-hwaddr": SysUtil._link_read_bond_port_perm_hwaddr( + ifname + ), } return links @@ -2130,14 +2152,13 @@ class Cmd(object): for idx, connection in enumerate(self.connections): if "type" in connection and connection["check_iface_exists"]: # when the profile is tied to a certain interface via - # 'interface_name' or 'mac', check that such an interface + # 'interface_name' and 'mac', check that such an interface # exists. # # This check has many flaws, as we don't check whether the # existing interface has the right device type. Also, there is # some ambiguity between the current MAC address and the # permanent MAC address. - li_mac = None li_ifname = None if connection["interface_name"]: li_ifname = SysUtil.link_info_find(connection["interface_name"]) @@ -2156,13 +2177,23 @@ class Cmd(object): "infiniband interface exists" % (connection["interface_name"]), ) - if li_mac and li_ifname and li_mac != li_ifname: - self.log_fatal( - idx, - "profile specifies interface_name '%s' and mac '%s' but no " - "such interface exists" - % (connection["interface_name"], connection["mac"]), - ) + elif connection["mac"]: + perm_address = li_ifname.get("perm-address", NULL_MAC) + current_address = li_ifname.get("address", NULL_MAC) + bond_port_perm_hwaddr = li_ifname.get( + "bond-port-perm-hwaddr", NULL_MAC + ) + if (perm_address not in (NULL_MAC, connection["mac"])) or ( + perm_address == NULL_MAC + and connection["mac"] + not in (current_address, bond_port_perm_hwaddr) + ): + self.log_fatal( + idx, + "profile specifies interface_name '%s' and mac '%s' " + "but no such interface exists" + % (connection["interface_name"], connection["mac"]), + ) def start_transaction(self): """Hook before making changes""" diff --git a/tests/ensure_provider_tests.py b/tests/ensure_provider_tests.py index d1d1dc4..8502464 100755 --- a/tests/ensure_provider_tests.py +++ b/tests/ensure_provider_tests.py @@ -74,6 +74,7 @@ ibution_major_version | int < 9", }, "playbooks/tests_ignore_auto_dns.yml": {}, "playbooks/tests_bond_options.yml": {}, + "playbooks/tests_bond_port_match_by_mac.yml": {}, "playbooks/tests_eth_dns_support.yml": {}, "playbooks/tests_dummy.yml": {}, # wokeignore:rule=dummy "playbooks/tests_infiniband.yml": {}, diff --git a/tests/playbooks/tests_bond_port_match_by_mac.yml b/tests/playbooks/tests_bond_port_match_by_mac.yml new file mode 100644 index 0000000..a66616b --- /dev/null +++ b/tests/playbooks/tests_bond_port_match_by_mac.yml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Play for creating the connection to match the port device + based on the perm_hwaddr + hosts: all + vars: + controller_profile: bond0 + controller_device: nm-bond + port1_profile: bond0.0 + dhcp_interface1: test1 + port2_profile: bond0.1 + dhcp_interface2: test2 + profile: test2conn + interface: test2 + tasks: + - name: Test creating the connection to match the port device + based on the perm_hwaddr + tags: + - tests::bond:create + block: + - name: Include the task 'run_test.yml' + include_tasks: tasks/run_test.yml + vars: + lsr_description: Given two DHCP-enabled network interfaces, + when creating a bond profile with them, + then we can still create the connection to match the port device + based on the perm_hwaddr + lsr_setup: + - tasks/create_test_interfaces_with_dhcp.yml + - tasks/assert_dhcp_device_present.yml + lsr_test: + - tasks/create_bond_profile.yml + - tasks/create_bond_port_match_by_mac.yml + lsr_assert: + - tasks/assert_controller_device_present.yml + - tasks/assert_profile_present.yml + lsr_cleanup: + - tasks/cleanup_bond_profile+device.yml + - tasks/remove_test_interfaces_with_dhcp.yml + - tasks/check_network_dns.yml diff --git a/tests/tasks/create_bond_port_match_by_mac.yml b/tests/tasks/create_bond_port_match_by_mac.yml new file mode 100644 index 0000000..5de8133 --- /dev/null +++ b/tests/tasks/create_bond_port_match_by_mac.yml @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Retrieve perm_hwaddr using ethtool + # wokeignore:rule=slave + command: cat /sys/class/net/{{ interface }}/bonding_slave/perm_hwaddr + register: mac_address_result + changed_when: false + failed_when: mac_address_result.rc != 0 +- name: Set the MAC address variable + set_fact: + mac: "{{ mac_address_result.stdout_lines[-1].split(' ')[-1] }}" +- name: Display the retrieved MAC address + debug: + msg: "Retrieved MAC address for {{ interface }}: {{ mac }}" +- name: Test matching the port device based on the perm_hwaddr + import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ profile }}" + state: up + type: ethernet + interface_name: "{{ interface }}" + mac: "{{ mac }}" +... diff --git a/tests/tests_bond_port_match_by_mac_nm.yml b/tests/tests_bond_port_match_by_mac_nm.yml new file mode 100644 index 0000000..399a506 --- /dev/null +++ b/tests/tests_bond_port_match_by_mac_nm.yml @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: BSD-3-Clause +# This file was generated by ensure_provider_tests.py +--- +# set network provider and gather facts +# yamllint disable rule:line-length +- name: Run playbook 'playbooks/tests_bond_port_match_by_mac.yml' with nm as provider + hosts: all + tasks: + - name: Include the task 'el_repo_setup.yml' + include_tasks: tasks/el_repo_setup.yml + - name: Set network provider to 'nm' + set_fact: + network_provider: nm + tags: + - always + + +# The test requires or should run with NetworkManager, therefore it cannot run +# on RHEL/CentOS 6 +- name: Import the playbook 'playbooks/tests_bond_port_match_by_mac.yml' + import_playbook: playbooks/tests_bond_port_match_by_mac.yml + when: + - ansible_distribution_major_version != '6'