Repository: ansible-collections/community.general

Issue:
nmcli always returning "changed"
### Summary

community.general.nmcli always returning "changed" when running task with dummy interface even though there is nothing to change. Found this problem on Ubuntu 20.04 controller node but also was able to reproduce it on Fedora 34. I ran this task with --check --diff flags and found out that nmcli trying to change "802-3-ethernet.mtu" parameter. I also found similar issue [here](https://github.com/ansible-collections/community.general/issues/3216) but it's not clear if this problem is the same. 

### Issue Type

Bug Report

### Component Name

nmcli
community.general : 3.8.0

### Ansible Version

```console
ansible [core 2.11.6] 
  config file = /home/alex/ansible/ansible.cfg
  configured module search path = ['/home/alex/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/alex/.virtualenvs/latest_ansible/lib64/python3.9/site-packages/ansible
  ansible collection location = /home/alex/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/alex/.virtualenvs/latest_ansible/bin/ansible
  python version = 3.9.7 (default, Aug 30 2021, 00:00:00) [GCC 11.2.1 20210728 (Red Hat 11.2.1-1)]
  jinja version = 3.0.2
  libyaml = True
```


### Community.general Version

```console
Collection        Version
----------------- -------
community.general 3.8.0  

```


### Configuration

```console
DEFAULT_BECOME(/home/alex/ansible/ansible.cfg) = False
DEFAULT_BECOME_METHOD(/home/alex/ansible/ansible.cfg) = sudo
DEFAULT_HOST_LIST(/home/alex/ansible/ansible.cfg) = ['/home/alex/ansible/hosts.ini']
DEFAULT_REMOTE_USER(/home/alex/ansible/ansible.cfg) = ansible
HOST_KEY_CHECKING(/home/alex/ansible/ansible.cfg) = False
```


### OS / Environment

Controller Node : Ubuntu 20.04, Fedora 34
Target Node : RHEL 8.3, Fedora 34, Ubuntu 20.04

### Steps to Reproduce


```yaml (paste below)
- name: nmcli set interface
  community.general.nmcli:
    conn_name: dummy_con0
    ifname: dummy0
    ip4: 10.0.0.151/32
    state: present
    type: dummy
```


### Expected Results

I expect to recive something this after running playbook
```console
TASK [nmcli set interface] ****************************************************************************************************
OK: [redhat]
OK: [personal]
```

### Actual Results

Got this result
```console
TASK [nmcli set interface] ****************************************************************************************************
changed: [redhat]
changed: [personal]
```
so i reran this playbook with --diff --check and got this
```console
TASK [nmcli set interface] *********************************************************************************************************************************************************************
--- before
+++ after
@@ -1,5 +1,5 @@
 {
-    "802-3-ethernet.mtu": null,
+    "802-3-ethernet.mtu": "auto",
     "connection.autoconnect": "yes",
     "connection.interface-name": "dummy0",
     "ipv4.addresses": "10.0.0.151/32",

changed: [redhat]
--- before
+++ after
@@ -1,5 +1,5 @@
 {
-    "802-3-ethernet.mtu": null,
+    "802-3-ethernet.mtu": "auto",
     "connection.autoconnect": "yes",
     "connection.interface-name": "dummy0",
     "ipv4.addresses": "10.0.0.151/32",

changed: [personal]
```

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct
nmcli always returning "changed"
### Summary

community.general.nmcli always returning "changed" when running task with dummy interface even though there is nothing to change. Found this problem on Ubuntu 20.04 controller node but also was able to reproduce it on Fedora 34. I ran this task with --check --diff flags and found out that nmcli trying to change "802-3-ethernet.mtu" parameter. I also found similar issue [here](https://github.com/ansible-collections/community.general/issues/3216) but it's not clear if this problem is the same. 

### Issue Type

Bug Report

### Component Name

nmcli
community.general : 3.8.0

### Ansible Version

```console
ansible [core 2.11.6] 
  config file = /home/alex/ansible/ansible.cfg
  configured module search path = ['/home/alex/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/alex/.virtualenvs/latest_ansible/lib64/python3.9/site-packages/ansible
  ansible collection location = /home/alex/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/alex/.virtualenvs/latest_ansible/bin/ansible
  python version = 3.9.7 (default, Aug 30 2021, 00:00:00) [GCC 11.2.1 20210728 (Red Hat 11.2.1-1)]
  jinja version = 3.0.2
  libyaml = True
```


### Community.general Version

```console
Collection        Version
----------------- -------
community.general 3.8.0  

```


### Configuration

```console
DEFAULT_BECOME(/home/alex/ansible/ansible.cfg) = False
DEFAULT_BECOME_METHOD(/home/alex/ansible/ansible.cfg) = sudo
DEFAULT_HOST_LIST(/home/alex/ansible/ansible.cfg) = ['/home/alex/ansible/hosts.ini']
DEFAULT_REMOTE_USER(/home/alex/ansible/ansible.cfg) = ansible
HOST_KEY_CHECKING(/home/alex/ansible/ansible.cfg) = False
```


### OS / Environment

Controller Node : Ubuntu 20.04, Fedora 34
Target Node : RHEL 8.3, Fedora 34, Ubuntu 20.04

### Steps to Reproduce


```yaml (paste below)
- name: nmcli set interface
  community.general.nmcli:
    conn_name: dummy_con0
    ifname: dummy0
    ip4: 10.0.0.151/32
    state: present
    type: dummy
```


### Expected Results

I expect to recive something this after running playbook
```console
TASK [nmcli set interface] ****************************************************************************************************
OK: [redhat]
OK: [personal]
```

### Actual Results

Got this result
```console
TASK [nmcli set interface] ****************************************************************************************************
changed: [redhat]
changed: [personal]
```
so i reran this playbook with --diff --check and got this
```console
TASK [nmcli set interface] *********************************************************************************************************************************************************************
--- before
+++ after
@@ -1,5 +1,5 @@
 {
-    "802-3-ethernet.mtu": null,
+    "802-3-ethernet.mtu": "auto",
     "connection.autoconnect": "yes",
     "connection.interface-name": "dummy0",
     "ipv4.addresses": "10.0.0.151/32",

changed: [redhat]
--- before
+++ after
@@ -1,5 +1,5 @@
 {
-    "802-3-ethernet.mtu": null,
+    "802-3-ethernet.mtu": "auto",
     "connection.autoconnect": "yes",
     "connection.interface-name": "dummy0",
     "ipv4.addresses": "10.0.0.151/32",

changed: [personal]
```

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/net_tools/nmcli.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/net_tools/nmcli.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @alcamie101
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Files identified in the description:
* [`plugins/modules/net_tools/nmcli.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/net_tools/nmcli.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @alcamie101
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py
--- a/plugins/modules/net_tools/nmcli.py
+++ b/plugins/modules/net_tools/nmcli.py
@@ -1695,6 +1695,8 @@ def _compare_conn_params(self, conn_info, options):
                     # Depending on version nmcli adds double-qoutes to gsm.apn
                     # Need to strip them in order to compare both
                     current_value = current_value.strip('"')
+                if key == self.mtu_setting and self.mtu is None:
+                    self.mtu = 0
             else:
                 # parameter does not exist
                 current_value = None
@@ -1703,6 +1705,8 @@ def _compare_conn_params(self, conn_info, options):
                 # compare values between two lists
                 if sorted(current_value) != sorted(value):
                     changed = True
+            elif all([key == self.mtu_setting, self.type == 'dummy', current_value is None, value == 'auto', self.mtu is None]):
+                value = None
             else:
                 if current_value != to_text(value):
                     changed = True


Test patch:
diff --git a/tests/unit/plugins/modules/net_tools/test_nmcli.py b/tests/unit/plugins/modules/net_tools/test_nmcli.py
--- a/tests/unit/plugins/modules/net_tools/test_nmcli.py
+++ b/tests/unit/plugins/modules/net_tools/test_nmcli.py
@@ -675,6 +675,45 @@
 ipv6.addresses:                         2001:db8::1/128
 """
 
+TESTCASE_DUMMY_STATIC_WITHOUT_MTU_SHOW_OUTPUT = """\
+connection.id:                          non_existent_nw_device
+connection.interface-name:              dummy_non_existant
+connection.autoconnect:                 yes
+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
+"""
+
+TESTCASE_DUMMY_STATIC_WITH_CUSTOM_MTU_SHOW_OUTPUT = """\
+connection.id:                          non_existent_nw_device
+connection.interface-name:              dummy_non_existant
+connection.autoconnect:                 yes
+802-3-ethernet.mtu:                     1500
+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
+"""
+
 
 TESTCASE_GSM = [
     {
@@ -955,6 +994,24 @@ def mocked_dummy_connection_static_unchanged(mocker):
                execute_return=(0, TESTCASE_DUMMY_STATIC_SHOW_OUTPUT, ""))
 
 
+@pytest.fixture
+def mocked_dummy_connection_static_without_mtu_unchanged(mocker):
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_DUMMY_STATIC_WITHOUT_MTU_SHOW_OUTPUT, ""))
+
+
+@pytest.fixture
+def mocked_dummy_connection_static_with_custom_mtu_modify(mocker):
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=None,
+               execute_side_effect=(
+                   (0, TESTCASE_DUMMY_STATIC_WITH_CUSTOM_MTU_SHOW_OUTPUT, ""),
+                   (0, "", ""),
+               ))
+
+
 @pytest.fixture
 def mocked_gsm_connection_unchanged(mocker):
     mocker_set(mocker,
@@ -2283,6 +2340,48 @@ def test_dummy_connection_static_unchanged(mocked_dummy_connection_static_unchan
     assert not results['changed']
 
 
+@pytest.mark.parametrize('patch_ansible_module', TESTCASE_DUMMY_STATIC, indirect=['patch_ansible_module'])
+def test_dummy_connection_static_without_mtu_unchanged(mocked_dummy_connection_static_without_mtu_unchanged, capfd):
+    """
+    Test : Dummy connection with static IP configuration and no mtu set 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_DUMMY_STATIC, indirect=['patch_ansible_module'])
+def test_dummy_connection_static_with_custom_mtu_modify(mocked_dummy_connection_static_with_custom_mtu_modify, capfd):
+    """
+    Test : Dummy connection with static IP configuration and no mtu set modify
+    """
+    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] == 'non_existent_nw_device'
+
+    args_text = list(map(to_text, args[0]))
+    for param in ['802-3-ethernet.mtu', '0']:
+        assert param in 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_GSM, indirect=['patch_ansible_module'])
 def test_create_gsm(mocked_generic_connection_create, capfd):
     """