Repository: ansible-collections/community.general

Issue:
nmcli: remove Python DBus and GTK Object library dependencies
##### SUMMARY

The DBus and GObject Python bindings are unnecessary dependencies for the `nmcli` module.  In particular, the DBus-related code is *only* used to [list nmcli connections](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L751-L762) to determine if a [connection exists](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L794).  As far as I can tell, the following `Nmcli` methods/properties are unused and superflous:

* [`merge_secrets`](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L699-L710), [`dict_to_string`](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L712-L733), [`connection_to_string`](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L735-L740): only used by [`list_connection_info`](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L749-L787), which in turn is only used by [`connection_exists`](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L791).
* [`DEVTYPES`](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L596-L613), [`STATES`](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L614-L628): Unused DBus type mappings.

All other connection creation, modification and determining changes (`Nmcli.show_connection`) are done executing `nmcli`.  DBus-related code can have its functionality supplanted completely with:

```python
def connection_exists(self):
    cmd = [self.nmcli_bin, 'con', 'show', self.conn_name]
    (rc, out, err) = self.execute_command(cmd)
    if rc == 0:
        return True
    elif rc == 10:
        return False
    else:
        raise NmcliModuleError(err)
```

It's simple: `nmcli` will exit with 10 if the connection doesn't exist (as already noted in the [module's docstring](https://github.com/ansible-collections/community.general/blob/e48083e66b3b2fa0695b6fc71c4fb33247a79a4a/plugins/modules/net_tools/nmcli.py#L537)).  No sense in carrying dead code and dependencies.

##### ISSUE TYPE
- Feature Idea

##### COMPONENT NAME

`nmcli`

##### ADDITIONAL INFORMATION

N/A



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

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @alcamie101 @nerzhul
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/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
@@ -15,21 +15,13 @@
 - Chris Long (@alcamie101)
 short_description: Manage Networking
 requirements:
-- dbus
-- NetworkManager-libnm (or NetworkManager-glib on older systems)
 - nmcli
 description:
-    - Manage the network devices. Create, modify and manage various connection and device type e.g., ethernet, teams, bonds, vlans etc.
-    - 'On CentOS 8 and Fedora >=29 like systems, the requirements can be met by installing the following packages: NetworkManager-libnm,
-      python3-libsemanage, python3-policycoreutils.'
-    - 'On CentOS 7 and Fedora <=28 like systems, the requirements can be met by installing the following packages: NetworkManager-glib,
-      libnm-qt-devel.x86_64, nm-connection-editor.x86_64, python-libsemanage, python-policycoreutils.'
-    - 'On Ubuntu and Debian like systems, the requirements can be met by installing the following packages: network-manager,
-      python-dbus (or python3-dbus, depending on the Python version in use), libnm-dev.'
-    - 'On older Ubuntu and Debian like systems, the requirements can be met by installing the following packages: network-manager,
-      python-dbus (or python3-dbus, depending on the Python version in use), libnm-glib-dev.'
-    - 'On openSUSE, the requirements can be met by installing the following packages: NetworkManager, python2-dbus-python (or
-      python3-dbus-python), typelib-1_0-NMClient-1_0 and typelib-1_0-NetworkManager-1_0.'
+    - 'Manage the network devices. Create, modify and manage various connection and device type e.g., ethernet, teams, bonds, vlans etc.'
+    - 'On CentOS 8 and Fedora >=29 like systems, the requirements can be met by installing the following packages: NetworkManager.'
+    - 'On CentOS 7 and Fedora <=28 like systems, the requirements can be met by installing the following packages: NetworkManager-tui.'
+    - 'On Ubuntu and Debian like systems, the requirements can be met by installing the following packages: network-manager'
+    - 'On openSUSE, the requirements can be met by installing the following packages: NetworkManager.'
 options:
     state:
         description:
@@ -86,10 +78,12 @@
         description:
             - A list of up to 3 dns servers.
             - IPv4 format e.g. to add two IPv4 DNS server addresses, use C(192.0.2.53 198.51.100.53).
+        elements: str
         type: list
     dns4_search:
         description:
             - A list of DNS search domains.
+        elements: str
         type: list
     ip6:
         description:
@@ -105,10 +99,12 @@
         description:
             - A list of up to 3 dns servers.
             - IPv6 format e.g. to add two IPv6 DNS server addresses, use C(2001:4860:4860::8888 2001:4860:4860::8844).
+        elements: str
         type: list
     dns6_search:
         description:
             - A list of DNS search domains.
+        elements: str
         type: list
     mtu:
         description:
@@ -179,6 +175,7 @@
         description:
             - This is only used with bridge - MAC address of the bridge.
             - Note this requires a recent kernel feature, originally introduced in 3.15 upstream kernel.
+        type: str
     slavepriority:
         description:
             - This is only used with 'bridge-slave' - [<0-63>] - STP priority of this slave.
@@ -540,34 +537,8 @@
 RETURN = r"""#
 """
 
-import traceback
-
-DBUS_IMP_ERR = None
-try:
-    import dbus
-    HAVE_DBUS = True
-except ImportError:
-    DBUS_IMP_ERR = traceback.format_exc()
-    HAVE_DBUS = False
-
-NM_CLIENT_IMP_ERR = None
-HAVE_NM_CLIENT = True
-try:
-    import gi
-    gi.require_version('NM', '1.0')
-    from gi.repository import NM
-except (ImportError, ValueError):
-    try:
-        import gi
-        gi.require_version('NMClient', '1.0')
-        gi.require_version('NetworkManager', '1.0')
-        from gi.repository import NetworkManager, NMClient
-    except (ImportError, ValueError):
-        NM_CLIENT_IMP_ERR = traceback.format_exc()
-        HAVE_NM_CLIENT = False
-
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils._text import to_native, to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_text
 import re
 
 
@@ -590,42 +561,6 @@ class Nmcli(object):
 
     platform = 'Generic'
     distribution = None
-    if HAVE_DBUS:
-        bus = dbus.SystemBus()
-    # The following is going to be used in dbus code
-    DEVTYPES = {
-        1: "Ethernet",
-        2: "Wi-Fi",
-        5: "Bluetooth",
-        6: "OLPC",
-        7: "WiMAX",
-        8: "Modem",
-        9: "InfiniBand",
-        10: "Bond",
-        11: "VLAN",
-        12: "ADSL",
-        13: "Bridge",
-        14: "Generic",
-        15: "Team",
-        16: "VxLan",
-        17: "ipip",
-        18: "sit",
-    }
-    STATES = {
-        0: "Unknown",
-        10: "Unmanaged",
-        20: "Unavailable",
-        30: "Disconnected",
-        40: "Prepare",
-        50: "Config",
-        60: "Need Auth",
-        70: "IP Config",
-        80: "IP Check",
-        90: "Secondaries",
-        100: "Activated",
-        110: "Deactivating",
-        120: "Failed"
-    }
 
     def __init__(self, module):
         self.module = module
@@ -637,12 +572,12 @@ def __init__(self, module):
         self.type = module.params['type']
         self.ip4 = module.params['ip4']
         self.gw4 = module.params['gw4']
-        self.dns4 = ' '.join(module.params['dns4']) if module.params.get('dns4') else None
-        self.dns4_search = ' '.join(module.params['dns4_search']) if module.params.get('dns4_search') else None
+        self.dns4 = module.params['dns4']
+        self.dns4_search = module.params['dns4_search']
         self.ip6 = module.params['ip6']
         self.gw6 = module.params['gw6']
-        self.dns6 = ' '.join(module.params['dns6']) if module.params.get('dns6') else None
-        self.dns6_search = ' '.join(module.params['dns6_search']) if module.params.get('dns6_search') else None
+        self.dns6 = module.params['dns6']
+        self.dns6_search = module.params['dns6_search']
         self.mtu = module.params['mtu']
         self.stp = module.params['stp']
         self.priority = module.params['priority']
@@ -696,48 +631,164 @@ def execute_command(self, cmd, use_unsafe_shell=False, data=None):
             cmd = to_text(cmd)
         return self.module.run_command(cmd, use_unsafe_shell=use_unsafe_shell, data=data)
 
-    def merge_secrets(self, proxy, config, setting_name):
-        try:
-            # returns a dict of dicts mapping name::setting, where setting is a dict
-            # mapping key::value.  Each member of the 'setting' dict is a secret
-            secrets = proxy.GetSecrets(setting_name)
-
-            # Copy the secrets into our connection config
-            for setting in secrets:
-                for key in secrets[setting]:
-                    config[setting_name][key] = secrets[setting][key]
-        except Exception:
-            pass
-
-    def dict_to_string(self, d):
-        # Try to trivially translate a dictionary's elements into nice string
-        # formatting.
-        dstr = ""
-        for key in d:
-            val = d[key]
-            str_val = ""
-            add_string = True
-            if isinstance(val, dbus.Array):
-                for elt in val:
-                    if isinstance(elt, dbus.Byte):
-                        str_val += "%s " % int(elt)
-                    elif isinstance(elt, dbus.String):
-                        str_val += "%s" % elt
-            elif isinstance(val, dbus.Dictionary):
-                dstr += self.dict_to_string(val)
-                add_string = False
-            else:
-                str_val = val
-            if add_string:
-                dstr += "%s: %s\n" % (key, str_val)
-        return dstr
-
-    def connection_to_string(self, config):
-        # dump a connection configuration to use in list_connection_info
-        setting_list = []
-        for setting_name in config:
-            setting_list.append(self.dict_to_string(config[setting_name]))
-        return setting_list
+    def connection_options(self, detect_change=False):
+        # Options common to multiple connection types.
+        options = {
+            'connection.autoconnect': self.autoconnect,
+        }
+
+        # IP address options.
+        if self.ip_conn_type:
+            options.update({
+                'ipv4.addresses': self.ip4,
+                'ipv4.dhcp-client-id': self.dhcp_client_id,
+                'ipv4.dns': self.dns4,
+                'ipv4.dns-search': self.dns4_search,
+                'ipv4.gateway': self.gw4,
+                'ipv4.method': self.ipv4_method,
+                'ipv6.addresses': self.ip6,
+                'ipv6.dns': self.dns6,
+                'ipv6.dns-search': self.dns6_search,
+                'ipv6.gateway': self.gw6,
+                'ipv6.method': self.ipv6_method,
+            })
+
+        # Layer 2 options.
+        if self.mac_conn_type:
+            options.update({self.mac_setting: self.mac})
+
+        if self.mtu_conn_type:
+            options.update({self.mtu_setting: self.mtu})
+
+        # Connections that can have a master.
+        if self.slave_conn_type:
+            options.update({
+                'connection.master': self.master,
+            })
+
+        # Options specific to a connection type.
+        if self.type == 'bond':
+            options.update({
+                'arp-interval': self.arp_interval,
+                'arp-ip-target': self.arp_ip_target,
+                'downdelay': self.downdelay,
+                'miimon': self.miimon,
+                'mode': self.mode,
+                'primary': self.primary,
+                'updelay': self.updelay,
+            })
+        elif self.type == 'bridge':
+            options.update({
+                'bridge.ageing-time': self.ageingtime,
+                'bridge.forward-delay': self.forwarddelay,
+                'bridge.hello-time': self.hellotime,
+                'bridge.max-age': self.maxage,
+                'bridge.priority': self.priority,
+                'bridge.stp': self.stp,
+            })
+        elif self.type == 'bridge-slave':
+            options.update({
+                'bridge-port.path-cost': self.path_cost,
+                'bridge-port.hairpin-mode': self.hairpin,
+                'bridge-port.priority': self.slavepriority,
+            })
+        elif self.tunnel_conn_type:
+            options.update({
+                'ip-tunnel.local': self.ip_tunnel_local,
+                'ip-tunnel.mode': self.type,
+                'ip-tunnel.parent': self.ip_tunnel_dev,
+                'ip-tunnel.remote': self.ip_tunnel_remote,
+            })
+        elif self.type == 'vlan':
+            options.update({
+                'vlan.id': self.vlanid,
+                'vlan.parent': self.vlandev,
+            })
+        elif self.type == 'vxlan':
+            options.update({
+                'vxlan.id': self.vxlan_id,
+                'vxlan.local': self.vxlan_local,
+                'vxlan.remote': self.vxlan_remote,
+            })
+
+        # Convert settings values based on the situation.
+        for setting, value in options.items():
+            setting_type = self.settings_type(setting)
+            convert_func = None
+            if setting_type is bool:
+                # Convert all bool options to yes/no.
+                convert_func = self.bool_to_string
+            if detect_change:
+                if setting in ('vlan.id', 'vxlan.id'):
+                    # Convert VLAN/VXLAN IDs to text when detecting changes.
+                    convert_func = to_text
+                elif setting == self.mtu_setting:
+                    # MTU is 'auto' by default when detecting changes.
+                    convert_func = self.mtu_to_string
+            elif setting_type is list:
+                # Convert lists to strings for nmcli create/modify commands.
+                convert_func = self.list_to_string
+
+            if callable(convert_func):
+                options[setting] = convert_func(options[setting])
+
+        return options
+
+    @property
+    def ip_conn_type(self):
+        return self.type in (
+            'bond',
+            'bridge',
+            'bridge-slave',
+            'ethernet',
+            'generic',
+            'team',
+            'vlan',
+        )
+
+    @property
+    def mac_conn_type(self):
+        return self.type == 'bridge'
+
+    @property
+    def mac_setting(self):
+        if self.type == 'bridge':
+            return 'bridge.mac-address'
+        else:
+            return '802-3-ethernet.cloned-mac-address'
+
+    @property
+    def mtu_conn_type(self):
+        return self.type in (
+            'ethernet',
+            'team-slave',
+        )
+
+    @property
+    def mtu_setting(self):
+        return '802-3-ethernet.mtu'
+
+    @staticmethod
+    def mtu_to_string(mtu):
+        if not mtu:
+            return 'auto'
+        else:
+            return to_text(mtu)
+
+    @property
+    def slave_conn_type(self):
+        return self.type in (
+            'bond-slave',
+            'bridge-slave',
+            'team-slave',
+        )
+
+    @property
+    def tunnel_conn_type(self):
+        return self.type in (
+            'ipip',
+            'sit',
+        )
 
     @staticmethod
     def bool_to_string(boolean):
@@ -746,53 +797,32 @@ def bool_to_string(boolean):
         else:
             return "no"
 
+    @staticmethod
+    def list_to_string(lst):
+        return ",".join(lst or [""])
+
+    @staticmethod
+    def settings_type(setting):
+        if setting in ('bridge.stp',
+                       'bridge-port.hairpin-mode',
+                       'connection.autoconnect'):
+            return bool
+        elif setting in ('ipv4.dns',
+                         'ipv4.dns-search',
+                         'ipv6.dns',
+                         'ipv6.dns-search'):
+            return list
+        return str
+
     def list_connection_info(self):
-        # Ask the settings service for the list of connections it provides
-        bus = dbus.SystemBus()
-
-        service_name = "org.freedesktop.NetworkManager"
-        settings = None
-        try:
-            proxy = bus.get_object(service_name, "/org/freedesktop/NetworkManager/Settings")
-            settings = dbus.Interface(proxy, "org.freedesktop.NetworkManager.Settings")
-        except dbus.exceptions.DBusException as e:
-            self.module.fail_json(msg="Unable to read Network Manager settings from DBus system bus: %s" % to_native(e),
-                                  details="Please check if NetworkManager is installed and"
-                                          "service network-manager is started.")
-        connection_paths = settings.ListConnections()
-        connection_list = []
-        # List each connection's name, UUID, and type
-        for path in connection_paths:
-            con_proxy = bus.get_object(service_name, path)
-            settings_connection = dbus.Interface(con_proxy, "org.freedesktop.NetworkManager.Settings.Connection")
-            config = settings_connection.GetSettings()
-
-            # Now get secrets too; we grab the secrets for each type of connection
-            # (since there isn't a "get all secrets" call because most of the time
-            # you only need 'wifi' secrets or '802.1x' secrets, not everything) and
-            # merge that into the configuration data - To use at a later stage
-            self.merge_secrets(settings_connection, config, '802-11-wireless')
-            self.merge_secrets(settings_connection, config, '802-11-wireless-security')
-            self.merge_secrets(settings_connection, config, '802-1x')
-            self.merge_secrets(settings_connection, config, 'gsm')
-            self.merge_secrets(settings_connection, config, 'cdma')
-            self.merge_secrets(settings_connection, config, 'ppp')
-
-            # Get the details of the 'connection' setting
-            s_con = config['connection']
-            connection_list.append(s_con['id'])
-            connection_list.append(s_con['uuid'])
-            connection_list.append(s_con['type'])
-            connection_list.append(self.connection_to_string(config))
-        return connection_list
+        cmd = [self.nmcli_bin, '--fields', 'name', '--terse', 'con', 'show']
+        (rc, out, err) = self.execute_command(cmd)
+        if rc != 0:
+            raise NmcliModuleError(err)
+        return out.splitlines()
 
     def connection_exists(self):
-        # we are going to use name and type in this instance to find if that connection exists and is of type x
-        connections = self.list_connection_info()
-
-        for con_item in connections:
-            if self.conn_name == con_item:
-                return True
+        return self.conn_name in self.list_connection_info()
 
     def down_connection(self):
         cmd = [self.nmcli_bin, 'con', 'down', self.conn_name]
@@ -802,615 +832,54 @@ def up_connection(self):
         cmd = [self.nmcli_bin, 'con', 'up', self.conn_name]
         return self.execute_command(cmd)
 
-    def create_connection_team(self):
-        cmd = [self.nmcli_bin, 'con', 'add', 'type', 'team', 'con-name']
-        # format for creating team interface
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-
-        options = {
-            'ip4': self.ip4,
-            'gw4': self.gw4,
-            'ip6': self.ip6,
-            'gw6': self.gw6,
-            'autoconnect': self.bool_to_string(self.autoconnect),
-            'ipv4.dns-search': self.dns4_search,
-            'ipv6.dns-search': self.dns6_search,
-            'ipv4.dhcp-client-id': self.dhcp_client_id,
-        }
-
-        for key, value in options.items():
-            if value is not None:
-                cmd.extend([key, value])
-
-        return cmd
-
-    def modify_connection_team(self):
-        cmd = [self.nmcli_bin, 'con', 'mod', self.conn_name]
-        options = {
-            'ipv4.method': self.ipv4_method,
-            'ipv4.address': self.ip4,
-            'ipv4.gateway': self.gw4,
-            'ipv4.dns': self.dns4,
-            'ipv6.method': self.ipv6_method,
-            'ipv6.address': self.ip6,
-            'ipv6.gateway': self.gw6,
-            'ipv6.dns': self.dns6,
-            'autoconnect': self.bool_to_string(self.autoconnect),
-            'ipv4.dns-search': self.dns4_search,
-            'ipv6.dns-search': self.dns6_search,
-            'ipv4.dhcp-client-id': self.dhcp_client_id,
-        }
-
-        for key, value in options.items():
-            if value is not None:
-                cmd.extend([key, value])
-
-        return cmd
-
-    def create_connection_team_slave(self):
-        cmd = [self.nmcli_bin, 'connection', 'add', 'type', self.type, 'con-name']
-        # format for creating team-slave interface
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-        cmd.append('master')
-        if self.conn_name is not None:
-            cmd.append(self.master)
-        return cmd
-
-    def modify_connection_team_slave(self):
-        cmd = [self.nmcli_bin, 'con', 'mod', self.conn_name, 'connection.master', self.master]
-        # format for modifying team-slave interface
-        if self.mtu is not None:
-            cmd.extend(['802-3-ethernet.mtu', self.mtu])
-        return cmd
-
-    def create_connection_bond(self):
-        cmd = [self.nmcli_bin, 'con', 'add', 'type', 'bond', 'con-name']
-        # format for creating bond interface
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-        options = {
-            'mode': self.mode,
-            'ip4': self.ip4,
-            'gw4': self.gw4,
-            'ip6': self.ip6,
-            'gw6': self.gw6,
-            'autoconnect': self.bool_to_string(self.autoconnect),
-            'ipv4.dns-search': self.dns4_search,
-            'ipv6.dns-search': self.dns6_search,
-            'miimon': self.miimon,
-            'downdelay': self.downdelay,
-            'updelay': self.updelay,
-            'arp-interval': self.arp_interval,
-            'arp-ip-target': self.arp_ip_target,
-            'primary': self.primary,
-            'ipv4.dhcp-client-id': self.dhcp_client_id,
-        }
-
-        for key, value in options.items():
-            if value is not None:
-                cmd.extend([key, value])
-        return cmd
-
-    def modify_connection_bond(self):
-        cmd = [self.nmcli_bin, 'con', 'mod', self.conn_name]
-        # format for modifying bond interface
-
-        options = {
-            'ipv4.method': self.ipv4_method,
-            'ipv4.address': self.ip4,
-            'ipv4.gateway': self.gw4,
-            'ipv4.dns': self.dns4,
-            'ipv6.method': self.ipv6_method,
-            'ipv6.address': self.ip6,
-            'ipv6.gateway': self.gw6,
-            'ipv6.dns': self.dns6,
-            'autoconnect': self.bool_to_string(self.autoconnect),
-            'ipv4.dns-search': self.dns4_search,
-            'ipv6.dns-search': self.dns6_search,
-            'miimon': self.miimon,
-            'downdelay': self.downdelay,
-            'updelay': self.updelay,
-            'arp-interval': self.arp_interval,
-            'arp-ip-target': self.arp_ip_target,
-            'ipv4.dhcp-client-id': self.dhcp_client_id,
-        }
-
-        for key, value in options.items():
-            if value is not None:
-                cmd.extend([key, value])
-
-        return cmd
-
-    def create_connection_bond_slave(self):
-        cmd = [self.nmcli_bin, 'connection', 'add', 'type', 'bond-slave', 'con-name']
-        # format for creating bond-slave interface
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-        cmd.append('master')
-        if self.conn_name is not None:
-            cmd.append(self.master)
-        return cmd
-
-    def modify_connection_bond_slave(self):
-        cmd = [self.nmcli_bin, 'con', 'mod', self.conn_name, 'connection.master', self.master]
-        # format for modifying bond-slave interface
-        return cmd
-
-    def create_connection_ethernet(self, conn_type='ethernet'):
-        # format for creating ethernet interface
-        # To add an Ethernet connection with static IP configuration, issue a command as follows
-        # - community.general.nmcli: name=add conn_name=my-eth1 ifname=eth1 type=ethernet ip4=192.0.2.100/24 gw4=192.0.2.1 state=present
-        # nmcli con add con-name my-eth1 ifname eth1 type ethernet ip4 192.0.2.100/24 gw4 192.0.2.1
-        cmd = [self.nmcli_bin, 'con', 'add', 'type']
-        if conn_type == 'ethernet':
-            cmd.append('ethernet')
-        elif conn_type == 'generic':
-            cmd.append('generic')
-        cmd.append('con-name')
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-
-        options = {
-            'ip4': self.ip4,
-            'gw4': self.gw4,
-            'ip6': self.ip6,
-            'gw6': self.gw6,
-            'autoconnect': self.bool_to_string(self.autoconnect),
-            'ipv4.dns-search': self.dns4_search,
-            'ipv6.dns-search': self.dns6_search,
-            'ipv4.dhcp-client-id': self.dhcp_client_id,
-        }
-
-        for key, value in options.items():
-            if value is not None:
-                cmd.extend([key, value])
-
-        return cmd
-
-    def modify_connection_ethernet(self, conn_type='ethernet'):
-        cmd = [self.nmcli_bin, 'con', 'mod', self.conn_name]
-        # format for modifying ethernet interface
-        # To modify an Ethernet connection with static IP configuration, issue a command as follows
-        # - community.general.nmcli: conn_name=my-eth1 ifname=eth1 type=ethernet ip4=192.0.2.100/24 gw4=192.0.2.1 state=present
-        # nmcli con mod con-name my-eth1 ifname eth1 type ethernet ipv4.address 192.0.2.100/24 ipv4.gateway 192.0.2.1
-        options = {
-            'ipv4.method': self.ipv4_method,
-            'ipv4.address': self.ip4,
-            'ipv4.gateway': self.gw4,
-            'ipv4.dns': self.dns4,
-            'ipv6.method': self.ipv6_method,
-            'ipv6.address': self.ip6,
-            'ipv6.gateway': self.gw6,
-            'ipv6.dns': self.dns6,
-            'autoconnect': self.bool_to_string(self.autoconnect),
-            'ipv4.dns-search': self.dns4_search,
-            'ipv6.dns-search': self.dns6_search,
-            '802-3-ethernet.mtu': self.mtu,
-            'ipv4.dhcp-client-id': self.dhcp_client_id,
-        }
-
-        for key, value in options.items():
-            if value is not None:
-                if key == '802-3-ethernet.mtu' and conn_type != 'ethernet':
-                    continue
-                cmd.extend([key, value])
-
-        return cmd
-
-    def create_connection_bridge(self):
-        # format for creating bridge interface
-        # To add an Bridge connection with static IP configuration, issue a command as follows
-        # - community.general.nmcli: name=add conn_name=my-eth1 ifname=eth1 type=bridge ip4=192.0.2.100/24 gw4=192.0.2.1 state=present
-        # nmcli con add con-name my-eth1 ifname eth1 type bridge ip4 192.0.2.100/24 gw4 192.0.2.1
-        cmd = [self.nmcli_bin, 'con', 'add', 'type', 'bridge', 'con-name']
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-
-        options = {
-            'ip4': self.ip4,
-            'gw4': self.gw4,
-            'ip6': self.ip6,
-            'gw6': self.gw6,
-            'autoconnect': self.bool_to_string(self.autoconnect),
-            'bridge.ageing-time': self.ageingtime,
-            'bridge.forward-delay': self.forwarddelay,
-            'bridge.hello-time': self.hellotime,
-            'bridge.mac-address': self.mac,
-            'bridge.max-age': self.maxage,
-            'bridge.priority': self.priority,
-            'bridge.stp': self.bool_to_string(self.stp)
-        }
-
-        for key, value in options.items():
-            if value is not None:
-                cmd.extend([key, value])
-
-        return cmd
+    def connection_update(self, nmcli_command):
+        if nmcli_command == 'create':
+            cmd = [self.nmcli_bin, 'con', 'add', 'type']
+            if self.tunnel_conn_type:
+                cmd.append('ip-tunnel')
+            else:
+                cmd.append(self.type)
+            cmd.append('con-name')
+        elif nmcli_command == 'modify':
+            cmd = [self.nmcli_bin, 'con', 'modify']
+        else:
+            self.module.fail_json(msg="Invalid nmcli command.")
+        cmd.append(self.conn_name)
 
-    def modify_connection_bridge(self):
-        # format for modifying bridge interface
-        # To add an Bridge connection with static IP configuration, issue a command as follows
-        # - community.general.nmcli: name=mod conn_name=my-eth1 ifname=eth1 type=bridge ip4=192.0.2.100/24 gw4=192.0.2.1 state=present
-        # nmcli con mod my-eth1 ifname eth1 type bridge ip4 192.0.2.100/24 gw4 192.0.2.1
-        cmd = [self.nmcli_bin, 'con', 'mod', self.conn_name]
+        # Use connection name as default for interface name on creation.
+        if nmcli_command == 'create' and self.ifname is None:
+            ifname = self.conn_name
+        else:
+            ifname = self.ifname
 
         options = {
-            'ipv4.method': self.ipv4_method,
-            'ipv4.address': self.ip4,
-            'ipv4.gateway': self.gw4,
-            'ipv6.method': self.ipv6_method,
-            'ipv6.address': self.ip6,
-            'ipv6.gateway': self.gw6,
-            'autoconnect': self.bool_to_string(self.autoconnect),
-            'bridge.ageing-time': self.ageingtime,
-            'bridge.forward-delay': self.forwarddelay,
-            'bridge.hello-time': self.hellotime,
-            'bridge.mac-address': self.mac,
-            'bridge.max-age': self.maxage,
-            'bridge.priority': self.priority,
-            'bridge.stp': self.bool_to_string(self.stp)
+            'connection.interface-name': ifname,
         }
 
-        for key, value in options.items():
-            if value is not None:
-                cmd.extend([key, value])
-
-        return cmd
-
-    def create_connection_bridge_slave(self):
-        # format for creating bond-slave interface
-        cmd = [self.nmcli_bin, 'con', 'add', 'type', 'bridge-slave', 'con-name']
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-
-        options = {
-            'master': self.master,
-            'bridge-port.path-cost': self.path_cost,
-            'bridge-port.hairpin': self.bool_to_string(self.hairpin),
-            'bridge-port.priority': self.slavepriority,
-        }
+        options.update(self.connection_options())
 
+        # Constructing the command.
         for key, value in options.items():
             if value is not None:
                 cmd.extend([key, value])
 
-        return cmd
-
-    def modify_connection_bridge_slave(self):
-        # format for modifying bond-slave interface
-        cmd = [self.nmcli_bin, 'con', 'mod', self.conn_name]
-        options = {
-            'master': self.master,
-            'bridge-port.path-cost': self.path_cost,
-            'bridge-port.hairpin': self.bool_to_string(self.hairpin),
-            'bridge-port.priority': self.slavepriority,
-        }
-
-        for key, value in options.items():
-            if value is not None:
-                cmd.extend([key, value])
-
-        return cmd
-
-    def create_connection_vlan(self):
-        cmd = [self.nmcli_bin]
-        cmd.append('con')
-        cmd.append('add')
-        cmd.append('type')
-        cmd.append('vlan')
-        cmd.append('con-name')
-
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        else:
-            cmd.append('vlan%s' % to_text(self.vlanid))
-
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-        else:
-            cmd.append('vlan%s' % to_text(self.vlanid))
-
-        params = {'dev': self.vlandev,
-                  'id': self.vlanid,
-                  'ip4': self.ip4 or '',
-                  'gw4': self.gw4 or '',
-                  'ip6': self.ip6 or '',
-                  'gw6': self.gw6 or '',
-                  'autoconnect': self.bool_to_string(self.autoconnect)
-                  }
-        for k, v in params.items():
-            cmd.extend([k, v])
-
-        return cmd
-
-    def modify_connection_vlan(self):
-        cmd = [self.nmcli_bin]
-        cmd.append('con')
-        cmd.append('mod')
-
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        else:
-            cmd.append('vlan%s' % to_text(self.vlanid))
-
-        params = {'vlan.parent': self.vlandev,
-                  'vlan.id': self.vlanid,
-                  'ipv4.method': self.ipv4_method,
-                  'ipv4.address': self.ip4 or '',
-                  'ipv4.gateway': self.gw4 or '',
-                  'ipv4.dns': self.dns4 or '',
-                  'ipv6.method': self.ipv6_method,
-                  'ipv6.address': self.ip6 or '',
-                  'ipv6.gateway': self.gw6 or '',
-                  'ipv6.dns': self.dns6 or '',
-                  'autoconnect': self.bool_to_string(self.autoconnect)
-                  }
-
-        for k, v in params.items():
-            cmd.extend([k, v])
-
-        return cmd
-
-    def create_connection_vxlan(self):
-        cmd = [self.nmcli_bin, 'con', 'add', 'type', 'vxlan', 'con-name']
-
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        else:
-            cmd.append('vxlan%s' % to_text(self.vxlan_id))
-
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-        else:
-            cmd.append('vxan%s' % to_text(self.vxlan_id))
-
-        params = {'vxlan.id': self.vxlan_id,
-                  'vxlan.local': self.vxlan_local,
-                  'vxlan.remote': self.vxlan_remote,
-                  'autoconnect': self.bool_to_string(self.autoconnect)
-                  }
-        for k, v in params.items():
-            cmd.extend([k, v])
-
-        return cmd
-
-    def modify_connection_vxlan(self):
-        cmd = [self.nmcli_bin, 'con', 'mod']
-
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        else:
-            cmd.append('vxlan%s' % to_text(self.vxlan_id))
-
-        params = {'vxlan.id': self.vxlan_id,
-                  'vxlan.local': self.vxlan_local,
-                  'vxlan.remote': self.vxlan_remote,
-                  'autoconnect': self.bool_to_string(self.autoconnect)
-                  }
-        for k, v in params.items():
-            cmd.extend([k, v])
-        return cmd
-
-    def create_connection_ipip(self):
-        cmd = [self.nmcli_bin, 'con', 'add', 'type', 'ip-tunnel', 'mode', 'ipip', 'con-name']
-
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.ip_tunnel_dev is not None:
-            cmd.append('ipip%s' % to_text(self.ip_tunnel_dev))
-
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-        else:
-            cmd.append('ipip%s' % to_text(self.ipip_dev))
-
-        if self.ip_tunnel_dev is not None:
-            cmd.extend(['dev', self.ip_tunnel_dev])
-
-        params = {'ip-tunnel.local': self.ip_tunnel_local,
-                  'ip-tunnel.remote': self.ip_tunnel_remote,
-                  'autoconnect': self.bool_to_string(self.autoconnect)
-                  }
-        for k, v in params.items():
-            cmd.extend([k, v])
-
-        return cmd
-
-    def modify_connection_ipip(self):
-        cmd = [self.nmcli_bin, 'con', 'mod']
-
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.ip_tunnel_dev is not None:
-            cmd.append('ipip%s' % to_text(self.ip_tunnel_dev))
-
-        params = {'ip-tunnel.local': self.ip_tunnel_local,
-                  'ip-tunnel.remote': self.ip_tunnel_remote,
-                  'autoconnect': self.bool_to_string(self.autoconnect)
-                  }
-        for k, v in params.items():
-            cmd.extend([k, v])
-        return cmd
-
-    def create_connection_sit(self):
-        cmd = [self.nmcli_bin, 'con', 'add', 'type', 'ip-tunnel', 'mode', 'sit', 'con-name']
-
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.ip_tunnel_dev is not None:
-            cmd.append('sit%s' % to_text(self.ip_tunnel_dev))
-
-        cmd.append('ifname')
-        if self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.conn_name is not None:
-            cmd.append(self.conn_name)
-        else:
-            cmd.append('sit%s' % to_text(self.ipip_dev))
-
-        if self.ip_tunnel_dev is not None:
-            cmd.extend(['dev', self.ip_tunnel_dev])
-
-        params = {'ip-tunnel.local': self.ip_tunnel_local,
-                  'ip-tunnel.remote': self.ip_tunnel_remote,
-                  'autoconnect': self.bool_to_string(self.autoconnect)
-                  }
-        for k, v in params.items():
-            cmd.extend([k, v])
-
-        return cmd
-
-    def modify_connection_sit(self):
-        cmd = [self.nmcli_bin, 'con', 'mod']
-
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        elif self.ip_tunnel_dev is not None:
-            cmd.append('sit%s' % to_text(self.ip_tunnel_dev))
-
-        params = {'ip-tunnel.local': self.ip_tunnel_local,
-                  'ip-tunnel.remote': self.ip_tunnel_remote,
-                  'autoconnect': self.bool_to_string(self.autoconnect)
-                  }
-        for k, v in params.items():
-            cmd.extend([k, v])
-        return cmd
+        return self.execute_command(cmd)
 
     def create_connection(self):
-        cmd = []
-        if self.type == 'team':
-            if (self.dns4 is not None) or (self.dns6 is not None):
-                cmd = self.create_connection_team()
-                self.execute_command(cmd)
-                cmd = self.modify_connection_team()
-                self.execute_command(cmd)
-                return self.up_connection()
-            elif (self.dns4 is None) or (self.dns6 is None):
-                cmd = self.create_connection_team()
-        elif self.type == 'team-slave':
-            if self.mtu is not None:
-                cmd = self.create_connection_team_slave()
-                self.execute_command(cmd)
-                cmd = self.modify_connection_team_slave()
-                return self.execute_command(cmd)
-            else:
-                cmd = self.create_connection_team_slave()
-        elif self.type == 'bond':
-            if (self.mtu is not None) or (self.dns4 is not None) or (self.dns6 is not None):
-                cmd = self.create_connection_bond()
-                self.execute_command(cmd)
-                cmd = self.modify_connection_bond()
-                self.execute_command(cmd)
-                return self.up_connection()
-            else:
-                cmd = self.create_connection_bond()
-        elif self.type == 'bond-slave':
-            cmd = self.create_connection_bond_slave()
-        elif self.type == 'ethernet':
+        status = self.connection_update('create')
+        if self.create_connection_up:
+            status = self.up_connection()
+        return status
+
+    @property
+    def create_connection_up(self):
+        if self.type in ('bond', 'ethernet'):
             if (self.mtu is not None) or (self.dns4 is not None) or (self.dns6 is not None):
-                cmd = self.create_connection_ethernet()
-                self.execute_command(cmd)
-                cmd = self.modify_connection_ethernet()
-                self.execute_command(cmd)
-                return self.up_connection()
-            else:
-                cmd = self.create_connection_ethernet()
-        elif self.type == 'bridge':
-            cmd = self.create_connection_bridge()
-        elif self.type == 'bridge-slave':
-            cmd = self.create_connection_bridge_slave()
-        elif self.type == 'vlan':
-            cmd = self.create_connection_vlan()
-        elif self.type == 'vxlan':
-            cmd = self.create_connection_vxlan()
-        elif self.type == 'ipip':
-            cmd = self.create_connection_ipip()
-        elif self.type == 'sit':
-            cmd = self.create_connection_sit()
-        elif self.type == 'generic':
-            cmd = self.create_connection_ethernet(conn_type='generic')
-
-        if cmd:
-            return self.execute_command(cmd)
-        else:
-            self.module.fail_json(msg="Type of device or network connection is required "
-                                      "while performing 'create' operation. Please specify 'type' as an argument.")
+                return True
+        elif self.type == 'team':
+            if (self.dns4 is not None) or (self.dns6 is not None):
+                return True
+        return False
 
     def remove_connection(self):
         # self.down_connection()
@@ -1418,55 +887,10 @@ def remove_connection(self):
         return self.execute_command(cmd)
 
     def modify_connection(self):
-        cmd = []
-        if self.type == 'team':
-            cmd = self.modify_connection_team()
-        elif self.type == 'team-slave':
-            cmd = self.modify_connection_team_slave()
-        elif self.type == 'bond':
-            cmd = self.modify_connection_bond()
-        elif self.type == 'bond-slave':
-            cmd = self.modify_connection_bond_slave()
-        elif self.type == 'ethernet':
-            cmd = self.modify_connection_ethernet()
-        elif self.type == 'bridge':
-            cmd = self.modify_connection_bridge()
-        elif self.type == 'bridge-slave':
-            cmd = self.modify_connection_bridge_slave()
-        elif self.type == 'vlan':
-            cmd = self.modify_connection_vlan()
-        elif self.type == 'vxlan':
-            cmd = self.modify_connection_vxlan()
-        elif self.type == 'ipip':
-            cmd = self.modify_connection_ipip()
-        elif self.type == 'sit':
-            cmd = self.modify_connection_sit()
-        elif self.type == 'generic':
-            cmd = self.modify_connection_ethernet(conn_type='generic')
-        if cmd:
-            return self.execute_command(cmd)
-        else:
-            self.module.fail_json(msg="Type of device or network connection is required "
-                                      "while performing 'modify' operation. Please specify 'type' as an argument.")
+        return self.connection_update('modify')
 
     def show_connection(self):
-        cmd = [self.nmcli_bin, 'con', 'show']
-        if self.conn_name is not None:
-            cmd.append(self.conn_name)
-        elif self.ifname is not None:
-            cmd.append(self.ifname)
-        else:
-            if self.type == 'vlan':
-                cmd.append('vlan%s' % to_text(self.vlanid))
-            elif self.type == 'vxlan':
-                cmd.append('vxlan%s' % to_text(self.vxlan_id))
-            elif self.type == 'ipip':
-                cmd.append('ipip%s' % self.ip_tunnel_dev)
-            elif self.type == 'sit':
-                cmd.append('sit%s' % self.ip_tunnel_dev)
-            else:
-                raise NmcliModuleError(
-                    "Invalid connection identifier for nmcli")
+        cmd = [self.nmcli_bin, 'con', 'show', self.conn_name]
 
         (rc, out, err) = self.execute_command(cmd)
 
@@ -1479,6 +903,7 @@ def show_connection(self):
         for line in out.splitlines():
             pair = line.split(':', 1)
             key = pair[0].strip()
+            key_type = self.settings_type(key)
             if key and len(pair) > 1:
                 raw_value = pair[1].lstrip()
                 if raw_value == '--':
@@ -1492,9 +917,8 @@ def show_connection(self):
                             alias_key = alias_pair[0]
                             alias_value = alias_pair[1]
                             conn_info[alias_key] = alias_value
-                elif key in ['ipv4.dns', 'ipv4.dns-search', 'ipv6.dns', 'ipv6.dns-search']:
-                    values = raw_value.split(',')
-                    conn_info[key] = values
+                elif key_type == list:
+                    conn_info[key] = [s.strip() for s in raw_value.split(',')]
                 else:
                     m_enum = p_enum_value.match(raw_value)
                     if m_enum is not None:
@@ -1512,6 +936,7 @@ def _compare_conn_params(self, conn_info, options):
             'con-name': 'connection.id',
             'autoconnect': 'connection.autoconnect',
             'ifname': 'connection.interface-name',
+            'mac': self.mac_setting,
             'master': 'connection.master',
             'slave-type': 'connection.slave-type',
         }
