From 3dba697e3353a9e9dac9bbfd4e0216d7b62b6c5f Mon Sep 17 00:00:00 2001 From: Reto Kupferschmid Date: Thu, 5 Aug 2021 14:25:42 +0200 Subject: [PATCH] nmcli: manage dummy connections (#3132) * manage dummy connections * add issue reference in changelog fragment * Update changelogs/fragments/3132-nmcli-dummy.yaml Co-authored-by: Ajpantuso * resolve test conflicts Co-authored-by: Ajpantuso --- changelogs/fragments/3132-nmcli-dummy.yaml | 2 + plugins/modules/net_tools/nmcli.py | 12 ++- .../plugins/modules/net_tools/test_nmcli.py | 102 ++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/3132-nmcli-dummy.yaml diff --git a/changelogs/fragments/3132-nmcli-dummy.yaml b/changelogs/fragments/3132-nmcli-dummy.yaml new file mode 100644 index 0000000000..970bda34e8 --- /dev/null +++ b/changelogs/fragments/3132-nmcli-dummy.yaml @@ -0,0 +1,2 @@ +minor_changes: + - nmcli - add ``dummy`` interface support (https://github.com/ansible-collections/community.general/issues/724). diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 90fd5bbd0c..bbc1b4770f 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -51,10 +51,11 @@ options: type: description: - This is the type of device or network connection that you wish to create or modify. + - Type C(dummy) is added in community.general 3.5.0. - Type C(generic) is added in Ansible 2.5. - Type C(infiniband) is added in community.general 2.0.0. type: str - choices: [ bond, bond-slave, bridge, bridge-slave, ethernet, generic, infiniband, ipip, sit, team, team-slave, vlan, vxlan, wifi ] + choices: [ bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, infiniband, ipip, sit, team, team-slave, vlan, vxlan, wifi ] mode: description: - This is the type of device or network connection that you wish to create for a bond or bridge. @@ -775,6 +776,8 @@ class Nmcli(object): if self.method4: self.ipv4_method = self.method4 + elif self.type == 'dummy' and not self.ip4: + self.ipv4_method = 'disabled' elif self.ip4: self.ipv4_method = 'manual' else: @@ -782,6 +785,8 @@ class Nmcli(object): if self.method6: self.ipv6_method = self.method6 + elif self.type == 'dummy' and not self.ip6: + self.ipv6_method = 'disabled' elif self.ip6: self.ipv6_method = 'manual' else: @@ -938,6 +943,7 @@ class Nmcli(object): return self.type in ( 'bond', 'bridge', + 'dummy', 'ethernet', 'generic', 'infiniband', @@ -956,6 +962,7 @@ class Nmcli(object): @property def mtu_conn_type(self): return self.type in ( + 'dummy', 'ethernet', 'team-slave', ) @@ -1092,7 +1099,7 @@ class Nmcli(object): @property def create_connection_up(self): - if self.type in ('bond', 'ethernet', 'infiniband', 'wifi'): + if self.type in ('bond', 'dummy', 'ethernet', 'infiniband', 'wifi'): if (self.mtu is not None) or (self.dns4 is not None) or (self.dns6 is not None): return True elif self.type == 'team': @@ -1218,6 +1225,7 @@ def main(): 'bond-slave', 'bridge', 'bridge-slave', + 'dummy', 'ethernet', 'generic', 'infiniband', diff --git a/tests/unit/plugins/modules/net_tools/test_nmcli.py b/tests/unit/plugins/modules/net_tools/test_nmcli.py index 6df320a0c7..b2307f245a 100644 --- a/tests/unit/plugins/modules/net_tools/test_nmcli.py +++ b/tests/unit/plugins/modules/net_tools/test_nmcli.py @@ -74,6 +74,12 @@ TESTCASE_CONNECTION = [ 'state': 'absent', '_ansible_check_mode': True, }, + { + 'type': 'dummy', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, ] TESTCASE_GENERIC = [ @@ -485,6 +491,40 @@ TESTCASE_WIRELESS = [ } ] +TESTCASE_DUMMY_STATIC = [ + { + 'type': 'dummy', + 'conn_name': 'non_existent_nw_device', + 'ifname': 'dummy_non_existant', + 'ip4': '10.10.10.10/24', + 'gw4': '10.10.10.1', + 'dns4': ['1.1.1.1', '8.8.8.8'], + 'ip6': '2001:db8::1/128', + 'state': 'present', + '_ansible_check_mode': False, + } +] + +TESTCASE_DUMMY_STATIC_SHOW_OUTPUT = """\ +connection.id: non_existent_nw_device +connection.interface-name: dummy_non_existant +connection.autoconnect: yes +802-3-ethernet.mtu: auto +ipv4.method: manual +ipv4.addresses: 10.10.10.10/24 +ipv4.gateway: 10.10.10.1 +ipv4.ignore-auto-dns: no +ipv4.ignore-auto-routes: no +ipv4.never-default: no +ipv4.may-fail: yes +ipv4.dns: 1.1.1.1,8.8.8.8 +ipv6.method: auto +ipv6.ignore-auto-dns: no +ipv6.ignore-auto-routes: no +ipv6.method: manual +ipv6.addresses: 2001:db8::1/128 +""" + def mocker_set(mocker, connection_exists=False, @@ -641,6 +681,13 @@ def mocked_ethernet_connection_dhcp_to_static(mocker): )) +@pytest.fixture +def mocked_dummy_connection_static_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_DUMMY_STATIC_SHOW_OUTPUT, "")) + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_BOND, indirect=['patch_ansible_module']) def test_bond_connection_create(mocked_generic_connection_create, capfd): """ @@ -1581,3 +1628,58 @@ def test_create_wireless(mocked_generic_connection_create, capfd): results = json.loads(out) assert not results.get('failed') assert results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_DUMMY_STATIC, indirect=['patch_ansible_module']) +def test_create_dummy_static(mocked_generic_connection_create, capfd): + """ + Test : Create dummy connection with static IP 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] == 'dummy' + 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', 'dummy_non_existant', + 'ipv4.addresses', '10.10.10.10/24', + 'ipv4.gateway', '10.10.10.1', + 'ipv4.dns', '1.1.1.1,8.8.8.8', + 'ipv6.addresses', '2001:db8::1/128']: + 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_DUMMY_STATIC, indirect=['patch_ansible_module']) +def test_dummy_connection_static_unchanged(mocked_dummy_connection_static_unchanged, capfd): + """ + Test : Dummy connection with static IP 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']