From 82462e407e06e32d7b3a694e174a54d6852ac38f Mon Sep 17 00:00:00 2001 From: Ian Bishop <151477169+ianb-mp@users.noreply.github.com> Date: Tue, 3 Dec 2024 05:18:28 +1000 Subject: [PATCH] Add SR-IOV support to nmcli module (#9168) * Add SR-IOV support to nmcli module (#9168) * Add SR-IOV support to nmcli module (#9168) Fixes * Add SR-IOV support to nmcli module (#9168) Add test * Update changelogs/fragments/9168-nmcli-add-sriov-parameter.yml Co-authored-by: Felix Fontein * Update plugins/modules/nmcli.py Co-authored-by: Felix Fontein * Update plugins/modules/nmcli.py Co-authored-by: Felix Fontein * Update plugins/modules/nmcli.py Co-authored-by: Felix Fontein * Populate sriov options --------- Co-authored-by: Felix Fontein --- .../9168-nmcli-add-sriov-parameter.yml | 2 + plugins/modules/nmcli.py | 41 ++++++++++++ tests/unit/plugins/modules/test_nmcli.py | 64 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 changelogs/fragments/9168-nmcli-add-sriov-parameter.yml diff --git a/changelogs/fragments/9168-nmcli-add-sriov-parameter.yml b/changelogs/fragments/9168-nmcli-add-sriov-parameter.yml new file mode 100644 index 0000000000..77f28e73bf --- /dev/null +++ b/changelogs/fragments/9168-nmcli-add-sriov-parameter.yml @@ -0,0 +1,2 @@ +minor_changes: + - nmcli - add ``sriov`` parameter that enables support for SR-IOV settings (https://github.com/ansible-collections/community.general/pull/9168). diff --git a/plugins/modules/nmcli.py b/plugins/modules/nmcli.py index e2803432a9..4ea6799577 100644 --- a/plugins/modules/nmcli.py +++ b/plugins/modules/nmcli.py @@ -1058,6 +1058,38 @@ options: You can encode using this Ansible jinja2 expression: V("0s{{ '[YOUR PRE-SHARED KEY]' | ansible.builtin.b64encode }}"). - This is only used when O(vpn.ipsec-enabled=true). type: str + sriov: + description: + - Allow to configure SR-IOV settings. + - 'An up-to-date list of supported attributes can be found here: + U(https://networkmanager.pages.freedesktop.org/NetworkManager/NetworkManager/settings-sriov.html).' + type: dict + version_added: 10.1.0 + suboptions: + autoprobe-drivers: + description: + - Whether to autoprobe virtual functions by a compatible driver. + type: int + eswitch-encap-mode: + description: + - Select the eswitch encapsulation support. + type: int + eswitch-inline-mode: + description: + - Select the eswitch inline-mode of the device. + type: int + eswitch-mode: + description: + - Select the eswitch mode of the device. + type: int + total-vfs: + description: Number of virtual functions to create. Consult your NIC documentation for the maximum number of VFs supported. + type: int + vfs: + description: + - 'Virtual function descriptors in the form: V(INDEX [ATTR=VALUE[ ATTR=VALUE]...]).' + - Multiple VFs can be specified using a comma as separator, for example V(2 mac=00:11:22:33:44:55 spoof-check=true,3 vlans=100). + type: str ''' EXAMPLES = r''' @@ -1687,6 +1719,7 @@ class Nmcli(object): self.wireguard = module.params['wireguard'] self.vpn = module.params['vpn'] self.transport_mode = module.params['transport_mode'] + self.sriov = module.params['sriov'] if self.method4: self.ipv4_method = self.method4 @@ -1952,6 +1985,13 @@ class Nmcli(object): 'infiniband.transport-mode': self.transport_mode, }) + if self.type == 'ethernet': + if self.sriov: + for name, value in self.sriov.items(): + options.update({ + 'sriov.%s' % name: value, + }) + # Convert settings values based on the situation. for setting, value in options.items(): setting_type = self.settings_type(setting) @@ -2607,6 +2647,7 @@ def main(): wireguard=dict(type='dict'), vpn=dict(type='dict'), transport_mode=dict(type='str', choices=['datagram', 'connected']), + sriov=dict(type='dict'), ), mutually_exclusive=[['never_default4', 'gw4'], ['routes4_extended', 'routes4'], diff --git a/tests/unit/plugins/modules/test_nmcli.py b/tests/unit/plugins/modules/test_nmcli.py index 570b04d56f..89e8de6d64 100644 --- a/tests/unit/plugins/modules/test_nmcli.py +++ b/tests/unit/plugins/modules/test_nmcli.py @@ -357,6 +357,28 @@ ipv6.ignore-auto-dns: no ipv6.ignore-auto-routes: no """ +TESTCASE_ETHERNET_ADD_SRIOV_VFS = [ + { + 'type': 'ethernet', + 'conn_name': 'non_existent_nw_device', + 'ifname': 'ethernet_non_existant', + 'sriov': { + 'total-vfs': 16, + 'vfs': '0 spoof-check=true vlans=100', + }, + 'state': 'present', + '_ansible_check_mode': False, + } +] + +TESTCASE_ETHERNET_ADD_SRIOV_VFS_SHOW_OUTPUT = """\ +connection.id: non_existent_nw_device +connection.interface-name: ethernet_non_existant +connection.autoconnect: yes +sriov.total-vfs: 16 +sriov.vfs: 0 spoof-check=true vlans=100 +""" + TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_ROUTE_AND_METRIC = [ { 'type': 'ethernet', @@ -1806,6 +1828,12 @@ def mocked_ethernet_connection_with_ipv6_static_address_multiple_static_routes_c )) +@pytest.fixture +def mocked_ethernet_connection_with_sriov_vfs_create(mocker): + mocker_set(mocker, + execute_return=(0, TESTCASE_ETHERNET_ADD_SRIOV_VFS_SHOW_OUTPUT, "")) + + @pytest.fixture def mocked_ethernet_connection_with_ipv6_static_address_static_route_with_metric_create(mocker): mocker_set(mocker, @@ -3312,6 +3340,41 @@ def test_ethernet_connection_static_ipv6_address_multiple_static_routes_with_met assert results['changed'] +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_ETHERNET_ADD_SRIOV_VFS, indirect=['patch_ansible_module']) +def test_ethernet_connection_sriov_vfs_create( + mocked_ethernet_connection_with_sriov_vfs_create, capfd): + """ + Test : Create ethernet connection with SR-IOV VFs + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + add_args, add_kw = arg_list[0] + + assert add_args[0][0] == '/usr/bin/nmcli' + assert add_args[0][1] == 'con' + assert add_args[0][2] == 'add' + assert add_args[0][3] == 'type' + assert add_args[0][4] == 'ethernet' + assert add_args[0][5] == 'con-name' + assert add_args[0][6] == 'non_existent_nw_device' + + add_args_text = list(map(to_text, add_args[0])) + + for param in ['connection.interface-name', 'ethernet_non_existant', + 'con-name', 'non_existent_nw_device', + 'sriov.total-vfs', '16', + 'sriov.vfs', '0 spoof-check=true vlans=100']: + assert param in add_args_text + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_ROUTE_AND_METRIC, indirect=['patch_ansible_module']) def test_ethernet_connection_static_ipv6_address_static_route_with_metric_create( mocked_ethernet_connection_with_ipv6_static_address_static_route_with_metric_create, capfd): @@ -4384,6 +4447,7 @@ def test_bond_connection_unchanged(mocked_generic_connection_diff_check, capfd): wireguard=dict(type='dict'), vpn=dict(type='dict'), transport_mode=dict(type='str', choices=['datagram', 'connected']), + sriov=dict(type='dict'), ), mutually_exclusive=[['never_default4', 'gw4'], ['routes4_extended', 'routes4'],