@@ -1524,11 +949,6 @@ def _compare_conn_params(self, conn_info, options):
             if not value:
                 continue
 
-            # TODO: retain typed list arguments in Nmcli object instead of encoded strings
-            if key in ['ipv4.dns', 'ipv4.dns-search', 'ipv6.dns', 'ipv6.dns-search']:
-                list_values = value.split()
-                value = list_values
-
             if key in conn_info:
                 current_value = conn_info[key]
             elif key in param_alias:
@@ -1560,145 +980,11 @@ def _compare_conn_params(self, conn_info, options):
         return (changed, diff)
 
     def is_connection_changed(self):
-        conn_info = self.show_connection()
-        changed = False
-
-        if self.type == 'team':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'ipv4.method': self.ipv4_method,
-                'ipv4.addresses': self.ip4,
-                'ipv4.gateway': self.gw4,
-                'ipv4.dns': self.dns4,
-                'ipv6.method': self.ipv6_method,
-                'ipv6.addresses': self.ip6,
-                'ipv6.gateway': self.gw6,
-                'ipv6.dns': self.dns6,
-                'autoconnect': self.bool_to_string(self.autoconnect),
-                'ipv4.dns-search': self.dns4_search,
-                'ipv6.dns-search': self.dns6_search,
-                'ipv4.dhcp-client-id': self.dhcp_client_id,
-            })
-        elif self.type == 'team-slave':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'connection.master': self.master,
-                '802-3-ethernet.mtu': to_text(self.mtu)
-            })
-        elif self.type == 'bond':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'ipv4.method': self.ipv4_method,
-                'ipv4.addresses': self.ip4,
-                'ipv4.gateway': self.gw4,
-                'ipv4.dns': self.dns4,
-                'ipv6.method': self.ipv6_method,
-                'ipv6.addresses': self.ip6,
-                'ipv6.gateway': self.gw6,
-                'ipv6.dns': self.dns6,
-                'autoconnect': self.bool_to_string(self.autoconnect),
-                'ipv4.dns-search': self.dns4_search,
-                'ipv6.dns-search': self.dns6_search,
-                'miimon': self.miimon,
-                'downdelay': self.downdelay,
-                'updelay': self.updelay,
-                'arp-interval': self.arp_interval,
-                'arp-ip-target': self.arp_ip_target,
-                'ipv4.dhcp-client-id': self.dhcp_client_id,
-            })
-        elif self.type == 'bond-slave':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'connection.master': self.master,
-            })
-        elif self.type == 'ethernet':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'ipv4.method': self.ipv4_method,
-                'ipv4.addresses': self.ip4,
-                'ipv4.gateway': self.gw4,
-                'ipv4.dns': self.dns4,
-                'ipv6.method': self.ipv6_method,
-                'ipv6.addresses': self.ip6,
-                'ipv6.gateway': self.gw6,
-                'ipv6.dns': self.dns6,
-                'autoconnect': self.bool_to_string(self.autoconnect),
-                'ipv4.dns-search': self.dns4_search,
-                'ipv6.dns-search': self.dns6_search,
-                '802-3-ethernet.mtu': self.mtu,
-                'ipv4.dhcp-client-id': self.dhcp_client_id,
-            })
-        elif self.type == 'bridge':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'ipv4.method': self.ipv4_method,
-                'ipv4.addresses': self.ip4,
-                'ipv4.gateway': self.gw4,
-                'ipv6.method': self.ipv6_method,
-                'ipv6.addresses': self.ip6,
-                'ipv6.gateway': self.gw6,
-                'autoconnect': self.bool_to_string(self.autoconnect),
-                'bridge.ageing-time': self.ageingtime,
-                'bridge.forward-delay': self.forwarddelay,
-                'bridge.hello-time': self.hellotime,
-                'bridge.mac-address': self.mac,
-                'bridge.max-age': self.maxage,
-                'bridge.priority': self.priority,
-                'bridge.stp': self.bool_to_string(self.stp)
-            })
-        elif self.type == 'bridge-slave':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'master': self.master,
-                'bridge-port.path-cost': self.path_cost,
-                'bridge-port.hairpin-mode': self.bool_to_string(self.hairpin),
-                'bridge-port.priority': self.slavepriority,
-            })
-        elif self.type == 'vlan':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'vlan.parent': self.vlandev,
-                'vlan.id': self.vlanid,
-                'ipv4.method': self.ipv4_method,
-                'ipv4.addresses': self.ip4 or '',
-                'ipv4.gateway': self.gw4 or '',
-                'ipv4.dns': self.dns4 or '',
-                'ipv6.method': self.ipv6_method,
-                'ipv6.addresses': self.ip6 or '',
-                'ipv6.gateway': self.gw6 or '',
-                'ipv6.dns': self.dns6 or '',
-                'autoconnect': self.bool_to_string(self.autoconnect),
-            })
-        elif self.type == 'vxlan':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'vxlan.id': self.vxlan_id,
-                'vxlan.local': self.vxlan_local,
-                'vxlan.remote': self.vxlan_remote,
-                'autoconnect': self.bool_to_string(self.autoconnect),
-            })
-        elif self.type == 'ipip':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'ip-tunnel.local': self.ip_tunnel_local,
-                'ip-tunnel.remote': self.ip_tunnel_remote,
-                'autoconnect': self.bool_to_string(self.autoconnect),
-            })
-        elif self.type == 'sit':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'ip-tunnel.local': self.ip_tunnel_local,
-                'ip-tunnel.remote': self.ip_tunnel_remote,
-                'autoconnect': self.bool_to_string(self.autoconnect),
-            })
-        elif self.type == 'generic':
-            changed, diff = self._compare_conn_params(conn_info, {
-                'ipv4.method': self.ipv4_method,
-                'ipv4.addresses': self.ip4,
-                'ipv4.gateway': self.gw4,
-                'ipv4.dns': self.dns4,
-                'ipv6.method': self.ipv6_method,
-                'ipv6.addresses': self.ip6,
-                'ipv6.gateway': self.gw6,
-                'ipv6.dns': self.dns6,
-                'autoconnect': self.bool_to_string(self.autoconnect),
-                'ipv4.dns-search': self.dns4_search,
-                'ipv6.dns-search': self.dns6_search,
-                'ipv4.dhcp-client-id': self.dhcp_client_id,
-            })
-        else:
-            raise NmcliModuleError("Unknown type of device: %s" % self.type)
-
-        return changed, diff
+        options = {
+            'connection.interface-name': self.ifname,
+        }
+        options.update(self.connection_options(detect_change=True))
+        return self._compare_conn_params(self.show_connection(), options)
 
 
 def main():
