From 88dcb11695a1c3fcd26bc2bcbda3e4e1bdd7a92f Mon Sep 17 00:00:00 2001 From: Susant Sahani <145210+ssahani@users.noreply.github.com> Date: Sat, 17 Nov 2018 10:27:11 +0530 Subject: [PATCH] nmcli: Introduce IPIP Tunnel (#45933) This work enables to add ipip tunnel via nmcli module ``` - nmcli: state: present type: ipip conn_name: ipip_test1 autoconnect: yes ip_tunnel_dev: enp0s8 ip_tunnel_local: 192.168.1.2 ip_tunnel_remote: 192.168.1.5 ``` version2: Added tests Signed-off-by: Susant Sahani Rebase --- lib/ansible/modules/net_tools/nmcli.py | 85 +++++++++++++++++++++- test/units/modules/net_tools/test_nmcli.py | 71 ++++++++++++++++++ 2 files changed, 154 insertions(+), 2 deletions(-) diff --git a/lib/ansible/modules/net_tools/nmcli.py b/lib/ansible/modules/net_tools/nmcli.py index ce25e2d5182..595f51e4c32 100644 --- a/lib/ansible/modules/net_tools/nmcli.py +++ b/lib/ansible/modules/net_tools/nmcli.py @@ -53,7 +53,7 @@ options: 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." - choices: [ ethernet, team, team-slave, bond, bond-slave, bridge, bridge-slave, vlan, vxlan, generic ] + choices: [ ethernet, team, team-slave, bond, bond-slave, bridge, bridge-slave, vlan, vxlan, ipip, generic ] mode: description: - This is the type of device or network connection that you wish to create for a bond, team or bridge. @@ -186,6 +186,18 @@ options: description: - This is only used with VXLAN - VXLAN local IP address. version_added: "2.8" + ip_tunnel_dev: + description: + - This is only used with IPIP - parent device this IPIP tunnel, can use ifname. + version_added: "2.8" + ip_tunnel_remote: + description: + - This is only used with IPIP - IPIP destination IP address. + version_added: "2.8" + ip_tunnel_local: + description: + - This is only used with IPIP - IPIP local IP address. + version_added: "2.8" ''' EXAMPLES = ''' @@ -453,6 +465,14 @@ EXAMPLES = ''' vxlan_local: 192.168.1.2 vxlan_remote: 192.168.1.5 +# To add ipip, issue a command as follows: + - nmcli: + type: ipip + conn_name: ipip_test1 + ip_tunnel_dev: eth0 + ip_tunnel_local: 192.168.1.2 + ip_tunnel_remote: 192.168.1.5 + # nmcli exits with status 0 if it succeeds and exits with a status greater # than zero when there is a failure. The following list of status codes may be # returned: @@ -526,6 +546,7 @@ class Nmcli(object): 14: "Generic", 15: "Team", 16: "VxLan", + 17: "ipip", } STATES = { 0: "Unknown", @@ -585,6 +606,9 @@ class Nmcli(object): self.vxlan_id = module.params['vxlan_id'] self.vxlan_local = module.params['vxlan_local'] self.vxlan_remote = module.params['vxlan_remote'] + self.ip_tunnel_dev = module.params['ip_tunnel_dev'] + self.ip_tunnel_local = module.params['ip_tunnel_local'] + self.ip_tunnel_remote = module.params['ip_tunnel_remote'] self.nmcli_bin = self.module.get_bin_path('nmcli', True) self.dhcp_client_id = module.params['dhcp_client_id'] @@ -1142,6 +1166,55 @@ class Nmcli(object): 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' % 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' % self.ipip_dev) + + if self.ip_tunnel_dev is not None: + cmd.append('dev') + cmd.append(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' % 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(self): cmd = [] if self.type == 'team': @@ -1189,6 +1262,8 @@ class Nmcli(object): 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 == 'generic': cmd = self.create_connection_ethernet(conn_type='generic') @@ -1223,6 +1298,8 @@ class Nmcli(object): 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 == 'generic': cmd = self.modify_connection_ethernet(conn_type='generic') if cmd: @@ -1244,7 +1321,7 @@ def main(): type=dict(required=False, default=None, choices=['ethernet', 'team', 'team-slave', 'bond', 'bond-slave', 'bridge', 'bridge-slave', - 'vlan', 'vxlan', 'generic'], + 'vlan', 'vxlan', 'ipip', 'generic'], type='str'), ip4=dict(required=False, default=None, type='str'), gw4=dict(required=False, default=None, type='str'), @@ -1287,6 +1364,10 @@ def main(): vxlan_id=dict(required=False, default=None, type='str'), vxlan_local=dict(required=False, default=None, type='str'), vxlan_remote=dict(required=False, default=None, type='str'), + # ip-tunnel specific vars + ip_tunnel_dev=dict(required=False, default=None, type='str'), + ip_tunnel_local=dict(required=False, default=None, type='str'), + ip_tunnel_remote=dict(required=False, default=None, type='str'), ), supports_check_mode=True ) diff --git a/test/units/modules/net_tools/test_nmcli.py b/test/units/modules/net_tools/test_nmcli.py index 8719a30fda6..45ec7ae2dda 100644 --- a/test/units/modules/net_tools/test_nmcli.py +++ b/test/units/modules/net_tools/test_nmcli.py @@ -58,6 +58,13 @@ TESTCASE_CONNECTION = [ 'state': 'absent', '_ansible_check_mode': True, }, + { + 'type': 'ipip', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, + ] TESTCASE_GENERIC = [ @@ -150,6 +157,19 @@ TESTCASE_VXLAN = [ } ] +TESTCASE_IPIP = [ + { + 'type': 'ipip', + 'conn_name': 'non_existent_nw_device', + 'ifname': 'ipip-existent_nw_device', + 'ip_tunnel_dev': 'non_existent_ipip_device', + 'ip_tunnel_local': '192.168.225.5', + 'ip_tunnel_remote': '192.168.225.6', + 'state': 'present', + '_ansible_check_mode': False, + } +] + TESTCASE_ETHERNET_DHCP = [ { 'type': 'ethernet', @@ -500,6 +520,57 @@ def test_vxlan_mod(mocked_generic_connection_modify): assert param in args[0] +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_IPIP, indirect=['patch_ansible_module']) +def test_create_ipip(mocked_generic_connection_create): + """ + Test if ipip 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] == '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' + + for param in ['ip-tunnel.local', '192.168.225.5', 'ip-tunnel.remote', '192.168.225.6']: + assert param in args[0] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_IPIP, indirect=['patch_ansible_module']) +def test_ipip_mod(mocked_generic_connection_modify): + """ + Test if ipip modified + """ + 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' + + for param in ['ip-tunnel.local', '192.168.225.5', 'ip-tunnel.remote', '192.168.225.6']: + assert param in args[0] + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_ETHERNET_DHCP, indirect=['patch_ansible_module']) def test_eth_dhcp_client_id_con_create(mocked_generic_connection_create): """