From 5496326c4dd4f9a198a561e8b550c51d3f94be8d Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Tue, 2 Jan 2018 17:07:22 +0530 Subject: [PATCH] nmcli: Add 'generic' connection type (#34251) This fix adds 'generic' connection type in nmcli module. Fixes: #33648 Signed-off-by: Abhijeet Kasurde --- lib/ansible/modules/net_tools/nmcli.py | 47 +++++--- test/units/modules/net_tools/test_nmcli.py | 131 +++++++++++++++++++-- 2 files changed, 155 insertions(+), 23 deletions(-) diff --git a/lib/ansible/modules/net_tools/nmcli.py b/lib/ansible/modules/net_tools/nmcli.py index 947f2b4eae6..1896eb79426 100644 --- a/lib/ansible/modules/net_tools/nmcli.py +++ b/lib/ansible/modules/net_tools/nmcli.py @@ -1,16 +1,18 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# (c) 2015, Chris Long +# Copyright: (c) 2015, Chris Long +# Copyright: (c) 2017, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} DOCUMENTATION = ''' @@ -21,7 +23,7 @@ short_description: Manage Networking requirements: [ nmcli, dbus, NetworkManager-glib ] version_added: "2.0" description: - - Manage the network devices. Create, modify, and manage, ethernet, teams, bonds, vlans etc. + - Manage the network devices. Create, modify and manage various connection and device type e.g., ethernet, teams, bonds, vlans etc. options: state: required: True @@ -49,9 +51,10 @@ options: - The ifname argument is mandatory for all connection types except bond, team, bridge and vlan. type: required: False - choices: [ ethernet, team, team-slave, bond, bond-slave, bridge, vlan ] + choices: [ ethernet, team, team-slave, bond, bond-slave, bridge, vlan, generic ] description: - This is the type of device or network connection that you wish to create or modify. + - "type C(generic) is added in version 2.5." mode: required: False choices: [ "balance-rr", "active-backup", "balance-xor", "broadcast", "802.3ad", "balance-tlb", "balance-alb" ] @@ -864,12 +867,17 @@ class Nmcli(object): # format for modifying bond-slave interface return cmd - def create_connection_ethernet(self): - cmd = [self.nmcli_bin, 'con', 'add', 'type', 'ethernet', 'con-name'] + 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 # - 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: @@ -896,12 +904,12 @@ class Nmcli(object): cmd.append(self.bool_to_string(self.autoconnect)) return cmd - def modify_connection_ethernet(self): + def modify_connection_ethernet(self, conn_type='ethernet'): cmd = [self.nmcli_bin, 'con', 'mod', self.conn_name] - # format for modifying ethernet interface - # To add an Ethernet connection with static IP configuration, issue a command as follows - # - 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 + # format for modifying ethernet interface + # To modify an Ethernet connection with static IP configuration, issue a command as follows + # - 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 ip4 192.0.2.100/24 gw4 192.0.2.1 if self.ip4 is not None: cmd.append('ipv4.address') cmd.append(self.ip4) @@ -920,7 +928,7 @@ class Nmcli(object): if self.dns6 is not None: cmd.append('ipv6.dns') cmd.append(self.dns6) - if self.mtu is not None: + if self.mtu is not None and conn_type == 'ethernet': cmd.append('802-3-ethernet.mtu') cmd.append(self.mtu) if self.autoconnect is not None: @@ -993,6 +1001,8 @@ class Nmcli(object): cmd = self.create_connection_bridge() elif self.type == 'vlan': cmd = self.create_connection_vlan() + elif self.type == 'generic': + cmd = self.create_connection_ethernet(conn_type='generic') if cmd: return self.execute_command(cmd) @@ -1021,6 +1031,8 @@ class Nmcli(object): cmd = self.modify_connection_bridge() elif self.type == 'vlan': cmd = self.modify_connection_vlan() + elif self.type == 'generic': + cmd = self.modify_connection_ethernet(conn_type='generic') if cmd: return self.execute_command(cmd) else: @@ -1037,7 +1049,10 @@ def main(): conn_name=dict(required=True, type='str'), master=dict(required=False, default=None, type='str'), ifname=dict(required=False, default=None, type='str'), - type=dict(required=False, default=None, choices=['ethernet', 'team', 'team-slave', 'bond', 'bond-slave', 'bridge', 'vlan'], type='str'), + type=dict(required=False, default=None, + choices=['ethernet', 'team', 'team-slave', 'bond', + 'bond-slave', 'bridge', 'vlan', 'generic'], + type='str'), ip4=dict(required=False, default=None, type='str'), gw4=dict(required=False, default=None, type='str'), dns4=dict(required=False, default=None, type='list'), diff --git a/test/units/modules/net_tools/test_nmcli.py b/test/units/modules/net_tools/test_nmcli.py index 55eb5f48b59..fd66d007979 100644 --- a/test/units/modules/net_tools/test_nmcli.py +++ b/test/units/modules/net_tools/test_nmcli.py @@ -8,30 +8,147 @@ from ansible.modules.net_tools import nmcli pytestmark = pytest.mark.usefixtures('patch_ansible_module') -TESTCASE = [ +TESTCASE_CONNECTION = [ { 'type': 'ethernet', 'conn_name': 'non_existent_nw_device', 'state': 'absent', '_ansible_check_mode': True, + }, + { + 'type': 'generic', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, + { + 'type': 'team', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, + { + 'type': 'bond', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, + { + 'type': 'bond-slave', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, + { + 'type': 'bridge', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, + { + 'type': 'vlan', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, +] + +TESTCASE_GENERIC = [ + { + 'type': 'generic', + 'conn_name': 'non_existent_nw_device', + 'ifname': 'generic_non_existant', + 'ip4': '10.10.10.10', + 'gw4': '10.10.10.1', + 'state': 'present', + '_ansible_check_mode': False, } ] -@pytest.fixture -def mocked_connection_exists(mocker): +def mocker_set(mocker, connection_exists=False): + """ + Common mocker object + """ mocker.patch('ansible.modules.net_tools.nmcli.HAVE_DBUS', True) mocker.patch('ansible.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 = True + connection.return_value = connection_exists return connection -@pytest.mark.parametrize('patch_ansible_module', TESTCASE, indirect=['patch_ansible_module']) +@pytest.fixture +def mocked_generic_connection_create(mocker): + mocker_set(mocker) + command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command') + command_result.return_value = {"rc": 100, "out": "aaa", "err": "none"} + return command_result + + +@pytest.fixture +def mocked_generic_connection_modify(mocker): + mocker_set(mocker, connection_exists=True) + command_result = mocker.patch.object(nmcli.Nmcli, 'execute_command') + command_result.return_value = {"rc": 100, "out": "aaa", "err": "none"} + return command_result + + +@pytest.fixture +def mocked_connection_exists(mocker): + connection = mocker_set(mocker, connection_exists=True) + return connection + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC, indirect=['patch_ansible_module']) +def test_generic_connection_create(mocked_generic_connection_create): + """ + Test : Generic connection created + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert args[0][0] == '/usr/bin/nmcli' + assert args[0][1] == 'con' + assert args[0][2] == 'add' + assert args[0][3] == 'type' + assert args[0][4] == 'generic' + assert args[0][5] == 'con-name' + assert args[0][6] == 'non_existent_nw_device' + assert args[0][9] == 'ip4' + assert args[0][10] == '10.10.10.10' + assert args[0][11] == 'gw4' + assert args[0][12] == '10.10.10.1' + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC, indirect=['patch_ansible_module']) +def test_generic_connection_modify(mocked_generic_connection_modify): + """ + Test : Generic connection modify + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert args[0][0] == '/usr/bin/nmcli' + assert args[0][1] == 'con' + assert args[0][2] == 'mod' + assert args[0][3] == 'non_existent_nw_device' + assert args[0][4] == 'ipv4.address' + assert args[0][5] == '10.10.10.10' + assert args[0][6] == 'ipv4.gateway' + assert args[0][7] == '10.10.10.1' + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_CONNECTION, indirect=['patch_ansible_module']) def test_dns4_none(mocked_connection_exists, capfd): """ Test if DNS4 param is None