@@ -1714,13 +1000,13 @@ def main():
                       choices=['bond', 'bond-slave', 'bridge', 'bridge-slave', 'ethernet', 'generic', 'ipip', 'sit', 'team', 'team-slave', 'vlan', 'vxlan']),
             ip4=dict(type='str'),
             gw4=dict(type='str'),
-            dns4=dict(type='list'),
-            dns4_search=dict(type='list'),
+            dns4=dict(type='list', elements='str'),
+            dns4_search=dict(type='list', elements='str'),
             dhcp_client_id=dict(type='str'),
             ip6=dict(type='str'),
             gw6=dict(type='str'),
-            dns6=dict(type='list'),
-            dns6_search=dict(type='list'),
+            dns6=dict(type='list', elements='str'),
+            dns6_search=dict(type='list', elements='str'),
             # Bond Specific vars
             mode=dict(type='str', default='balance-rr',
                       choices=['802.3ad', 'active-backup', 'balance-alb', 'balance-rr', 'balance-tlb', 'balance-xor', 'broadcast']),
@@ -1762,12 +1048,6 @@ def main():
     )
     module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
 
-    if not HAVE_DBUS:
-        module.fail_json(msg=missing_required_lib('dbus'), exception=DBUS_IMP_ERR)
-
-    if not HAVE_NM_CLIENT:
-        module.fail_json(msg=missing_required_lib('NetworkManager glib API'), exception=NM_CLIENT_IMP_ERR)
-
     nmcli = Nmcli(module)
 
     (rc, out, err) = (None, '', '')


