Repository: ansible-collections/community.general

Issue:
 The mode parameter of community.general.nmcli does not take effect for team connection
### Summary

I want to create a connection of type team, mode is active-backup. But the setting does not take effect

### Issue Type

Bug Report

### Component Name

nmcli

### Ansible Version

```
ansible 2.10.6
python version = 3.7.6 (default, Dec 30 2019, 19:38:28) [Clang 11.0.0 (clang-1100.0.33.16)]

```


### Configuration

```paste below

```


### OS / Environment

_No response_

### Steps To Reproduce

```
- name: add team1
      community.general.nmcli:
        type: team
        conn_name: team1
        ip4: 1.1.1.1/24
        gw4: 1.1.1.254
        mode: active-backup
        state: present
```


### Expected Results

_No response_

### Actual Results

```
#nmcli con show team1


team.runner:                            roundrobin


```





Discussion:
Files identified in the description:
None

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 --->
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 --->
As far as I can see the mode is not used for configuring teaming devices.
Red Hat [documentation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/networking_guide/sec-configure_a_network_team_using-the_command_line) show that json format can be used to specify the configuration for the team.

I am considering implementing something like the sample below and then use `team_config` in json format in `nmcli.py`. Would you add checking for `team_config` parameters or just let nmcli validate it? What do you think?
```
- name: Add an Team connection with static IP configuration
  community.general.nmcli:
    conn_name: team2
    ifname: team2
    type: team
    ip4: 192.0.2.100/24
    gw4: 192.0.2.1
    state: present
    autoconnect: yes
    team_config:
      runner:
        name: activebackup
        hwaddr_policy: by_active
      link_watch:
        name: ethtool
```
@felixfontein what are your thoughts?
@omula I personally prefer strict validation, but I know that others do not want that. Since I'm not maintaining and using that module, my preferences are not that important :)

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
@@ -57,7 +57,7 @@
         choices: [ bond, bond-slave, bridge, bridge-slave, ethernet, generic, infiniband, ipip, sit, team, team-slave, vlan, vxlan, wifi ]
     mode:
         description:
-            - This is the type of device or network connection that you wish to create for a bond, team or bridge.
+            - This is the type of device or network connection that you wish to create for a bond or bridge.
         type: str
         choices: [ 802.3ad, active-backup, balance-alb, balance-rr, balance-tlb, balance-xor, broadcast ]
         default: balance-rr
@@ -265,6 +265,20 @@
               frame was received on.
         type: bool
         default: yes
+    runner:
+        description:
+            - This is the type of device or network connection that you wish to create for a team.
+        type: str
+        choices: [ broadcast, roundrobin, activebackup, loadbalance, lacp ]
+        default: roundrobin
+        version_added: 3.4.0
+    runner_hwaddr_policy:
+        description:
+            - This defines the policy of how hardware addresses of team device and port devices
+              should be set during the team lifetime.
+        type: str
+        choices: [ same_all, by_active, only_active ]
+        version_added: 3.4.0
     vlanid:
         description:
             - This is only used with VLAN - VLAN ID in range <0-4095>.
@@ -719,6 +733,8 @@ def __init__(self, module):
         self.hairpin = module.params['hairpin']
         self.path_cost = module.params['path_cost']
         self.mac = module.params['mac']
+        self.runner = module.params['runner']
+        self.runner_hwaddr_policy = module.params['runner_hwaddr_policy']
         self.vlanid = module.params['vlanid']
         self.vlandev = module.params['vlandev']
         self.flags = module.params['flags']
@@ -826,6 +842,11 @@ def connection_options(self, detect_change=False):
                 'bridge.priority': self.priority,
                 'bridge.stp': self.stp,
             })
+        elif self.type == 'team':
+            options.update({
+                'team.runner': self.runner,
+                'team.runner-hwaddr-policy': self.runner_hwaddr_policy,
+            })
         elif self.type == 'bridge-slave':
             options.update({
                 'connection.slave-type': 'bridge',
@@ -1214,6 +1235,11 @@ def main():
             ageingtime=dict(type='int', default=300),
             hairpin=dict(type='bool', default=True),
             path_cost=dict(type='int', default=100),
+            # team specific vars
+            runner=dict(type='str', default='roundrobin',
+                             choices=['broadcast', 'roundrobin', 'activebackup', 'loadbalance', 'lacp']),
+            # team active-backup runner specific options
+            runner_hwaddr_policy=dict(type='str', choices=['same_all', 'by_active', 'only_active']),
             # vlan specific vars
             vlanid=dict(type='int'),
             vlandev=dict(type='str'),
@@ -1245,6 +1271,10 @@ def main():
     # check for issues
     if nmcli.conn_name is None:
         nmcli.module.fail_json(msg="Please specify a name for the connection")
+    # team checks
+    if nmcli.type == "team":
+        if nmcli.runner_hwaddr_policy and not nmcli.runner == "activebackup":
+            nmcli.module.fail_json(msg="Runner-hwaddr-policy is only allowed for runner activebackup")
     # team-slave checks
     if nmcli.type == 'team-slave':
         if nmcli.master is None:


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
@@ -279,8 +279,20 @@
 ipv6.method:                            auto
 ipv6.ignore-auto-dns:                   no
 ipv6.ignore-auto-routes:                no
+team.runner:                            roundrobin
 """
 
+TESTCASE_TEAM_HWADDR_POLICY_FAILS = [
+    {
+        'type': 'team',
+        'conn_name': 'non_existent_nw_device',
+        'ifname': 'team0_non_existant',
+        'runner_hwaddr_policy': 'by_active',
+        'state': 'present',
+        '_ansible_check_mode': False,
+    }
+]
+
 TESTCASE_TEAM_SLAVE = [
     {
         'type': 'team-slave',
@@ -1053,6 +1065,20 @@ def test_team_connection_unchanged(mocked_team_connection_unchanged, capfd):
     assert not results['changed']
 
 
+@pytest.mark.parametrize('patch_ansible_module', TESTCASE_TEAM_HWADDR_POLICY_FAILS, indirect=['patch_ansible_module'])
+def test_team_connection_create_hwaddr_policy_fails(mocked_generic_connection_create, capfd):
+    """
+    Test : Team connection created
+    """
+    with pytest.raises(SystemExit):
+        nmcli.main()
+
+    out, err = capfd.readouterr()
+    results = json.loads(out)
+    assert results.get('failed')
+    assert results['msg'] == "Runner-hwaddr-policy is only allowed for runner activebackup"
+
+
 @pytest.mark.parametrize('patch_ansible_module', TESTCASE_TEAM_SLAVE, indirect=['patch_ansible_module'])
 def test_create_team_slave(mocked_generic_connection_create, capfd):
     """