nmcli: Add ipv4.dns-search attribute (#34261)

This fix adds following -
* ipv4.dns-search attribute
* refactoring of code
* correct return status for up_connection
* documentation update

Fixes: #17843

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
pull/34864/merge
Abhijeet Kasurde 7 years ago committed by Martin Krizek
parent bbd6b8bb42
commit b7724fdf85

@ -24,6 +24,8 @@ requirements: [ nmcli, dbus, NetworkManager-glib ]
version_added: "2.0"
description:
- Manage the network devices. Create, modify and manage various connection and device type e.g., ethernet, teams, bonds, vlans etc.
- "On CentOS and Fedora like systems, install dependencies as 'yum/dnf install -y python-gobject NetworkManager-glib'"
- "On Ubuntu and Debian like systems, install dependencies as 'apt-get install -y libnm-glib-dev'"
options:
state:
required: True
@ -80,6 +82,12 @@ options:
default: None
description:
- 'A list of upto 3 dns servers, ipv4 format e.g. To add two IPv4 DNS server addresses: "192.0.2.53 198.51.100.53"'
dns4_search:
required: False
default: None
description:
- 'A list of DNS search domains.'
version_added: 2.5
ip6:
required: False
default: None
@ -94,6 +102,12 @@ options:
required: False
description:
- 'A list of upto 3 dns servers, ipv6 format e.g. To add two IPv6 DNS server addresses: "2001:4860:4860::8888 2001:4860:4860::8844"'
dns6_search:
required: False
default: None
description:
- 'A list of DNS search domains.'
version_added: 2.5
mtu:
required: False
default: 1500
@ -485,19 +499,20 @@ EXAMPLES = '''
# - 10 Connection, device, or access point does not exist.
'''
HAVE_DBUS = False
RETURN = r"""#
"""
try:
import dbus
HAVE_DBUS = True
except ImportError:
pass
HAVE_DBUS = False
HAVE_NM_CLIENT = False
try:
from gi.repository import NetworkManager, NMClient
HAVE_NM_CLIENT = True
except ImportError:
pass
HAVE_NM_CLIENT = False
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
@ -521,7 +536,8 @@ class Nmcli(object):
if HAVE_DBUS:
bus = dbus.SystemBus()
# The following is going to be used in dbus code
DEVTYPES = {1: "Ethernet",
DEVTYPES = {
1: "Ethernet",
2: "Wi-Fi",
5: "Bluetooth",
6: "OLPC",
@ -535,7 +551,8 @@ class Nmcli(object):
14: "Generic",
15: "Team"
}
STATES = {0: "Unknown",
STATES = {
0: "Unknown",
10: "Unmanaged",
20: "Unavailable",
30: "Disconnected",
@ -561,9 +578,11 @@ class Nmcli(object):
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.ip6 = module.params['ip6']
self.gw6 = module.params['gw6']
self.dns6 = module.params['dns6']
self.dns6_search = ' '.join(module.params['dns6_search']) if module.params.get('dns6_search') else None
self.mtu = module.params['mtu']
self.stp = module.params['stp']
self.priority = module.params['priority']
@ -633,7 +652,8 @@ class Nmcli(object):
setting_list.append(self.dict_to_string(config[setting_name]))
return setting_list
def bool_to_string(self, boolean):
@staticmethod
def bool_to_string(boolean):
if boolean:
return "yes"
else:
@ -689,7 +709,6 @@ class Nmcli(object):
def down_connection(self):
cmd = [self.nmcli_bin, 'con', 'down', self.conn_name]
# if self.connection_exists():
return self.execute_command(cmd)
def up_connection(self):
@ -708,48 +727,40 @@ class Nmcli(object):
cmd.append(self.ifname)
elif self.conn_name is not None:
cmd.append(self.conn_name)
if self.ip4 is not None:
cmd.append('ip4')
cmd.append(self.ip4)
if self.gw4 is not None:
cmd.append('gw4')
cmd.append(self.gw4)
if self.ip6 is not None:
cmd.append('ip6')
cmd.append(self.ip6)
if self.gw6 is not None:
cmd.append('gw6')
cmd.append(self.gw6)
if self.autoconnect is not None:
cmd.append('autoconnect')
cmd.append(self.bool_to_string(self.autoconnect))
options = {
'ipv4.address': self.ip4,
'ipv4.gateway': self.gw4,
'ipv6.address': self.ip6,
'ipv6.gateway': self.gw6,
'autoconnect': self.bool_to_string(self.autoconnect),
'ipv4.dns-search': self.dns4_search,
'ipv6.dns-search': self.dns6_search,
}
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]
# format for modifying team interface
if self.ip4 is not None:
cmd.append('ipv4.address')
cmd.append(self.ip4)
if self.gw4 is not None:
cmd.append('ipv4.gateway')
cmd.append(self.gw4)
if self.dns4 is not None:
cmd.append('ipv4.dns')
cmd.append(self.dns4)
if self.ip6 is not None:
cmd.append('ipv6.address')
cmd.append(self.ip6)
if self.gw6 is not None:
cmd.append('ipv6.gateway')
cmd.append(self.gw6)
if self.dns6 is not None:
cmd.append('ipv6.dns')
cmd.append(self.dns6)
if self.autoconnect is not None:
cmd.append('autoconnect')
cmd.append(self.bool_to_string(self.autoconnect))
# Can't use MTU with team
options = {
'ipv4.address': self.ip4,
'ipv4.gateway': self.gw4,
'ipv4.dns': self.dns4,
'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,
}
for key, value in options.items():
if value is not None:
cmd.extend([key, value])
return cmd
def create_connection_team_slave(self):
@ -767,9 +778,6 @@ class Nmcli(object):
cmd.append('master')
if self.conn_name is not None:
cmd.append(self.master)
# if self.mtu is not None:
# cmd.append('802-3-ethernet.mtu')
# cmd.append(self.mtu)
return cmd
def modify_connection_team_slave(self):
@ -792,68 +800,53 @@ class Nmcli(object):
cmd.append(self.ifname)
elif self.conn_name is not None:
cmd.append(self.conn_name)
if self.mode is not None:
cmd.append('mode')
cmd.append(self.mode)
if self.ip4 is not None:
cmd.append('ip4')
cmd.append(self.ip4)
if self.gw4 is not None:
cmd.append('gw4')
cmd.append(self.gw4)
if self.ip6 is not None:
cmd.append('ip6')
cmd.append(self.ip6)
if self.gw6 is not None:
cmd.append('gw6')
cmd.append(self.gw6)
if self.autoconnect is not None:
cmd.append('autoconnect')
cmd.append(self.bool_to_string(self.autoconnect))
if self.miimon is not None:
cmd.append('miimon')
cmd.append(self.miimon)
if self.downdelay is not None:
cmd.append('downdelay')
cmd.append(self.downdelay)
if self.downdelay is not None:
cmd.append('updelay')
cmd.append(self.updelay)
if self.downdelay is not None:
cmd.append('arp-interval')
cmd.append(self.arp_interval)
if self.downdelay is not None:
cmd.append('arp-ip-target')
cmd.append(self.arp_ip_target)
if self.primary is not None:
cmd.append('primary')
cmd.append(self.primary)
options = {
'mode': self.mode,
'ipv4.address': self.ip4,
'ipv4.gateway': self.gw4,
'ipv6.address': self.ip6,
'ipv6.gateway': 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,
}
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
if self.ip4 is not None:
cmd.append('ipv4.address')
cmd.append(self.ip4)
if self.gw4 is not None:
cmd.append('ipv4.gateway')
cmd.append(self.gw4)
if self.dns4 is not None:
cmd.append('ipv4.dns')
cmd.append(self.dns4)
if self.ip6 is not None:
cmd.append('ipv6.address')
cmd.append(self.ip6)
if self.gw6 is not None:
cmd.append('ipv6.gateway')
cmd.append(self.gw6)
if self.dns6 is not None:
cmd.append('ipv6.dns')
cmd.append(self.dns6)
if self.autoconnect is not None:
cmd.append('autoconnect')
cmd.append(self.bool_to_string(self.autoconnect))
options = {
'ipv4.address': self.ip4,
'ipv4.gateway': self.gw4,
'ipv4.dns': self.dns4,
'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,
}
for key, value in options.items():
if value is not None:
cmd.extend([key, value])
return cmd
def create_connection_bond_slave(self):
@ -898,21 +891,20 @@ class Nmcli(object):
cmd.append(self.ifname)
elif self.conn_name is not None:
cmd.append(self.conn_name)
if self.ip4 is not None:
cmd.append('ip4')
cmd.append(self.ip4)
if self.gw4 is not None:
cmd.append('gw4')
cmd.append(self.gw4)
if self.ip6 is not None:
cmd.append('ip6')
cmd.append(self.ip6)
if self.gw6 is not None:
cmd.append('gw6')
cmd.append(self.gw6)
if self.autoconnect is not None:
cmd.append('autoconnect')
cmd.append(self.bool_to_string(self.autoconnect))
options = {
'ipv4.address': self.ip4,
'ipv4.gateway': self.gw4,
'ipv6.address': self.ip6,
'ipv6.gateway': self.gw6,
'autoconnect': self.bool_to_string(self.autoconnect),
'ipv4.dns-search': self.dns4_search,
'ipv6.dns-search': self.dns6_search,
}
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'):
@ -921,30 +913,25 @@ class Nmcli(object):
# 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)
if self.gw4 is not None:
cmd.append('ipv4.gateway')
cmd.append(self.gw4)
if self.dns4 is not None:
cmd.append('ipv4.dns')
cmd.append(self.dns4)
if self.ip6 is not None:
cmd.append('ipv6.address')
cmd.append(self.ip6)
if self.gw6 is not None:
cmd.append('ipv6.gateway')
cmd.append(self.gw6)
if self.dns6 is not None:
cmd.append('ipv6.dns')
cmd.append(self.dns6)
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:
cmd.append('autoconnect')
cmd.append(self.bool_to_string(self.autoconnect))
options = {
'ipv4.address': self.ip4,
'ipv4.gateway': self.gw4,
'ipv4.dns': self.dns4,
'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,
}
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):
@ -970,13 +957,12 @@ class Nmcli(object):
def create_connection(self):
cmd = []
if self.type == 'team':
# cmd=self.create_connection_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)
cmd = self.up_connection()
return self.up_connection()
elif (self.dns4 is None) or (self.dns6 is None):
cmd = self.create_connection_team()
elif self.type == 'team-slave':
@ -984,8 +970,7 @@ class Nmcli(object):
cmd = self.create_connection_team_slave()
self.execute_command(cmd)
cmd = self.modify_connection_team_slave()
self.execute_command(cmd)
# cmd=self.up_connection()
return self.execute_command(cmd)
else:
cmd = self.create_connection_team_slave()
elif self.type == 'bond':
@ -994,7 +979,7 @@ class Nmcli(object):
self.execute_command(cmd)
cmd = self.modify_connection_bond()
self.execute_command(cmd)
cmd = self.up_connection()
return self.up_connection()
else:
cmd = self.create_connection_bond()
elif self.type == 'bond-slave':
@ -1005,7 +990,7 @@ class Nmcli(object):
self.execute_command(cmd)
cmd = self.modify_connection_ethernet()
self.execute_command(cmd)
cmd = self.up_connection()
return self.up_connection()
else:
cmd = self.create_connection_ethernet()
elif self.type == 'bridge':
@ -1067,9 +1052,11 @@ def main():
ip4=dict(required=False, default=None, type='str'),
gw4=dict(required=False, default=None, type='str'),
dns4=dict(required=False, default=None, type='list'),
dns4_search=dict(type='list'),
ip6=dict(required=False, default=None, type='str'),
gw6=dict(required=False, default=None, type='str'),
dns6=dict(required=False, default=None, type='str'),
dns6_search=dict(type='list'),
# Bond Specific vars
mode=dict(require=False, default="balance-rr", type='str', choices=["balance-rr", "active-backup", "balance-xor", "broadcast", "802.3ad",
"balance-tlb", "balance-alb"]),

@ -62,6 +62,20 @@ TESTCASE_GENERIC = [
'gw4': '10.10.10.1',
'state': 'present',
'_ansible_check_mode': False,
},
]
TESTCASE_GENERIC_DNS4_SEARCH = [
{
'type': 'generic',
'conn_name': 'non_existent_nw_device',
'ifname': 'generic_non_existant',
'ip4': '10.10.10.10',
'gw4': '10.10.10.1',
'state': 'present',
'dns4_search': 'search.redhat.com',
'dns6_search': 'search6.redhat.com',
'_ansible_check_mode': False,
}
]
@ -101,6 +115,20 @@ def mocked_generic_connection_create(mocker):
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_BOND, indirect=['patch_ansible_module'])
def test_bond_connection_create(mocked_generic_connection_create):
"""
@ -122,49 +150,9 @@ def test_bond_connection_create(mocked_generic_connection_create):
assert args[0][6] == 'non_existent_nw_device'
assert args[0][7] == 'ifname'
assert args[0][8] == 'bond_non_existant'
assert args[0][9] == 'mode'
assert args[0][10] == 'active-backup'
assert args[0][11] == 'ip4'
assert args[0][12] == '10.10.10.10'
assert args[0][13] == 'gw4'
assert args[0][14] == '10.10.10.1'
assert args[0][15] == 'primary'
assert args[0][16] == 'non_existent_primary'
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 = connection_exists
return connection
@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
for param in ['ipv4.gateway', 'primary', 'autoconnect', 'mode', 'active-backup', 'ipv4.address']:
assert param in args[0]
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC, indirect=['patch_ansible_module'])
@ -186,10 +174,9 @@ def test_generic_connection_create(mocked_generic_connection_create):
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'
for param in ['autoconnect', 'ipv4.gateway', 'ipv4.address']:
assert param in args[0]
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC, indirect=['patch_ansible_module'])
@ -208,10 +195,41 @@ def test_generic_connection_modify(mocked_generic_connection_modify):
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'
for param in ['ipv4.gateway', 'ipv4.address']:
assert param in args[0]
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC_DNS4_SEARCH, indirect=['patch_ansible_module'])
def test_generic_connection_create_dns_search(mocked_generic_connection_create):
"""
Test : Generic connection created with dns search
"""
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 'ipv4.dns-search' in args[0]
assert 'ipv6.dns-search' in args[0]
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC_DNS4_SEARCH, indirect=['patch_ansible_module'])
def test_generic_connection_modify_dns_search(mocked_generic_connection_create):
"""
Test : Generic connection modified with dns search
"""
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 'ipv4.dns-search' in args[0]
assert 'ipv6.dns-search' in args[0]
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_CONNECTION, indirect=['patch_ansible_module'])

Loading…
Cancel
Save