Test patch:
diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt
--- a/tests/sanity/ignore-2.10.txt
+++ b/tests/sanity/ignore-2.10.txt
@@ -840,8 +840,6 @@ plugins/modules/net_tools/nios/nios_zone.py validate-modules:invalid-ansiblemodu
 plugins/modules/net_tools/nios/nios_zone.py validate-modules:parameter-alias-self
 plugins/modules/net_tools/nios/nios_zone.py validate-modules:parameter-type-not-in-doc
 plugins/modules/net_tools/nios/nios_zone.py validate-modules:undocumented-parameter
-plugins/modules/net_tools/nmcli.py validate-modules:parameter-list-no-elements
-plugins/modules/net_tools/nmcli.py validate-modules:parameter-type-not-in-doc
 plugins/modules/net_tools/nsupdate.py validate-modules:parameter-list-no-elements
 plugins/modules/net_tools/nsupdate.py validate-modules:parameter-type-not-in-doc
 plugins/modules/net_tools/omapi_host.py validate-modules:parameter-list-no-elements
diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt
--- a/tests/sanity/ignore-2.11.txt
+++ b/tests/sanity/ignore-2.11.txt
@@ -840,8 +840,6 @@ plugins/modules/net_tools/nios/nios_zone.py validate-modules:invalid-ansiblemodu
 plugins/modules/net_tools/nios/nios_zone.py validate-modules:parameter-alias-self
 plugins/modules/net_tools/nios/nios_zone.py validate-modules:parameter-type-not-in-doc
 plugins/modules/net_tools/nios/nios_zone.py validate-modules:undocumented-parameter
