From 76d011ed4b5789775280eaeac0a8cf002f769108 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:26:04 +0100 Subject: [PATCH] [PR #7515/5147c494 backport][stable-8] Add support for loopback type in nmcli module (#7566) Add support for loopback type in nmcli module (#7515) * add support for loopback devices * add support for loopback devices * fix yamllint issue in changelog fragment * Fix yamlint warning and add note about new connectin type in docs * change order of lines in documentation so they are sorted (cherry picked from commit 5147c49498c40165abe025b70b3996d1a3bb8a6b) Co-authored-by: Alex Groshev <38885591+haddystuff@users.noreply.github.com> --- .../6572-nmcli-add-support-loopback-type.yml | 2 + plugins/modules/nmcli.py | 5 +- tests/unit/plugins/modules/test_nmcli.py | 133 ++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/6572-nmcli-add-support-loopback-type.yml diff --git a/changelogs/fragments/6572-nmcli-add-support-loopback-type.yml b/changelogs/fragments/6572-nmcli-add-support-loopback-type.yml new file mode 100644 index 0000000000..4382851d68 --- /dev/null +++ b/changelogs/fragments/6572-nmcli-add-support-loopback-type.yml @@ -0,0 +1,2 @@ +minor_changes: + - nmcli - add support for new connection type ``loopback`` (https://github.com/ansible-collections/community.general/issues/6572). diff --git a/plugins/modules/nmcli.py b/plugins/modules/nmcli.py index d12a309bc6..47e2c8f049 100644 --- a/plugins/modules/nmcli.py +++ b/plugins/modules/nmcli.py @@ -63,6 +63,7 @@ options: - Type V(generic) is added in Ansible 2.5. - Type V(infiniband) is added in community.general 2.0.0. - Type V(gsm) is added in community.general 3.7.0. + - Type V(loopback) is added in community.general 8.1.0. - Type V(macvlan) is added in community.general 6.6.0. - Type V(wireguard) is added in community.general 4.3.0. - Type V(vpn) is added in community.general 5.1.0. @@ -70,7 +71,7 @@ options: - If you want to control non-ethernet connection attached to V(bond), V(bridge), or V(team) consider using O(slave_type) option. type: str choices: [ bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, gre, infiniband, ipip, macvlan, sit, team, team-slave, vlan, vxlan, - wifi, gsm, wireguard, vpn ] + wifi, gsm, wireguard, vpn, loopback ] mode: description: - This is the type of device or network connection that you wish to create for a bond or bridge. @@ -1938,6 +1939,7 @@ class Nmcli(object): 'macvlan', 'wireguard', 'vpn', + 'loopback', ) @property @@ -2421,6 +2423,7 @@ def main(): 'macvlan', 'wireguard', 'vpn', + 'loopback', ]), ip4=dict(type='list', elements='str'), gw4=dict(type='str'), diff --git a/tests/unit/plugins/modules/test_nmcli.py b/tests/unit/plugins/modules/test_nmcli.py index 3508f4d204..8c9c007ace 100644 --- a/tests/unit/plugins/modules/test_nmcli.py +++ b/tests/unit/plugins/modules/test_nmcli.py @@ -118,6 +118,12 @@ TESTCASE_CONNECTION = [ 'state': 'absent', '_ansible_check_mode': True, }, + { + 'type': 'loopback', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, ] TESTCASE_GENERIC = [ @@ -944,6 +950,28 @@ TESTCASE_ETHERNET_STATIC = [ } ] +TESTCASE_LOOPBACK = [ + { + 'type': 'loopback', + 'conn_name': 'lo', + 'ifname': 'lo', + 'ip4': '127.0.0.1/8', + 'state': 'present', + '_ansible_check_mode': False, + } +] + +TESTCASE_LOOPBACK_MODIFY = [ + { + 'type': 'loopback', + 'conn_name': 'lo', + 'ifname': 'lo', + 'ip4': ['127.0.0.1/8', '127.0.0.2/8'], + 'state': 'present', + '_ansible_check_mode': False, + } +] + TESTCASE_ETHERNET_STATIC_SHOW_OUTPUT = """\ connection.id: non_existent_nw_device connection.interface-name: ethernet_non_existant @@ -962,6 +990,21 @@ ipv6.ignore-auto-dns: no ipv6.ignore-auto-routes: no """ +TESTCASE_LOOPBACK_SHOW_OUTPUT = """\ +connection.id: lo +connection.interface-name: lo +connection.autoconnect: yes +ipv4.method: manual +ipv4.addresses: 127.0.0.1/8 +ipv4.ignore-auto-dns: no +ipv4.ignore-auto-routes: no +ipv4.never-default: no +ipv4.may-fail: yes +ipv6.method: manual +ipv6.ignore-auto-dns: no +ipv6.ignore-auto-routes: no +""" + TESTCASE_ETHERNET_STATIC_MULTIPLE_IP4_ADDRESSES = [ { 'type': 'ethernet', @@ -1946,6 +1989,24 @@ def mocked_generic_connection_diff_check(mocker): execute_return=(0, TESTCASE_GENERIC_SHOW_OUTPUT, "")) +@pytest.fixture +def mocked_loopback_connection_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_LOOPBACK_SHOW_OUTPUT, "")) + + +@pytest.fixture +def mocked_loopback_connection_modify(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=None, + execute_side_effect=( + (0, TESTCASE_LOOPBACK_SHOW_OUTPUT, ""), + (0, "", ""), + )) + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_BOND, indirect=['patch_ansible_module']) def test_bond_connection_create(mocked_generic_connection_create, capfd): """ @@ -4712,3 +4773,75 @@ def test_slave_type_team_unchanged(mocked_create_slave_type_team_unchanged, capf results = json.loads(out) assert not results.get('failed') assert not results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_LOOPBACK, indirect=['patch_ansible_module']) +def test_create_loopback(mocked_generic_connection_create, capfd): + """ + Test : Create loopback connection + """ + + 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] == 'loopback' + assert add_args[0][5] == 'con-name' + assert add_args[0][6] == 'lo' + + add_args_text = list(map(to_text, add_args[0])) + for param in ['connection.interface-name', 'lo', + 'ipv4.addresses', '127.0.0.1/8']: + 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_LOOPBACK, indirect=['patch_ansible_module']) +def test_unchanged_loopback(mocked_loopback_connection_unchanged, capfd): + """ + Test : loopback connection unchanged + """ + with pytest.raises(SystemExit): + nmcli.main() + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert not results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_LOOPBACK_MODIFY, indirect=['patch_ansible_module']) +def test_add_second_ip4_address_to_loopback_connection(mocked_loopback_connection_modify, capfd): + """ + Test : Modify loopback connection + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 2 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[1] + + assert args[0][0] == '/usr/bin/nmcli' + assert args[0][1] == 'con' + assert args[0][2] == 'modify' + assert args[0][3] == 'lo' + + for param in ['ipv4.addresses', '127.0.0.1/8,127.0.0.2/8']: + assert param in args[0] + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed']