From 4c21563ac6d9b4dd7dba518a7fac061df1ae9e5e Mon Sep 17 00:00:00 2001 From: Kedar K <4506537+kedarX@users.noreply.github.com> Date: Tue, 26 Sep 2017 21:34:51 +0530 Subject: [PATCH] - Adds sub-interface support for nxos_interface and nxos_ip_interface (#30830) - Support dot1 encapsulation on routed sub-interface --- .../modules/network/nxos/nxos_interface.py | 32 ++++--- .../modules/network/nxos/nxos_ip_interface.py | 88 ++++++++++++++++--- .../network/nxos/test_nxos_ip_interface.py | 1 - 3 files changed, 95 insertions(+), 26 deletions(-) diff --git a/lib/ansible/modules/network/nxos/nxos_interface.py b/lib/ansible/modules/network/nxos/nxos_interface.py index ef9d518938..7bcce1dd45 100644 --- a/lib/ansible/modules/network/nxos/nxos_interface.py +++ b/lib/ansible/modules/network/nxos/nxos_interface.py @@ -160,7 +160,7 @@ def is_default_interface(interface, module): try: body = execute_show_command(command, module)[0] - except IndexError: + except (IndexError, TypeError) as e: body = '' if body: @@ -276,7 +276,6 @@ def get_interface(intf, module): body = execute_show_command(command, module)[0] except IndexError: body = [] - if body: interface_table = body['TABLE_interface']['ROW_interface'] if interface_table.get('eth_mode') == 'fex-fabric': @@ -343,8 +342,6 @@ def get_interface(intf, module): temp_dict['description'] = "None" interface.update(temp_dict) - interface['type'] = intf_type - return interface @@ -526,7 +523,6 @@ def get_proposed(existing, normalized_interface, args): def smart_existing(module, intf_type, normalized_interface): - # 7K BUG MAY CAUSE THIS TO FAIL all_interfaces = get_interfaces_dict(module) @@ -534,12 +530,13 @@ def smart_existing(module, intf_type, normalized_interface): existing = get_interface(normalized_interface, module) is_default = is_default_interface(normalized_interface, module) else: - if intf_type == 'ethernet': - module.fail_json(msg='Invalid Ethernet interface provided.', - interface=normalized_interface) - elif intf_type in ['loopback', 'portchannel', 'svi', 'nve']: + if (intf_type in ['loopback', 'portchannel', 'svi', 'nve'] or + intf_type == 'ethernet' and "." in normalized_interface): existing = {} is_default = 'DNE' + elif intf_type == 'ethernet': + module.fail_json(msg='Invalid Ethernet interface provided.', + interface=normalized_interface) return existing, is_default @@ -554,7 +551,10 @@ def execute_show_command(command, module): }] body = run_commands(module, cmds) - return body + if body and "Invalid" in body[0]: + return [] + else: + return body def flatten_list(command_lists): @@ -627,9 +627,13 @@ def main(): module.fail_json(msg='The ip_forward and ' 'fabric_forwarding_anycast_gateway features ' ' are only available for SVIs.') + args = dict(interface=interface, admin_state=admin_state, description=description, mode=mode, ip_forward=ip_forward, 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': module.fail_json(msg='unknown interface type found-1', @@ -650,10 +654,12 @@ def main(): if is_default != 'DNE': cmds = ['no interface {0}'.format(normalized_interface)] commands.append(cmds) - elif intf_type in ['ethernet']: - if is_default is False: + elif intf_type in ['ethernet'] and is_default is False: + if "." in normalized_interface: + cmds = ['no interface {0}'.format(normalized_interface)] + else: cmds = ['default interface {0}'.format(normalized_interface)] - commands.append(cmds) + commands.append(cmds) elif state == 'present': if not existing: cmds = get_interface_config_commands(proposed, normalized_interface, existing) diff --git a/lib/ansible/modules/network/nxos/nxos_ip_interface.py b/lib/ansible/modules/network/nxos/nxos_ip_interface.py index 065ee9d16b..d187540fee 100644 --- a/lib/ansible/modules/network/nxos/nxos_ip_interface.py +++ b/lib/ansible/modules/network/nxos/nxos_ip_interface.py @@ -54,6 +54,12 @@ options: - Subnet mask for IPv4 or IPv6 Address in decimal format. required: false 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: description: - Route tag for IPv4 or IPv6 Address in integer format. @@ -105,6 +111,16 @@ EXAMPLES = ''' addr: 20.20.20.20 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 nxos_ip_interface: interface: Ethernet1/32 @@ -322,6 +338,35 @@ def parse_unstructured_data(body, interface_name, version, module): 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\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): body = send_show_command(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): - commands = ['interface {0}'.format(interface)] + commands = [] if version == 'v4': # We can't just remove primary address if secondary address exists 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.append(command) - - if commands[0] != 'interface {0}'.format(interface): - commands.insert(0, 'interface {0}'.format(interface)) - return commands @@ -415,7 +456,7 @@ def flatten_list(command_lists): 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 addr is None or mask is None: 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: 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: try: if 0 > tag > 4294967295: @@ -470,6 +518,7 @@ def main(): version=dict(required=False, choices=['v4', 'v6'], default='v4'), mask=dict(type='str', required=False), + dot1q=dict(required=False, default=0, type='int'), tag=dict(required=False, default=0, type='int'), state=dict(required=False, default='present', choices=['present', 'absent']), @@ -494,13 +543,14 @@ def main(): addr = module.params['addr'] version = module.params['version'] mask = module.params['mask'] + dot1q = module.params['dot1q'] tag = module.params['tag'] allow_secondary = module.params['allow_secondary'] interface = module.params['interface'].lower() state = module.params['state'] 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) if mode == 'layer2': @@ -509,21 +559,35 @@ def main(): 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) commands = [] changed = False end_state = existing - if state == 'absent' and existing['addresses']: - if find_same_addr(existing, addr, mask): - command = get_remove_ip_config_commands(interface, addr, - mask, existing, version) + commands = ['interface {0}'.format(interface)] + if state == 'absent': + if existing['addresses']: + 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) elif state == 'present': if not find_same_addr(existing, addr, mask, full=True, tag=tag, version=version): command = get_config_ip_commands(proposed, interface, existing, version) 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) if cmds: if module.check_mode: diff --git a/test/units/modules/network/nxos/test_nxos_ip_interface.py b/test/units/modules/network/nxos/test_nxos_ip_interface.py index f70047cac0..96d95c34d4 100644 --- a/test/units/modules/network/nxos/test_nxos_ip_interface.py +++ b/test/units/modules/network/nxos/test_nxos_ip_interface.py @@ -58,7 +58,6 @@ class TestNxosIPInterfaceModule(TestNxosModule): self.assertEqual(result['commands'], ['interface eth2/1', 'no ip address 1.1.1.1/8', - 'interface eth2/1', 'ip address 1.1.1.2/8']) def test_nxos_ip_interface_ip_idempotent(self):