-plugins/modules/net_tools/nmcli.py validate-modules:parameter-list-no-elements
-plugins/modules/net_tools/nmcli.py validate-modules:parameter-type-not-in-doc
 plugins/modules/net_tools/nsupdate.py validate-modules:parameter-list-no-elements
 plugins/modules/net_tools/nsupdate.py validate-modules:parameter-type-not-in-doc
 plugins/modules/net_tools/omapi_host.py validate-modules:parameter-list-no-elements
diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt
--- a/tests/sanity/ignore-2.9.txt
+++ b/tests/sanity/ignore-2.9.txt
@@ -652,7 +652,6 @@ plugins/modules/net_tools/nios/nios_zone.py validate-modules:doc-default-does-no
 plugins/modules/net_tools/nios/nios_zone.py validate-modules:doc-missing-type
 plugins/modules/net_tools/nios/nios_zone.py validate-modules:parameter-type-not-in-doc
 plugins/modules/net_tools/nios/nios_zone.py validate-modules:undocumented-parameter
-plugins/modules/net_tools/nmcli.py validate-modules:parameter-type-not-in-doc
 plugins/modules/net_tools/nsupdate.py validate-modules:parameter-type-not-in-doc
 plugins/modules/notification/bearychat.py validate-modules:parameter-type-not-in-doc
 plugins/modules/notification/campfire.py validate-modules:doc-missing-type
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
@@ -238,7 +238,7 @@
 
 TESTCASE_VXLAN_SHOW_OUTPUT = """\
 connection.id:                          non_existent_nw_device
-connection.interface-name:              existent_nw_device
+connection.interface-name:              vxlan-existent_nw_device
 connection.autoconnect:                 yes
 vxlan.id:                               11
 vxlan.local:                            192.168.225.5
@@ -306,6 +306,7 @@
 connection.id:                          non_existent_nw_device
 connection.interface-name:              ethernet_non_existant
 connection.autoconnect:                 yes
+802-3-ethernet.mtu:                     auto
 ipv4.method:                            auto
 ipv4.dhcp-client-id:                    00:11:22:AA:BB:CC:DD
 ipv6.method:                            auto
@@ -328,6 +329,7 @@
 connection.id:                          non_existent_nw_device
 connection.interface-name:              ethernet_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
@@ -336,150 +338,138 @@
 """
 
 
