From 90c0980e8d7ba8c59904197ef976b2fb807d6857 Mon Sep 17 00:00:00 2001 From: Alex Groshev <38885591+haddystuff@users.noreply.github.com> Date: Thu, 9 Dec 2021 23:17:32 +0300 Subject: [PATCH] nmcli: adding ipv6 address list support (#3776) * rebase * Add changelog fragment * add suggestions * split PR into two * Add multiple address support but with #3768 fiexed * rebase * clean some merge artifacts * update the wording --- ...1088-add_multiple_ipv6_address_support.yml | 4 + ...8-nmcli_add_multiple_addresses_support.yml | 2 +- plugins/modules/net_tools/nmcli.py | 19 ++--- .../plugins/modules/net_tools/test_nmcli.py | 84 +++++++++++++++++++ 4 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 changelogs/fragments/1088-add_multiple_ipv6_address_support.yml diff --git a/changelogs/fragments/1088-add_multiple_ipv6_address_support.yml b/changelogs/fragments/1088-add_multiple_ipv6_address_support.yml new file mode 100644 index 0000000000..f55473fc50 --- /dev/null +++ b/changelogs/fragments/1088-add_multiple_ipv6_address_support.yml @@ -0,0 +1,4 @@ +--- +minor_changes: + - nmcli - add multiple addresses support for ``ip6`` parameter + (https://github.com/ansible-collections/community.general/issues/1088). diff --git a/changelogs/fragments/1088-nmcli_add_multiple_addresses_support.yml b/changelogs/fragments/1088-nmcli_add_multiple_addresses_support.yml index b78f7790f8..21c0278d26 100644 --- a/changelogs/fragments/1088-nmcli_add_multiple_addresses_support.yml +++ b/changelogs/fragments/1088-nmcli_add_multiple_addresses_support.yml @@ -1,4 +1,4 @@ --- minor_changes: - nmcli - add multiple addresses support for ``ip4`` parameter - (https://github.com/ansible-collections/community.general/issues/1088, https://github.com/ansible-collections/community.general/pull/3738). \ No newline at end of file + (https://github.com/ansible-collections/community.general/issues/1088, https://github.com/ansible-collections/community.general/pull/3738). diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 20a43eb0c5..61f2674372 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -143,10 +143,11 @@ options: version_added: 3.3.0 ip6: description: - - The IPv6 address to this interface. - - Use the format C(abbe::cafe/128 or abbe::cafe). + - List of IPv6 addresses to this interface. + - Use the format C(abbe::cafe/128) or C(abbe::cafe). - If defined and I(method6) is not specified, automatically set C(ipv6.method) to C(manual). - type: str + type: list + elements: str gw6: description: - The IPv6 gateway for this interface. @@ -1510,13 +1511,10 @@ class Nmcli(object): return [address if '/' in address else address + '/32' for address in ip4_addresses] @staticmethod - def enforce_ipv6_cidr_notation(ip6_address): - if ip6_address is None: + def enforce_ipv6_cidr_notation(ip6_addresses): + if ip6_addresses is None: return None - elif '/' in ip6_address: - return ip6_address - else: - return ip6_address + '/128' + return [address if '/' in address else address + '/128' for address in ip6_addresses] @staticmethod def bool_to_string(boolean): @@ -1543,6 +1541,7 @@ class Nmcli(object): '802-11-wireless.hidden'): return bool elif setting in ('ipv4.addresses', + 'ipv6.addresses', 'ipv4.dns', 'ipv4.dns-search', 'ipv4.routes', @@ -1846,7 +1845,7 @@ def main(): method4=dict(type='str', choices=['auto', 'link-local', 'manual', 'shared', 'disabled']), may_fail4=dict(type='bool', default=True), dhcp_client_id=dict(type='str'), - ip6=dict(type='str'), + ip6=dict(type='list', elements='str'), gw6=dict(type='str'), gw6_ignore_auto=dict(type='bool', default=False), dns6=dict(type='list', elements='str'), diff --git a/tests/unit/plugins/modules/net_tools/test_nmcli.py b/tests/unit/plugins/modules/net_tools/test_nmcli.py index 9a78d855a7..499ea1e5ad 100644 --- a/tests/unit/plugins/modules/net_tools/test_nmcli.py +++ b/tests/unit/plugins/modules/net_tools/test_nmcli.py @@ -601,6 +601,29 @@ TESTCASE_ETHERNET_STATIC_IP6_PRIVACY_AND_ADDR_GEN_MODE = [ } ] +TESTCASE_ETHERNET_STATIC_MULTIPLE_IP6_ADDRESSES = [ + { + 'type': 'ethernet', + 'conn_name': 'non_existent_nw_device', + 'ifname': 'ethernet_non_existant', + 'ip6': ['2001:db8::cafe/128', '2002:db8::cafe/128'], + 'gw6': '2001:db8::cafa', + 'dns6': ['2001:4860:4860::8888', '2001:4860:4860::8844'], + 'state': 'present', + '_ansible_check_mode': False, + }, + { + 'type': 'ethernet', + 'conn_name': 'non_existent_nw_device', + 'ifname': 'ethernet_non_existant', + 'ip6': ['2001:db8::cafe', '2002:db8::cafe'], + 'gw6': '2001:db8::cafa', + 'dns6': ['2001:4860:4860::8888', '2001:4860:4860::8844'], + 'state': 'present', + '_ansible_check_mode': False, + } +] + TESTCASE_ETHERNET_STATIC_MULTIPLE_IP4_ADDRESSES_SHOW_OUTPUT = """\ connection.id: non_existent_nw_device connection.interface-name: ethernet_non_existant @@ -1050,6 +1073,13 @@ def mocked_ethernet_connection_static_ip6_privacy_and_addr_gen_mode_unchange(moc execute_return=(0, TESTCASE_ETHERNET_STATIC_IP6_PRIVACY_AND_ADDR_GEN_MODE_UNCHANGED_OUTPUT, "")) +@pytest.fixture +def mocked_ethernet_connection_static_multiple_ip6_addresses_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_ETHERNET_STATIC_MULTIPLE_IP6_ADDRESSES_SHOW_OUTPUT, "")) + + @pytest.fixture def mocked_ethernet_connection_static_modify(mocker): mocker_set(mocker, @@ -2650,6 +2680,46 @@ def test_create_ethernet_with_mulitple_ip4_addresses_static(mocked_generic_conne assert results['changed'] +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_ETHERNET_STATIC_MULTIPLE_IP6_ADDRESSES, indirect=['patch_ansible_module']) +def test_create_ethernet_with_mulitple_ip6_addresses_static(mocked_generic_connection_create, capfd): + """ + Test : Create ethernet connection with multiple IPv6 addresses configuration + """ + + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 2 + 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', + 'ipv6.addresses', '2001:db8::cafe/128,2002:db8::cafe/128', + 'ipv6.gateway', '2001:db8::cafa', + 'ipv6.dns', '2001:4860:4860::8888,2001:4860:4860::8844']: + assert param in add_args_text + + up_args, up_kw = arg_list[1] + assert up_args[0][0] == '/usr/bin/nmcli' + assert up_args[0][1] == 'con' + assert up_args[0][2] == 'up' + assert up_args[0][3] == 'non_existent_nw_device' + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_ETHERNET_STATIC_MULTIPLE_IP4_ADDRESSES, indirect=['patch_ansible_module']) def test_ethernet_connection_static_with_mulitple_ip4_addresses_unchanged(mocked_ethernet_connection_static_multiple_ip4_addresses_unchanged, capfd): """ @@ -2664,6 +2734,20 @@ def test_ethernet_connection_static_with_mulitple_ip4_addresses_unchanged(mocked assert not results['changed'] +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_ETHERNET_STATIC_MULTIPLE_IP6_ADDRESSES, indirect=['patch_ansible_module']) +def test_ethernet_connection_static_with_mulitple_ip6_addresses_unchanged(mocked_ethernet_connection_static_multiple_ip6_addresses_unchanged, capfd): + """ + Test : Ethernet connection with multiple IPv6 addresses configuration 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_ETHERNET_STATIC_MULTIPLE_IP4_ADDRESSES, indirect=['patch_ansible_module']) def test_add_second_ip4_address_to_ethernet_connection(mocked_ethernet_connection_static_modify, capfd): """