mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Support for creation and modification of bridge and bridge slaves for nmcli (#34071)
Fixes: #31737
This commit is contained in:
parent
b2ea28f1ad
commit
7fac5cd7d2
3 changed files with 231 additions and 5 deletions
|
@ -53,7 +53,7 @@ options:
|
||||||
- The ifname argument is mandatory for all connection types except bond, team, bridge and vlan.
|
- The ifname argument is mandatory for all connection types except bond, team, bridge and vlan.
|
||||||
type:
|
type:
|
||||||
required: False
|
required: False
|
||||||
choices: [ ethernet, team, team-slave, bond, bond-slave, bridge, vlan, generic ]
|
choices: [ ethernet, team, team-slave, bond, bond-slave, bridge, bridge-slave, vlan, generic ]
|
||||||
description:
|
description:
|
||||||
- This is the type of device or network connection that you wish to create or modify.
|
- 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."
|
- "type C(generic) is added in version 2.5."
|
||||||
|
@ -509,6 +509,10 @@ except ImportError:
|
||||||
HAVE_DBUS = False
|
HAVE_DBUS = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
import gi
|
||||||
|
gi.require_version('NMClient', '1.0')
|
||||||
|
gi.require_version('NetworkManager', '1.0')
|
||||||
|
|
||||||
from gi.repository import NetworkManager, NMClient
|
from gi.repository import NetworkManager, NMClient
|
||||||
HAVE_NM_CLIENT = True
|
HAVE_NM_CLIENT = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -598,6 +602,8 @@ class Nmcli(object):
|
||||||
self.hellotime = module.params['hellotime']
|
self.hellotime = module.params['hellotime']
|
||||||
self.maxage = module.params['maxage']
|
self.maxage = module.params['maxage']
|
||||||
self.ageingtime = module.params['ageingtime']
|
self.ageingtime = module.params['ageingtime']
|
||||||
|
self.hairpin = module.params['hairpin']
|
||||||
|
self.path_cost = module.params['path_cost']
|
||||||
self.mac = module.params['mac']
|
self.mac = module.params['mac']
|
||||||
self.vlanid = module.params['vlanid']
|
self.vlanid = module.params['vlanid']
|
||||||
self.vlandev = module.params['vlandev']
|
self.vlandev = module.params['vlandev']
|
||||||
|
@ -935,13 +941,110 @@ class Nmcli(object):
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
def create_connection_bridge(self):
|
def create_connection_bridge(self):
|
||||||
cmd = [self.nmcli_bin]
|
|
||||||
# format for creating bridge interface
|
# format for creating bridge interface
|
||||||
|
# To add an Bridge connection with static IP configuration, issue a command as follows
|
||||||
|
# - 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
|
return cmd
|
||||||
|
|
||||||
def modify_connection_bridge(self):
|
def modify_connection_bridge(self):
|
||||||
cmd = [self.nmcli_bin]
|
|
||||||
# format for modifying bridge interface
|
# format for modifying bridge interface
|
||||||
|
# To add an Bridge connection with static IP configuration, issue a command as follows
|
||||||
|
# - 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]
|
||||||
|
|
||||||
|
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 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
return cmd
|
||||||
|
|
||||||
def create_connection_vlan(self):
|
def create_connection_vlan(self):
|
||||||
|
@ -995,6 +1098,8 @@ class Nmcli(object):
|
||||||
cmd = self.create_connection_ethernet()
|
cmd = self.create_connection_ethernet()
|
||||||
elif self.type == 'bridge':
|
elif self.type == 'bridge':
|
||||||
cmd = self.create_connection_bridge()
|
cmd = self.create_connection_bridge()
|
||||||
|
elif self.type == 'bridge-slave':
|
||||||
|
cmd = self.create_connection_bridge_slave()
|
||||||
elif self.type == 'vlan':
|
elif self.type == 'vlan':
|
||||||
cmd = self.create_connection_vlan()
|
cmd = self.create_connection_vlan()
|
||||||
elif self.type == 'generic':
|
elif self.type == 'generic':
|
||||||
|
@ -1025,6 +1130,8 @@ class Nmcli(object):
|
||||||
cmd = self.modify_connection_ethernet()
|
cmd = self.modify_connection_ethernet()
|
||||||
elif self.type == 'bridge':
|
elif self.type == 'bridge':
|
||||||
cmd = self.modify_connection_bridge()
|
cmd = self.modify_connection_bridge()
|
||||||
|
elif self.type == 'bridge-slave':
|
||||||
|
cmd = self.modify_connection_bridge_slave()
|
||||||
elif self.type == 'vlan':
|
elif self.type == 'vlan':
|
||||||
cmd = self.modify_connection_vlan()
|
cmd = self.modify_connection_vlan()
|
||||||
elif self.type == 'generic':
|
elif self.type == 'generic':
|
||||||
|
@ -1047,7 +1154,8 @@ def main():
|
||||||
ifname=dict(required=False, default=None, type='str'),
|
ifname=dict(required=False, default=None, type='str'),
|
||||||
type=dict(required=False, default=None,
|
type=dict(required=False, default=None,
|
||||||
choices=['ethernet', 'team', 'team-slave', 'bond',
|
choices=['ethernet', 'team', 'team-slave', 'bond',
|
||||||
'bond-slave', 'bridge', 'vlan', 'generic'],
|
'bond-slave', 'bridge', 'bridge-slave',
|
||||||
|
'vlan', 'generic'],
|
||||||
type='str'),
|
type='str'),
|
||||||
ip4=dict(required=False, default=None, type='str'),
|
ip4=dict(required=False, default=None, type='str'),
|
||||||
gw4=dict(required=False, default=None, type='str'),
|
gw4=dict(required=False, default=None, type='str'),
|
||||||
|
@ -1077,6 +1185,8 @@ def main():
|
||||||
hellotime=dict(required=False, default="2", type='str'),
|
hellotime=dict(required=False, default="2", type='str'),
|
||||||
maxage=dict(required=False, default="20", type='str'),
|
maxage=dict(required=False, default="20", type='str'),
|
||||||
ageingtime=dict(required=False, default="300", type='str'),
|
ageingtime=dict(required=False, default="300", type='str'),
|
||||||
|
hairpin=dict(required=False, default=True, type='str'),
|
||||||
|
path_cost=dict(required=False, default="100", type='str'),
|
||||||
# vlan specific vars
|
# vlan specific vars
|
||||||
vlanid=dict(required=False, default=None, type='str'),
|
vlanid=dict(required=False, default=None, type='str'),
|
||||||
vlandev=dict(required=False, default=None, type='str'),
|
vlandev=dict(required=False, default=None, type='str'),
|
||||||
|
|
|
@ -264,7 +264,6 @@ lib/ansible/modules/net_tools/basics/uri.py E323
|
||||||
lib/ansible/modules/net_tools/cloudflare_dns.py E317
|
lib/ansible/modules/net_tools/cloudflare_dns.py E317
|
||||||
lib/ansible/modules/net_tools/haproxy.py E317
|
lib/ansible/modules/net_tools/haproxy.py E317
|
||||||
lib/ansible/modules/net_tools/ldap/ldap_attr.py E322
|
lib/ansible/modules/net_tools/ldap/ldap_attr.py E322
|
||||||
lib/ansible/modules/net_tools/nmcli.py E323
|
|
||||||
lib/ansible/modules/net_tools/omapi_host.py E317
|
lib/ansible/modules/net_tools/omapi_host.py E317
|
||||||
lib/ansible/modules/net_tools/omapi_host.py E322
|
lib/ansible/modules/net_tools/omapi_host.py E322
|
||||||
lib/ansible/modules/net_tools/snmp_facts.py E322
|
lib/ansible/modules/net_tools/snmp_facts.py E322
|
||||||
|
|
|
@ -93,6 +93,31 @@ TESTCASE_BOND = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
TESTCASE_BRIDGE = [
|
||||||
|
{
|
||||||
|
'type': 'bridge',
|
||||||
|
'conn_name': 'non_existent_nw_device',
|
||||||
|
'ifname': 'br0_non_existant',
|
||||||
|
'ip4': '10.10.10.10',
|
||||||
|
'gw4': '10.10.10.1',
|
||||||
|
'maxage': '100',
|
||||||
|
'stp': True,
|
||||||
|
'state': 'present',
|
||||||
|
'_ansible_check_mode': False,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
TESTCASE_BRIDGE_SLAVE = [
|
||||||
|
{
|
||||||
|
'type': 'bridge-slave',
|
||||||
|
'conn_name': 'non_existent_nw_device',
|
||||||
|
'ifname': 'br0_non_existant',
|
||||||
|
'path_cost': 100,
|
||||||
|
'state': 'present',
|
||||||
|
'_ansible_check_mode': False,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def mocker_set(mocker, connection_exists=False):
|
def mocker_set(mocker, connection_exists=False):
|
||||||
"""
|
"""
|
||||||
|
@ -243,3 +268,95 @@ def test_dns4_none(mocked_connection_exists, capfd):
|
||||||
out, err = capfd.readouterr()
|
out, err = capfd.readouterr()
|
||||||
results = json.loads(out)
|
results = json.loads(out)
|
||||||
assert results['changed']
|
assert results['changed']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_BRIDGE, indirect=['patch_ansible_module'])
|
||||||
|
def test_create_bridge(mocked_generic_connection_create):
|
||||||
|
"""
|
||||||
|
Test if Bridge 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] == 'bridge'
|
||||||
|
assert args[0][5] == 'con-name'
|
||||||
|
assert args[0][6] == 'non_existent_nw_device'
|
||||||
|
|
||||||
|
for param in ['ip4', '10.10.10.10', 'gw4', '10.10.10.1', 'bridge.max-age', '100', 'bridge.stp', 'yes']:
|
||||||
|
assert param in args[0]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_BRIDGE, indirect=['patch_ansible_module'])
|
||||||
|
def test_mod_bridge(mocked_generic_connection_modify):
|
||||||
|
"""
|
||||||
|
Test if Bridge 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 ['ip4', '10.10.10.10', 'gw4', '10.10.10.1', 'bridge.max-age', '100', 'bridge.stp', 'yes']:
|
||||||
|
assert param in args[0]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_BRIDGE_SLAVE, indirect=['patch_ansible_module'])
|
||||||
|
def test_create_bridge_slave(mocked_generic_connection_create):
|
||||||
|
"""
|
||||||
|
Test if Bridge_slave 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] == 'bridge-slave'
|
||||||
|
assert args[0][5] == 'con-name'
|
||||||
|
assert args[0][6] == 'non_existent_nw_device'
|
||||||
|
|
||||||
|
for param in ['bridge-port.path-cost', '100']:
|
||||||
|
assert param in args[0]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_BRIDGE_SLAVE, indirect=['patch_ansible_module'])
|
||||||
|
def test_mod_bridge_slave(mocked_generic_connection_modify):
|
||||||
|
"""
|
||||||
|
Test if Bridge_slave 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 ['bridge-port.path-cost', '100']:
|
||||||
|
assert param in args[0]
|
||||||
|
|
Loading…
Reference in a new issue