-def mocker_set(mocker, connection_exists=False):
+def mocker_set(mocker,
+               connection_exists=False,
+               execute_return=(0, "", ""),
+               execute_side_effect=None,
+               changed_return=None):
     """
     Common mocker object
     """
-    mocker.patch('ansible_collections.community.general.plugins.modules.net_tools.nmcli.HAVE_DBUS', True)
-    mocker.patch('ansible_collections.community.general.plugins.modules.net_tools.nmcli.HAVE_NM_CLIENT', True)
     get_bin_path = mocker.patch('ansible.module_utils.basic.AnsibleModule.get_bin_path')
     get_bin_path.return_value = '/usr/bin/nmcli'
     connection = mocker.patch.object(nmcli.Nmcli, 'connection_exists')
     connection.return_value = connection_exists
-    return connection
+    execute_command = mocker.patch.object(nmcli.Nmcli, 'execute_command')
+    if execute_return:
+        execute_command.return_value = execute_return
+    if execute_side_effect:
+        execute_command.side_effect = execute_side_effect
+    if changed_return:
+        is_connection_changed = mocker.patch.object(nmcli.Nmcli, 'is_connection_changed')
+        is_connection_changed.return_value = changed_return
 
 
 @pytest.fixture
 def mocked_generic_connection_create(mocker):
     mocker_set(mocker)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, "", "")
