mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
- Adds sub-interface support for nxos_interface and nxos_ip_interface (#30830)
- Support dot1 encapsulation on routed sub-interface
This commit is contained in:
parent
4553ec5cc5
commit
4c21563ac6
3 changed files with 95 additions and 26 deletions
|
@ -160,7 +160,7 @@ def is_default_interface(interface, module):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
body = execute_show_command(command, module)[0]
|
body = execute_show_command(command, module)[0]
|
||||||
except IndexError:
|
except (IndexError, TypeError) as e:
|
||||||
body = ''
|
body = ''
|
||||||
|
|
||||||
if body:
|
if body:
|
||||||
|
@ -276,7 +276,6 @@ def get_interface(intf, module):
|
||||||
body = execute_show_command(command, module)[0]
|
body = execute_show_command(command, module)[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
body = []
|
body = []
|
||||||
|
|
||||||
if body:
|
if body:
|
||||||
interface_table = body['TABLE_interface']['ROW_interface']
|
interface_table = body['TABLE_interface']['ROW_interface']
|
||||||
if interface_table.get('eth_mode') == 'fex-fabric':
|
if interface_table.get('eth_mode') == 'fex-fabric':
|
||||||
|
@ -343,8 +342,6 @@ def get_interface(intf, module):
|
||||||
temp_dict['description'] = "None"
|
temp_dict['description'] = "None"
|
||||||
interface.update(temp_dict)
|
interface.update(temp_dict)
|
||||||
|
|
||||||
interface['type'] = intf_type
|
|
||||||
|
|
||||||
return interface
|
return interface
|
||||||
|
|
||||||
|
|
||||||
|
@ -526,7 +523,6 @@ def get_proposed(existing, normalized_interface, args):
|
||||||
|
|
||||||
|
|
||||||
def smart_existing(module, intf_type, normalized_interface):
|
def smart_existing(module, intf_type, normalized_interface):
|
||||||
|
|
||||||
# 7K BUG MAY CAUSE THIS TO FAIL
|
# 7K BUG MAY CAUSE THIS TO FAIL
|
||||||
|
|
||||||
all_interfaces = get_interfaces_dict(module)
|
all_interfaces = get_interfaces_dict(module)
|
||||||
|
@ -534,12 +530,13 @@ def smart_existing(module, intf_type, normalized_interface):
|
||||||
existing = get_interface(normalized_interface, module)
|
existing = get_interface(normalized_interface, module)
|
||||||
is_default = is_default_interface(normalized_interface, module)
|
is_default = is_default_interface(normalized_interface, module)
|
||||||
else:
|
else:
|
||||||
if intf_type == 'ethernet':
|
if (intf_type in ['loopback', 'portchannel', 'svi', 'nve'] or
|
||||||
module.fail_json(msg='Invalid Ethernet interface provided.',
|
intf_type == 'ethernet' and "." in normalized_interface):
|
||||||
interface=normalized_interface)
|
|
||||||
elif intf_type in ['loopback', 'portchannel', 'svi', 'nve']:
|
|
||||||
existing = {}
|
existing = {}
|
||||||
is_default = 'DNE'
|
is_default = 'DNE'
|
||||||
|
elif intf_type == 'ethernet':
|
||||||
|
module.fail_json(msg='Invalid Ethernet interface provided.',
|
||||||
|
interface=normalized_interface)
|
||||||
return existing, is_default
|
return existing, is_default
|
||||||
|
|
||||||
|
|
||||||
|
@ -554,7 +551,10 @@ def execute_show_command(command, module):
|
||||||
}]
|
}]
|
||||||
|
|
||||||
body = run_commands(module, cmds)
|
body = run_commands(module, cmds)
|
||||||
return body
|
if body and "Invalid" in body[0]:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
def flatten_list(command_lists):
|
def flatten_list(command_lists):
|
||||||
|
@ -627,9 +627,13 @@ def main():
|
||||||
module.fail_json(msg='The ip_forward and '
|
module.fail_json(msg='The ip_forward and '
|
||||||
'fabric_forwarding_anycast_gateway features '
|
'fabric_forwarding_anycast_gateway features '
|
||||||
' are only available for SVIs.')
|
' are only available for SVIs.')
|
||||||
|
|
||||||
args = dict(interface=interface, admin_state=admin_state,
|
args = dict(interface=interface, admin_state=admin_state,
|
||||||
description=description, mode=mode, ip_forward=ip_forward,
|
description=description, mode=mode, ip_forward=ip_forward,
|
||||||
fabric_forwarding_anycast_gateway=fabric_forwarding_anycast_gateway)
|
fabric_forwarding_anycast_gateway=fabric_forwarding_anycast_gateway)
|
||||||
|
if (normalized_interface.startswith('Eth') or normalized_interface.startswith('po'))\
|
||||||
|
and "." in normalized_interface:
|
||||||
|
args["mode"] = None
|
||||||
|
|
||||||
if intf_type == 'unknown':
|
if intf_type == 'unknown':
|
||||||
module.fail_json(msg='unknown interface type found-1',
|
module.fail_json(msg='unknown interface type found-1',
|
||||||
|
@ -650,10 +654,12 @@ def main():
|
||||||
if is_default != 'DNE':
|
if is_default != 'DNE':
|
||||||
cmds = ['no interface {0}'.format(normalized_interface)]
|
cmds = ['no interface {0}'.format(normalized_interface)]
|
||||||
commands.append(cmds)
|
commands.append(cmds)
|
||||||
elif intf_type in ['ethernet']:
|
elif intf_type in ['ethernet'] and is_default is False:
|
||||||
if is_default is False:
|
if "." in normalized_interface:
|
||||||
|
cmds = ['no interface {0}'.format(normalized_interface)]
|
||||||
|
else:
|
||||||
cmds = ['default interface {0}'.format(normalized_interface)]
|
cmds = ['default interface {0}'.format(normalized_interface)]
|
||||||
commands.append(cmds)
|
commands.append(cmds)
|
||||||
elif state == 'present':
|
elif state == 'present':
|
||||||
if not existing:
|
if not existing:
|
||||||
cmds = get_interface_config_commands(proposed, normalized_interface, existing)
|
cmds = get_interface_config_commands(proposed, normalized_interface, existing)
|
||||||
|
|
|
@ -54,6 +54,12 @@ options:
|
||||||
- Subnet mask for IPv4 or IPv6 Address in decimal format.
|
- Subnet mask for IPv4 or IPv6 Address in decimal format.
|
||||||
required: false
|
required: false
|
||||||
default: null
|
default: null
|
||||||
|
dot1q:
|
||||||
|
description:
|
||||||
|
- Configures IEEE 802.1Q VLAN encapsulation on the subinterface. The range is from 2 to 4093.
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
version_added: "2.5"
|
||||||
tag:
|
tag:
|
||||||
description:
|
description:
|
||||||
- Route tag for IPv4 or IPv6 Address in integer format.
|
- Route tag for IPv4 or IPv6 Address in integer format.
|
||||||
|
@ -105,6 +111,16 @@ EXAMPLES = '''
|
||||||
addr: 20.20.20.20
|
addr: 20.20.20.20
|
||||||
mask: 24
|
mask: 24
|
||||||
|
|
||||||
|
- name: Ensure ipv4 address is configured on sub-intf with dot1q encapsulation
|
||||||
|
nxos_ip_interface:
|
||||||
|
interface: Ethernet1/32.10
|
||||||
|
transport: nxapi
|
||||||
|
version: v4
|
||||||
|
state: present
|
||||||
|
dot1q: 10
|
||||||
|
addr: 20.20.20.20
|
||||||
|
mask: 24
|
||||||
|
|
||||||
- name: Configure ipv4 address as secondary if needed
|
- name: Configure ipv4 address as secondary if needed
|
||||||
nxos_ip_interface:
|
nxos_ip_interface:
|
||||||
interface: Ethernet1/32
|
interface: Ethernet1/32
|
||||||
|
@ -322,6 +338,35 @@ def parse_unstructured_data(body, interface_name, version, module):
|
||||||
return interface
|
return interface
|
||||||
|
|
||||||
|
|
||||||
|
def parse_interface_data(body):
|
||||||
|
body = body[0]
|
||||||
|
splitted_body = body.split('\n')
|
||||||
|
|
||||||
|
for index in range(0, len(splitted_body) - 1):
|
||||||
|
if "Encapsulation 802.1Q" in splitted_body[index]:
|
||||||
|
regex = '(.+?ID\s(?P<dot1q>\d+).*)?'
|
||||||
|
match = re.match(regex, splitted_body[index])
|
||||||
|
if match:
|
||||||
|
match_dict = match.groupdict()
|
||||||
|
if match_dict['dot1q'] is not None:
|
||||||
|
return int(match_dict['dot1q'])
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_dot1q_id(interface_name, module):
|
||||||
|
|
||||||
|
if "." not in interface_name:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
command = 'show interface {0}'.format(interface_name)
|
||||||
|
try:
|
||||||
|
body = execute_show_command(command, module)
|
||||||
|
dot1q = parse_interface_data(body)
|
||||||
|
return dot1q
|
||||||
|
except KeyError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def get_ip_interface(interface_name, version, module):
|
def get_ip_interface(interface_name, version, module):
|
||||||
body = send_show_command(interface_name, version, module)
|
body = send_show_command(interface_name, version, module)
|
||||||
interface = parse_unstructured_data(body, interface_name, version, module)
|
interface = parse_unstructured_data(body, interface_name, version, module)
|
||||||
|
@ -329,7 +374,7 @@ def get_ip_interface(interface_name, version, module):
|
||||||
|
|
||||||
|
|
||||||
def get_remove_ip_config_commands(interface, addr, mask, existing, version):
|
def get_remove_ip_config_commands(interface, addr, mask, existing, version):
|
||||||
commands = ['interface {0}'.format(interface)]
|
commands = []
|
||||||
if version == 'v4':
|
if version == 'v4':
|
||||||
# We can't just remove primary address if secondary address exists
|
# We can't just remove primary address if secondary address exists
|
||||||
for address in existing['addresses']:
|
for address in existing['addresses']:
|
||||||
|
@ -398,10 +443,6 @@ def get_config_ip_commands(delta, interface, existing, version):
|
||||||
commands += get_remove_ip_config_commands(interface, delta['addr'], delta['mask'], existing, version)
|
commands += get_remove_ip_config_commands(interface, delta['addr'], delta['mask'], existing, version)
|
||||||
|
|
||||||
commands.append(command)
|
commands.append(command)
|
||||||
|
|
||||||
if commands[0] != 'interface {0}'.format(interface):
|
|
||||||
commands.insert(0, 'interface {0}'.format(interface))
|
|
||||||
|
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
|
|
||||||
|
@ -415,7 +456,7 @@ def flatten_list(command_lists):
|
||||||
return flat_command_list
|
return flat_command_list
|
||||||
|
|
||||||
|
|
||||||
def validate_params(addr, interface, mask, tag, allow_secondary, version, state, intf_type, module):
|
def validate_params(addr, interface, mask, dot1q, tag, allow_secondary, version, state, intf_type, module):
|
||||||
if state == "present":
|
if state == "present":
|
||||||
if addr is None or mask is None:
|
if addr is None or mask is None:
|
||||||
module.fail_json(msg="An IP address AND a mask must be provided "
|
module.fail_json(msg="An IP address AND a mask must be provided "
|
||||||
|
@ -446,6 +487,13 @@ def validate_params(addr, interface, mask, tag, allow_secondary, version, state,
|
||||||
except ValueError:
|
except ValueError:
|
||||||
module.fail_json(msg="Warning! Invalid ip address or mask set.", addr=addr, mask=mask)
|
module.fail_json(msg="Warning! Invalid ip address or mask set.", addr=addr, mask=mask)
|
||||||
|
|
||||||
|
if dot1q is not None:
|
||||||
|
try:
|
||||||
|
if 2 > dot1q > 4093:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
module.fail_json(msg="Warning! 'dot1q' must be an integer between"
|
||||||
|
" 2 and 4093", dot1q=dot1q)
|
||||||
if tag is not None:
|
if tag is not None:
|
||||||
try:
|
try:
|
||||||
if 0 > tag > 4294967295:
|
if 0 > tag > 4294967295:
|
||||||
|
@ -470,6 +518,7 @@ def main():
|
||||||
version=dict(required=False, choices=['v4', 'v6'],
|
version=dict(required=False, choices=['v4', 'v6'],
|
||||||
default='v4'),
|
default='v4'),
|
||||||
mask=dict(type='str', required=False),
|
mask=dict(type='str', required=False),
|
||||||
|
dot1q=dict(required=False, default=0, type='int'),
|
||||||
tag=dict(required=False, default=0, type='int'),
|
tag=dict(required=False, default=0, type='int'),
|
||||||
state=dict(required=False, default='present',
|
state=dict(required=False, default='present',
|
||||||
choices=['present', 'absent']),
|
choices=['present', 'absent']),
|
||||||
|
@ -494,13 +543,14 @@ def main():
|
||||||
addr = module.params['addr']
|
addr = module.params['addr']
|
||||||
version = module.params['version']
|
version = module.params['version']
|
||||||
mask = module.params['mask']
|
mask = module.params['mask']
|
||||||
|
dot1q = module.params['dot1q']
|
||||||
tag = module.params['tag']
|
tag = module.params['tag']
|
||||||
allow_secondary = module.params['allow_secondary']
|
allow_secondary = module.params['allow_secondary']
|
||||||
interface = module.params['interface'].lower()
|
interface = module.params['interface'].lower()
|
||||||
state = module.params['state']
|
state = module.params['state']
|
||||||
|
|
||||||
intf_type = get_interface_type(interface)
|
intf_type = get_interface_type(interface)
|
||||||
validate_params(addr, interface, mask, tag, allow_secondary, version, state, intf_type, module)
|
validate_params(addr, interface, mask, dot1q, tag, allow_secondary, version, state, intf_type, module)
|
||||||
|
|
||||||
mode = get_interface_mode(interface, intf_type, module)
|
mode = get_interface_mode(interface, intf_type, module)
|
||||||
if mode == 'layer2':
|
if mode == 'layer2':
|
||||||
|
@ -509,21 +559,35 @@ def main():
|
||||||
|
|
||||||
existing = get_ip_interface(interface, version, module)
|
existing = get_ip_interface(interface, version, module)
|
||||||
|
|
||||||
args = dict(addr=addr, mask=mask, tag=tag, interface=interface, allow_secondary=allow_secondary)
|
dot1q_tag = get_dot1q_id(interface, module)
|
||||||
|
if dot1q_tag > 1:
|
||||||
|
existing['dot1q'] = dot1q_tag
|
||||||
|
|
||||||
|
args = dict(addr=addr, mask=mask, dot1q=dot1q, tag=tag, interface=interface, allow_secondary=allow_secondary)
|
||||||
proposed = dict((k, v) for k, v in args.items() if v is not None)
|
proposed = dict((k, v) for k, v in args.items() if v is not None)
|
||||||
commands = []
|
commands = []
|
||||||
changed = False
|
changed = False
|
||||||
end_state = existing
|
end_state = existing
|
||||||
|
|
||||||
if state == 'absent' and existing['addresses']:
|
commands = ['interface {0}'.format(interface)]
|
||||||
if find_same_addr(existing, addr, mask):
|
if state == 'absent':
|
||||||
command = get_remove_ip_config_commands(interface, addr,
|
if existing['addresses']:
|
||||||
mask, existing, version)
|
if find_same_addr(existing, addr, mask):
|
||||||
|
command = get_remove_ip_config_commands(interface, addr,
|
||||||
|
mask, existing, version)
|
||||||
|
commands.append(command)
|
||||||
|
if 'dot1q' in existing and existing['dot1q'] > 1:
|
||||||
|
command = 'no encapsulation dot1Q {0}'.format(existing['dot1q'])
|
||||||
commands.append(command)
|
commands.append(command)
|
||||||
elif state == 'present':
|
elif state == 'present':
|
||||||
if not find_same_addr(existing, addr, mask, full=True, tag=tag, version=version):
|
if not find_same_addr(existing, addr, mask, full=True, tag=tag, version=version):
|
||||||
command = get_config_ip_commands(proposed, interface, existing, version)
|
command = get_config_ip_commands(proposed, interface, existing, version)
|
||||||
commands.append(command)
|
commands.append(command)
|
||||||
|
if 'dot1q' not in existing and (intf_type in ['ethernet', 'portchannel'] and "." in interface):
|
||||||
|
command = 'encapsulation dot1Q {0}'.format(proposed['dot1q'])
|
||||||
|
commands.append(command)
|
||||||
|
if len(commands) < 2:
|
||||||
|
del commands[0]
|
||||||
cmds = flatten_list(commands)
|
cmds = flatten_list(commands)
|
||||||
if cmds:
|
if cmds:
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
|
|
|
@ -58,7 +58,6 @@ class TestNxosIPInterfaceModule(TestNxosModule):
|
||||||
self.assertEqual(result['commands'],
|
self.assertEqual(result['commands'],
|
||||||
['interface eth2/1',
|
['interface eth2/1',
|
||||||
'no ip address 1.1.1.1/8',
|
'no ip address 1.1.1.1/8',
|
||||||
'interface eth2/1',
|
|
||||||
'ip address 1.1.1.2/8'])
|
'ip address 1.1.1.2/8'])
|
||||||
|
|
||||||
def test_nxos_ip_interface_ip_idempotent(self):
|
def test_nxos_ip_interface_ip_idempotent(self):
|
||||||
|
|
Loading…
Reference in a new issue