-    return command_result
 
 
 @pytest.fixture
 def mocked_connection_exists(mocker):
-    connection = mocker_set(mocker, connection_exists=True)
-    return connection
+    mocker_set(mocker, connection_exists=True)
 
 
 @pytest.fixture
 def mocked_generic_connection_modify(mocker):
-    mocker_set(mocker, connection_exists=True)
-    connection_changed = mocker.patch.object(
-        nmcli.Nmcli, 'is_connection_changed')
-    connection_changed.return_value = (True, dict())
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, "", "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               changed_return=(True, dict()))
 
 
 @pytest.fixture
 def mocked_generic_connection_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_GENERIC_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_GENERIC_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_generic_connection_dns_search_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (
-        0, TESTCASE_GENERIC_DNS4_SEARCH_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_GENERIC_DNS4_SEARCH_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_bond_connection_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_BOND_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_BOND_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_bridge_connection_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_BRIDGE_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_BRIDGE_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_bridge_slave_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_BRIDGE_SLAVE_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_BRIDGE_SLAVE_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_vlan_connection_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_VLAN_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_VLAN_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_vxlan_connection_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_VXLAN_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_VXLAN_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_ipip_connection_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_IPIP_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_IPIP_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_sit_connection_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_SIT_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_SIT_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_ethernet_connection_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_ETHERNET_DHCP, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_ETHERNET_DHCP, ""))
 
 
 @pytest.fixture
 def mocked_ethernet_connection_dhcp_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_ETHERNET_DHCP_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_ETHERNET_DHCP_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_ethernet_connection_static_unchanged(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.return_value = (0, TESTCASE_ETHERNET_STATIC_SHOW_OUTPUT, "")
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=(0, TESTCASE_ETHERNET_STATIC_SHOW_OUTPUT, ""))
 
 
 @pytest.fixture
 def mocked_ethernet_connection_dhcp_to_static(mocker):
-    mocker_set(mocker, connection_exists=True)
-    command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command')
-    command_result.side_effect = [
-        (0, TESTCASE_ETHERNET_DHCP_SHOW_OUTPUT, ""),
-        (0, "", ""),
-    ]
-    return command_result
+    mocker_set(mocker,
+               connection_exists=True,
+               execute_return=None,
+               execute_side_effect=(
+                   (0, TESTCASE_ETHERNET_DHCP_SHOW_OUTPUT, ""),
+                   (0, "", ""),
+               ))
 
 
 @pytest.mark.parametrize('patch_ansible_module', TESTCASE_BOND, indirect=['patch_ansible_module'])
@@ -501,10 +491,10 @@ def test_bond_connection_create(mocked_generic_connection_create, capfd):
     assert args[0][4] == 'bond'
     assert args[0][5] == 'con-name'
     assert args[0][6] == 'non_existent_nw_device'
-    assert args[0][7] == 'ifname'
-    assert args[0][8] == 'bond_non_existant'
 
-    for param in ['gw4', 'primary', 'autoconnect', 'mode', 'active-backup', 'ip4']:
+    for param in ['ipv4.gateway', 'primary', 'connection.autoconnect',
+                  'connection.interface-name', 'bond_non_existant',
+                  'mode', 'active-backup', 'ipv4.addresses']:
         assert param in args[0]
 
     out, err = capfd.readouterr()
@@ -547,7 +537,7 @@ def test_generic_connection_create(mocked_generic_connection_create, capfd):
     assert args[0][5] == 'con-name'
     assert args[0][6] == 'non_existent_nw_device'
 
-    for param in ['autoconnect', 'gw4', 'ip4']:
+    for param in ['connection.autoconnect', 'ipv4.gateway', 'ipv4.addresses']:
         assert param in args[0]
 
     out, err = capfd.readouterr()
@@ -570,10 +560,10 @@ def test_generic_connection_modify(mocked_generic_connection_modify, capfd):
 
     assert args[0][0] == '/usr/bin/nmcli'
     assert args[0][1] == 'con'
-    assert args[0][2] == 'mod'
+    assert args[0][2] == 'modify'
     assert args[0][3] == 'non_existent_nw_device'
 
-    for param in ['ipv4.gateway', 'ipv4.address']:
+    for param in ['ipv4.gateway', 'ipv4.addresses']:
         assert param in args[0]
 
     out, err = capfd.readouterr()
@@ -686,8 +676,9 @@ def test_create_bridge(mocked_generic_connection_create, capfd):
     assert args[0][5] == 'con-name'
     assert args[0][6] == 'non_existent_nw_device'
 
-    for param in ['ip4', '10.10.10.10/24', 'gw4', '10.10.10.1', 'bridge.max-age', '100', 'bridge.stp', 'yes']:
-        assert param in map(to_text, args[0])
+    args_text = list(map(to_text, args[0]))
+    for param in ['ipv4.addresses', '10.10.10.10/24', 'ipv4.gateway', '10.10.10.1', 'bridge.max-age', '100', 'bridge.stp', 'yes']:
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -710,10 +701,12 @@ def test_mod_bridge(mocked_generic_connection_modify, capfd):
 
     assert args[0][0] == '/usr/bin/nmcli'
     assert args[0][1] == 'con'
-    assert args[0][2] == 'mod'
+    assert args[0][2] == 'modify'
     assert args[0][3] == 'non_existent_nw_device'
-    for param in ['ipv4.address', '10.10.10.10/24', 'ipv4.gateway', '10.10.10.1', 'bridge.max-age', '100', 'bridge.stp', 'yes']:
-        assert param in map(to_text, args[0])
+
+    args_text = list(map(to_text, args[0]))
+    for param in ['ipv4.addresses', '10.10.10.10/24', 'ipv4.gateway', '10.10.10.1', 'bridge.max-age', '100', 'bridge.stp', 'yes']:
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -756,8 +749,9 @@ def test_create_bridge_slave(mocked_generic_connection_create, capfd):
     assert args[0][5] == 'con-name'
     assert args[0][6] == 'non_existent_nw_device'
 
+    args_text = list(map(to_text, args[0]))
     for param in ['bridge-port.path-cost', '100']:
-        assert param in map(to_text, args[0])
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -780,11 +774,12 @@ def test_mod_bridge_slave(mocked_generic_connection_modify, capfd):
 
     assert args[0][0] == '/usr/bin/nmcli'
     assert args[0][1] == 'con'
-    assert args[0][2] == 'mod'
+    assert args[0][2] == 'modify'
     assert args[0][3] == 'non_existent_nw_device'
 
+    args_text = list(map(to_text, args[0]))
     for param in ['bridge-port.path-cost', '100']:
-        assert param in map(to_text, args[0])
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -827,8 +822,9 @@ def test_create_vlan_con(mocked_generic_connection_create, capfd):
     assert args[0][5] == 'con-name'
     assert args[0][6] == 'non_existent_nw_device'
 
-    for param in ['ip4', '10.10.10.10/24', 'gw4', '10.10.10.1', 'id', '10']:
-        assert param in map(to_text, args[0])
+    args_text = list(map(to_text, args[0]))
+    for param in ['ipv4.addresses', '10.10.10.10/24', 'ipv4.gateway', '10.10.10.1', 'vlan.id', '10']:
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -851,11 +847,12 @@ def test_mod_vlan_conn(mocked_generic_connection_modify, capfd):
 
     assert args[0][0] == '/usr/bin/nmcli'
     assert args[0][1] == 'con'
-    assert args[0][2] == 'mod'
+    assert args[0][2] == 'modify'
     assert args[0][3] == 'non_existent_nw_device'
 
-    for param in ['ipv4.address', '10.10.10.10/24', 'ipv4.gateway', '10.10.10.1', 'vlan.id', '10']:
-        assert param in map(to_text, args[0])
+    args_text = list(map(to_text, args[0]))
+    for param in ['ipv4.addresses', '10.10.10.10/24', 'ipv4.gateway', '10.10.10.1', 'vlan.id', '10']:
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -896,10 +893,11 @@ def test_create_vxlan(mocked_generic_connection_create, capfd):
     assert args[0][4] == 'vxlan'
     assert args[0][5] == 'con-name'
     assert args[0][6] == 'non_existent_nw_device'
-    assert args[0][7] == 'ifname'
 
-    for param in ['vxlan.local', '192.168.225.5', 'vxlan.remote', '192.168.225.6', 'vxlan.id', '11']:
-        assert param in map(to_text, args[0])
+    args_text = list(map(to_text, args[0]))
+    for param in ['connection.interface-name', 'vxlan-existent_nw_device',
+                  'vxlan.local', '192.168.225.5', 'vxlan.remote', '192.168.225.6', 'vxlan.id', '11']:
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -921,11 +919,12 @@ def test_vxlan_mod(mocked_generic_connection_modify, capfd):
 
     assert args[0][0] == '/usr/bin/nmcli'
     assert args[0][1] == 'con'
-    assert args[0][2] == 'mod'
+    assert args[0][2] == 'modify'
     assert args[0][3] == 'non_existent_nw_device'
 
+    args_text = list(map(to_text, args[0]))
     for param in ['vxlan.local', '192.168.225.5', 'vxlan.remote', '192.168.225.6', 'vxlan.id', '11']:
-        assert param in map(to_text, args[0])
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -964,17 +963,16 @@ def test_create_ipip(mocked_generic_connection_create, capfd):
     assert args[0][2] == 'add'
     assert args[0][3] == 'type'
     assert args[0][4] == 'ip-tunnel'
-    assert args[0][5] == 'mode'
-    assert args[0][6] == 'ipip'
-    assert args[0][7] == 'con-name'
-    assert args[0][8] == 'non_existent_nw_device'
-    assert args[0][9] == 'ifname'
-    assert args[0][10] == 'ipip-existent_nw_device'
-    assert args[0][11] == 'dev'
-    assert args[0][12] == 'non_existent_ipip_device'
+    assert args[0][5] == 'con-name'
+    assert args[0][6] == 'non_existent_nw_device'
 
-    for param in ['ip-tunnel.local', '192.168.225.5', 'ip-tunnel.remote', '192.168.225.6']:
-        assert param in map(to_text, args[0])
+    args_text = list(map(to_text, args[0]))
+    for param in ['connection.interface-name', 'ipip-existent_nw_device',
+                  'ip-tunnel.local', '192.168.225.5',
+                  'ip-tunnel.mode', 'ipip',
+                  'ip-tunnel.parent', 'non_existent_ipip_device',
+                  'ip-tunnel.remote', '192.168.225.6']:
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -996,11 +994,12 @@ def test_ipip_mod(mocked_generic_connection_modify, capfd):
 
     assert args[0][0] == '/usr/bin/nmcli'
     assert args[0][1] == 'con'
-    assert args[0][2] == 'mod'
+    assert args[0][2] == 'modify'
     assert args[0][3] == 'non_existent_nw_device'
 
+    args_text = list(map(to_text, args[0]))
     for param in ['ip-tunnel.local', '192.168.225.5', 'ip-tunnel.remote', '192.168.225.6']:
-        assert param in map(to_text, args[0])
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -1039,17 +1038,16 @@ def test_create_sit(mocked_generic_connection_create, capfd):
     assert args[0][2] == 'add'
     assert args[0][3] == 'type'
     assert args[0][4] == 'ip-tunnel'
-    assert args[0][5] == 'mode'
-    assert args[0][6] == 'sit'
-    assert args[0][7] == 'con-name'
-    assert args[0][8] == 'non_existent_nw_device'
-    assert args[0][9] == 'ifname'
-    assert args[0][10] == 'sit-existent_nw_device'
-    assert args[0][11] == 'dev'
-    assert args[0][12] == 'non_existent_sit_device'
+    assert args[0][5] == 'con-name'
+    assert args[0][6] == 'non_existent_nw_device'
 
-    for param in ['ip-tunnel.local', '192.168.225.5', 'ip-tunnel.remote', '192.168.225.6']:
-        assert param in map(to_text, args[0])
+    args_text = list(map(to_text, args[0]))
+    for param in ['connection.interface-name', 'sit-existent_nw_device',
+                  'ip-tunnel.local', '192.168.225.5',
+                  'ip-tunnel.mode', 'sit',
+                  'ip-tunnel.parent', 'non_existent_sit_device',
+                  'ip-tunnel.remote', '192.168.225.6']:
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -1071,11 +1069,12 @@ def test_sit_mod(mocked_generic_connection_modify, capfd):
 
     assert args[0][0] == '/usr/bin/nmcli'
     assert args[0][1] == 'con'
-    assert args[0][2] == 'mod'
+    assert args[0][2] == 'modify'
     assert args[0][3] == 'non_existent_nw_device'
 
+    args_text = list(map(to_text, args[0]))
     for param in ['ip-tunnel.local', '192.168.225.5', 'ip-tunnel.remote', '192.168.225.6']:
-        assert param in map(to_text, args[0])
+        assert param in args_text
 
     out, err = capfd.readouterr()
     results = json.loads(out)
@@ -1145,10 +1144,10 @@ def test_modify_ethernet_dhcp_to_static(mocked_ethernet_connection_dhcp_to_stati
 
     assert args[0][0] == '/usr/bin/nmcli'
     assert args[0][1] == 'con'
-    assert args[0][2] == 'mod'
+    assert args[0][2] == 'modify'
     assert args[0][3] == 'non_existent_nw_device'
 
-    for param in ['ipv4.method', 'ipv4.gateway', 'ipv4.address']:
+    for param in ['ipv4.method', 'ipv4.gateway', 'ipv4.addresses']:
         assert param in args[0]
 
     out, err = capfd.readouterr()
@@ -1166,10 +1165,9 @@ def test_create_ethernet_static(mocked_generic_connection_create, capfd):
     with pytest.raises(SystemExit):
         nmcli.main()
 
-    assert nmcli.Nmcli.execute_command.call_count == 3
+    assert nmcli.Nmcli.execute_command.call_count == 2
     arg_list = nmcli.Nmcli.execute_command.call_args_list
     add_args, add_kw = arg_list[0]
-    mod_args, mod_kw = arg_list[1]
 
     assert add_args[0][0] == '/usr/bin/nmcli'
     assert add_args[0][1] == 'con'
@@ -1178,19 +1176,19 @@ def test_create_ethernet_static(mocked_generic_connection_create, capfd):
     assert add_args[0][4] == 'ethernet'
     assert add_args[0][5] == 'con-name'
     assert add_args[0][6] == 'non_existent_nw_device'
-    assert add_args[0][7] == 'ifname'
-    assert add_args[0][8] == 'ethernet_non_existant'
-
-    for param in ['ip4', '10.10.10.10/24', 'gw4', '10.10.10.1']:
-        assert param in map(to_text, add_args[0])
-
-    assert mod_args[0][0] == '/usr/bin/nmcli'
-    assert mod_args[0][1] == 'con'
-    assert mod_args[0][2] == 'mod'
-    assert mod_args[0][3] == 'non_existent_nw_device'
 
-    for param in ['ipv4.dns', '1.1.1.1 8.8.8.8']:
-        assert param in map(to_text, mod_args[0])
+    add_args_text = list(map(to_text, add_args[0]))
+    for param in ['connection.interface-name', 'ethernet_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']:
+        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)