From 7b840f3fe5208e64940da3030b5f76b58948095e Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Wed, 5 Sep 2018 04:26:57 +0530 Subject: [PATCH] Pick 2.6 nxos bugfixes (#44105) * Add md5sum check in nxos_file_copy module (#43423) * Add md5sum check in nxos_file_copy module Signed-off-by: Trishna Guha * address review comment Signed-off-by: Trishna Guha (cherry picked from commit fee4c24ad456020db4397d090509da5b96a227a2) * nxos_vlan refactor to support non structured output (#43805) * nxos_vlan refactor to support non structured output Signed-off-by: Trishna Guha * unittest fix Signed-off-by: Trishna Guha * minor fixes Signed-off-by: Trishna Guha * use check_rc Signed-off-by: Trishna Guha * address review comment Signed-off-by: Trishna Guha * remove additional return statement * address Nate's review Signed-off-by: Trishna Guha (cherry picked from commit 96346938eef9594f4d1ceeff1d48f13807e36e21) * nxos_facts test lldp feature and fix nxapi check_rc (#44104) Signed-off-by: Trishna Guha (cherry picked from commit 43ae240431fbfc73a4f9c692f2feaa4530ea5664) * nxos bugfix 2.6 changelog Signed-off-by: Trishna Guha * nxos_interface port-channel idempotence fix for mode (#44248) Signed-off-by: Trishna Guha (cherry picked from commit 6af6e806edb3e28a36f44ef50534786e944ad7c8) * changelog Signed-off-by: Trishna Guha * check_mode in nxos_static_route module (#44252) Signed-off-by: Trishna Guha (cherry picked from commit 7e39c5bf079d17b74a7ebec3a0a653a75b702684) * changelog nxos_static_route Signed-off-by: Trishna Guha * nxos_linkagg mode fix (#44294) Signed-off-by: Trishna Guha (cherry picked from commit 6090802551410a226ff67cabc06b688c33a6e9a8) * changelog nxos_linkagg * nxos_system idempotence fix (#44752) Signed-off-by: Trishna Guha (cherry picked from commit 8a79d944a3028f5a7da71ed3269451e5ca274c73) * nxos_system changelog Signed-off-by: Trishna Guha * use retry_json nxos_banner (#44376) Signed-off-by: Trishna Guha (cherry picked from commit 9c4ed4dfc1d58cb749b38149f981cb6db6a2927d) * nxos_banner changelog Signed-off-by: Trishna Guha * fix Python 2.6 regex bug terminal plugin nxos, iosxr (#45135) Signed-off-by: Trishna Guha (cherry picked from commit ab3cd10dfec0e316cc3210e8733845903a36d477) * terminal plugin changelog Signed-off-by: Trishna Guha --- changelogs/fragments/nxos_banner_text.yaml | 2 + .../fragments/nxos_file_copy_md5sum.yaml | 2 + changelogs/fragments/nxos_interface.yaml | 2 + changelogs/fragments/nxos_linkagg.yaml | 2 + .../nxos_non_structured_output_fix.yaml | 3 + .../fragments/nxos_static_route_fix.yaml | 2 + changelogs/fragments/nxos_system.yaml | 2 + changelogs/fragments/terminal_plugin.yaml | 2 + lib/ansible/module_utils/network/nxos/nxos.py | 19 +- .../modules/network/nxos/nxos_banner.py | 11 +- .../modules/network/nxos/nxos_facts.py | 60 ++--- .../modules/network/nxos/nxos_file_copy.py | 26 +- .../modules/network/nxos/nxos_interface.py | 6 +- .../modules/network/nxos/nxos_linkagg.py | 38 ++- .../modules/network/nxos/nxos_static_route.py | 4 +- .../modules/network/nxos/nxos_system.py | 4 +- lib/ansible/modules/network/nxos/nxos_vlan.py | 233 ++++++++++++------ lib/ansible/plugins/cliconf/nxos.py | 2 +- lib/ansible/plugins/httpapi/nxos.py | 2 +- lib/ansible/plugins/terminal/iosxr.py | 2 +- lib/ansible/plugins/terminal/nxos.py | 2 +- .../targets/prepare_nxos_tests/tasks/main.yml | 6 + .../nxos/fixtures/nxos_vlan/config.cfg | 4 + .../nxos/fixtures/nxos_vlan/show_vlan.txt | 18 -- .../fixtures/nxos_vlan/show_vlan_brief.txt | 11 + .../modules/network/nxos/test_nxos_vlan.py | 5 - 26 files changed, 299 insertions(+), 171 deletions(-) create mode 100644 changelogs/fragments/nxos_banner_text.yaml create mode 100644 changelogs/fragments/nxos_file_copy_md5sum.yaml create mode 100644 changelogs/fragments/nxos_interface.yaml create mode 100644 changelogs/fragments/nxos_linkagg.yaml create mode 100644 changelogs/fragments/nxos_non_structured_output_fix.yaml create mode 100644 changelogs/fragments/nxos_static_route_fix.yaml create mode 100644 changelogs/fragments/nxos_system.yaml create mode 100644 changelogs/fragments/terminal_plugin.yaml delete mode 100644 test/units/modules/network/nxos/fixtures/nxos_vlan/show_vlan.txt create mode 100644 test/units/modules/network/nxos/fixtures/nxos_vlan/show_vlan_brief.txt diff --git a/changelogs/fragments/nxos_banner_text.yaml b/changelogs/fragments/nxos_banner_text.yaml new file mode 100644 index 00000000000..c8bc536fd47 --- /dev/null +++ b/changelogs/fragments/nxos_banner_text.yaml @@ -0,0 +1,2 @@ +bugfixes: +- use retry_json nxos_banner (https://github.com/ansible/ansible/pull/44376). diff --git a/changelogs/fragments/nxos_file_copy_md5sum.yaml b/changelogs/fragments/nxos_file_copy_md5sum.yaml new file mode 100644 index 00000000000..13e28eb2949 --- /dev/null +++ b/changelogs/fragments/nxos_file_copy_md5sum.yaml @@ -0,0 +1,2 @@ +bugfixes: +- Add md5sum check in nxos_file_copy module (https://github.com/ansible/ansible/pull/43423). diff --git a/changelogs/fragments/nxos_interface.yaml b/changelogs/fragments/nxos_interface.yaml new file mode 100644 index 00000000000..62fc10e2f2c --- /dev/null +++ b/changelogs/fragments/nxos_interface.yaml @@ -0,0 +1,2 @@ +bugfixes: +- nxos_interface port-channel idempotence fix for mode (https://github.com/ansible/ansible/pull/44248). diff --git a/changelogs/fragments/nxos_linkagg.yaml b/changelogs/fragments/nxos_linkagg.yaml new file mode 100644 index 00000000000..d1a6bd3fd2f --- /dev/null +++ b/changelogs/fragments/nxos_linkagg.yaml @@ -0,0 +1,2 @@ +bugfixes: +- nxos_linkagg mode fix (https://github.com/ansible/ansible/pull/44294). diff --git a/changelogs/fragments/nxos_non_structured_output_fix.yaml b/changelogs/fragments/nxos_non_structured_output_fix.yaml new file mode 100644 index 00000000000..84c381cd9ce --- /dev/null +++ b/changelogs/fragments/nxos_non_structured_output_fix.yaml @@ -0,0 +1,3 @@ +bugfixes: +- nxos_vlan refactor to support non structured output (https://github.com/ansible/ansible/pull/43805). +- nxos_facts test lldp feature and fix nxapi check_rc (https://github.com/ansible/ansible/pull/44104). diff --git a/changelogs/fragments/nxos_static_route_fix.yaml b/changelogs/fragments/nxos_static_route_fix.yaml new file mode 100644 index 00000000000..7d708ecd585 --- /dev/null +++ b/changelogs/fragments/nxos_static_route_fix.yaml @@ -0,0 +1,2 @@ +bugfixes: +- Fix check_mode in nxos_static_route module (https://github.com/ansible/ansible/pull/44252). diff --git a/changelogs/fragments/nxos_system.yaml b/changelogs/fragments/nxos_system.yaml new file mode 100644 index 00000000000..b0da36e7084 --- /dev/null +++ b/changelogs/fragments/nxos_system.yaml @@ -0,0 +1,2 @@ +bugfixes: +- nxos_system idempotence fix (https://github.com/ansible/ansible/pull/44752). diff --git a/changelogs/fragments/terminal_plugin.yaml b/changelogs/fragments/terminal_plugin.yaml new file mode 100644 index 00000000000..15b6dd365fb --- /dev/null +++ b/changelogs/fragments/terminal_plugin.yaml @@ -0,0 +1,2 @@ +bugfixes: +- Fix Python2.6 regex bug terminal plugin nxos, iosxr (https://github.com/ansible/ansible/pull/45135). diff --git a/lib/ansible/module_utils/network/nxos/nxos.py b/lib/ansible/module_utils/network/nxos/nxos.py index ce5370c501b..cffac3fe0a9 100644 --- a/lib/ansible/module_utils/network/nxos/nxos.py +++ b/lib/ansible/module_utils/network/nxos/nxos.py @@ -147,7 +147,22 @@ class Cli: """Run list of commands on remote device and return results """ connection = self._get_connection() - return connection.run_commands(commands, check_rc) + + try: + out = connection.run_commands(commands, check_rc) + if check_rc == 'retry_json': + capabilities = self.get_capabilities() + network_api = capabilities.get('network_api') + + if network_api == 'cliconf' and out: + for index, resp in enumerate(out): + if 'Invalid command at' in resp and 'json' in resp: + if commands[index]['output'] == 'json': + commands[index]['output'] = 'text' + out = connection.run_commands(commands, check_rc) + return out + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc)) def load_config(self, config, return_error=False, opts=None): """Sends configuration commands to the remote device @@ -309,7 +324,7 @@ class Nxapi: if response['ins_api'].get('outputs'): output = response['ins_api']['outputs']['output'] for item in to_list(output): - if check_status and item['code'] != '200': + if check_status is True and item['code'] != '200': if return_error: result.append(item) else: diff --git a/lib/ansible/modules/network/nxos/nxos_banner.py b/lib/ansible/modules/network/nxos/nxos_banner.py index 5bb235869b0..2e6ea8c06e4 100644 --- a/lib/ansible/modules/network/nxos/nxos_banner.py +++ b/lib/ansible/modules/network/nxos/nxos_banner.py @@ -98,14 +98,7 @@ def execute_show_command(module, command): 'command': command, 'output': format, }] - output = run_commands(module, cmds, False) - if len(output) == 0 or len(output[0]) == 0: - # If we get here the platform does not - # support structured output. Resend as - # text. - cmds[0]['output'] = 'text' - output = run_commands(module, cmds, False) - + output = run_commands(module, cmds, check_rc='retry_json') return output @@ -130,7 +123,7 @@ def map_config_to_obj(module): output = execute_show_command(module, command)[0] if "Invalid command" in output: - module.fail_json(msg="banner: exec may not be supported on this platform. Possible values are : exec | motd") + module.fail_json(msg="banner: %s may not be supported on this platform. Possible values are : exec | motd" % module.params['banner']) if isinstance(output, dict): output = list(output.values()) diff --git a/lib/ansible/modules/network/nxos/nxos_facts.py b/lib/ansible/modules/network/nxos/nxos_facts.py index 593ca6d0b49..4ecb1ba7aa6 100644 --- a/lib/ansible/modules/network/nxos/nxos_facts.py +++ b/lib/ansible/modules/network/nxos/nxos_facts.py @@ -194,7 +194,7 @@ class FactsBase(object): 'command': command, 'output': output } - resp = run_commands(self.module, [command], check_rc=False) + resp = run_commands(self.module, [command], check_rc='retry_json') try: return resp[0] except IndexError: @@ -234,10 +234,8 @@ class Default(FactsBase): def populate(self): data = None - try: - data = self.run('show version', output='json') - except ConnectionError: - data = self.run('show version') + data = self.run('show version', output='json') + if data: if isinstance(data, dict): if data.get('sys_ver_str'): @@ -300,10 +298,8 @@ class Hardware(FactsBase): self.facts['filesystems'] = self.parse_filesystems(data) data = None - try: - data = self.run('show system resources', output='json') - except ConnectionError: - data = self.run('show system resources') + data = self.run('show system resources', output='json') + if data: if isinstance(data, dict): self.facts['memtotal_mb'] = int(data['memory_usage_total']) / 1024 @@ -380,10 +376,8 @@ class Interfaces(FactsBase): self.facts['all_ipv6_addresses'] = list() data = None - try: - data = self.run('show interface', output='json') - except ConnectionError: - data = self.run('show interface') + data = self.run('show interface', output='json') + if data: if isinstance(data, dict): self.facts['interfaces'] = self.populate_structured_interfaces(data) @@ -392,10 +386,7 @@ class Interfaces(FactsBase): self.facts['interfaces'] = self.populate_interfaces(interfaces) if self.ipv6_structure_op_supported(): - try: - data = self.run('show ipv6 interface', output='json') - except ConnectionError: - data = self.run('show ipv6 interface') + data = self.run('show ipv6 interface', output='json') else: data = None if data: @@ -409,10 +400,7 @@ class Interfaces(FactsBase): if data: self.facts['neighbors'] = self.populate_neighbors(data) - try: - data = self.run('show cdp neighbors detail', output='json') - except ConnectionError: - data = self.run('show cdp neighbors detail') + data = self.run('show cdp neighbors detail', output='json') if data: if isinstance(data, dict): self.facts['neighbors'] = self.populate_structured_neighbors_cdp(data) @@ -718,10 +706,7 @@ class Legacy(FactsBase): def populate(self): data = None - try: - data = self.run('show version') - except ConnectionError: - data = self.run('show version', output='json') + data = self.run('show version', output='json') if data: if isinstance(data, dict): self.facts.update(self.transform_dict(data, self.VERSION_MAP)) @@ -730,50 +715,35 @@ class Legacy(FactsBase): self.facts['_os'] = self.parse_os(data) self.facts['_platform'] = self.parse_platform(data) - try: - data = self.run('show interface', output='json') - except ConnectionError: - data = self.run('show interface') + data = self.run('show interface', output='json') if data: if isinstance(data, dict): self.facts['_interfaces_list'] = self.parse_structured_interfaces(data) else: self.facts['_interfaces_list'] = self.parse_interfaces(data) - try: - data = self.run('show vlan brief', output='json') - except ConnectionError: - data = self.run('show vlan brief') + data = self.run('show vlan brief', output='json') if data: if isinstance(data, dict): self.facts['_vlan_list'] = self.parse_structured_vlans(data) else: self.facts['_vlan_list'] = self.parse_vlans(data) - try: - data = self.run('show module', output='json') - except ConnectionError: - data = self.run('show module') + data = self.run('show module', output='json') if data: if isinstance(data, dict): self.facts['_module'] = self.parse_structured_module(data) else: self.facts['_module'] = self.parse_module(data) - try: - data = self.run('show environment fan', output='json') - except ConnectionError: - data = self.run('show environment fan') + data = self.run('show environment fan', output='json') if data: if isinstance(data, dict): self.facts['_fan_info'] = self.parse_structured_fan_info(data) else: self.facts['_fan_info'] = self.parse_fan_info(data) - try: - data = self.run('show environment power', output='json') - except ConnectionError: - data = self.run('show environment power') + data = self.run('show environment power', output='json') if data: if isinstance(data, dict): self.facts['_power_supply_info'] = self.parse_structured_power_supply_info(data) diff --git a/lib/ansible/modules/network/nxos/nxos_file_copy.py b/lib/ansible/modules/network/nxos/nxos_file_copy.py index dd55757a379..97ff2bd599f 100644 --- a/lib/ansible/modules/network/nxos/nxos_file_copy.py +++ b/lib/ansible/modules/network/nxos/nxos_file_copy.py @@ -86,6 +86,7 @@ remote_file: sample: '/path/to/remote/file' ''' +import hashlib import os import re import time @@ -93,6 +94,7 @@ import time from ansible.module_utils.network.nxos.nxos import run_commands from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, check_args from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native, to_text, to_bytes try: import paramiko @@ -107,12 +109,34 @@ except ImportError: HAS_SCP = False +def md5sum_check(module, dst, file_system): + command = 'show file {0}{1} md5sum'.format(file_system, dst) + remote_filehash = run_commands(module, {'command': command, 'output': 'text'})[0] + remote_filehash = to_bytes(remote_filehash, errors='surrogate_or_strict') + + local_file = module.params['local_file'] + try: + with open(local_file, 'r') as f: + filecontent = f.read() + except (OSError, IOError) as exc: + module.fail_json(msg="Error reading the file: %s" % to_text(exc)) + + filecontent = to_bytes(filecontent, errors='surrogate_or_strict') + local_filehash = hashlib.md5(filecontent).hexdigest() + + if local_filehash == remote_filehash: + return True + else: + return False + + def remote_file_exists(module, dst, file_system='bootflash:'): command = 'dir {0}/{1}'.format(file_system, dst) body = run_commands(module, {'command': command, 'output': 'text'})[0] if 'No such file' in body: return False - return True + else: + return md5sum_check(module, dst, file_system) def verify_remote_file_exists(module, dst, file_system='bootflash:'): diff --git a/lib/ansible/modules/network/nxos/nxos_interface.py b/lib/ansible/modules/network/nxos/nxos_interface.py index a199474e0a4..a3ce24eb8ac 100644 --- a/lib/ansible/modules/network/nxos/nxos_interface.py +++ b/lib/ansible/modules/network/nxos/nxos_interface.py @@ -578,7 +578,11 @@ def map_config_to_obj(want, module): obj['name'] = normalize_interface(interface_table.get('interface')) obj['admin_state'] = interface_table.get('admin_state') obj['description'] = interface_table.get('desc') - obj['mode'] = interface_table.get('eth_mode') + mode = interface_table.get('eth_mode') + if mode == 'access': + obj['mode'] = 'layer2' + else: + obj['mode'] = 'layer3' objs.append(obj) diff --git a/lib/ansible/modules/network/nxos/nxos_linkagg.py b/lib/ansible/modules/network/nxos/nxos_linkagg.py index f858945d953..f6a8966bcfd 100644 --- a/lib/ansible/modules/network/nxos/nxos_linkagg.py +++ b/lib/ansible/modules/network/nxos/nxos_linkagg.py @@ -139,6 +139,19 @@ def search_obj_in_list(group, lst): return o +def get_diff(w, obj): + c = deepcopy(w) + o = deepcopy(obj) + + if o['group'] == c['group'] and o.get('members') == c.get('members'): + if 'members' in o: + del o['members'] + if 'members' in c: + del c['members'] + diff_dict = dict(set(c.items()) - set(o.items())) + return diff_dict + + def map_obj_to_commands(updates, module): commands = list() want, have = updates @@ -208,6 +221,18 @@ def map_obj_to_commands(updates, module): commands.append('exit') commands.append('interface {0}'.format(m)) commands.append('no channel-group {0}'.format(group)) + + else: + diff = get_diff(w, obj_in_have) + if diff and 'mode' in diff: + mode = diff['mode'] + for i in members: + commands.append('interface {0}'.format(i)) + if force: + commands.append('channel-group {0} force mode {1}'.format(group, mode)) + else: + commands.append('channel-group {0} mode {1}'.format(group, mode)) + if purge: for h in have: obj_in_want = search_obj_in_list(h['group'], want) @@ -310,7 +335,7 @@ def parse_channel_options(module, output, channel): group = channel['group'] obj['group'] = group - obj['min-links'] = parse_min_links(module, group) + obj['min_links'] = parse_min_links(module, group) members = parse_members(output, group) obj['members'] = members for m in members: @@ -389,7 +414,16 @@ def main(): if commands: if not module.check_mode: - load_config(module, commands) + resp = load_config(module, commands, True) + if resp: + for item in resp: + if item: + if isinstance(item, dict): + err_str = item['clierror'] + else: + err_str = item + if 'cannot add' in err_str.lower(): + module.fail_json(msg=err_str) result['changed'] = True module.exit_json(**result) diff --git a/lib/ansible/modules/network/nxos/nxos_static_route.py b/lib/ansible/modules/network/nxos/nxos_static_route.py index 8460996d7be..abe17647059 100644 --- a/lib/ansible/modules/network/nxos/nxos_static_route.py +++ b/lib/ansible/modules/network/nxos/nxos_static_route.py @@ -280,13 +280,11 @@ def main(): candidate = CustomNetworkConfig(indent=3) reconcile_candidate(module, candidate, prefix, w) - if candidate: + if not module.check_mode and candidate: candidate = candidate.items_text() load_config(module, candidate) result['commands'].extend(candidate) result['changed'] = True - else: - result['commands'] = [] module.exit_json(**result) diff --git a/lib/ansible/modules/network/nxos/nxos_system.py b/lib/ansible/modules/network/nxos/nxos_system.py index b2db9105153..6a34307ea94 100644 --- a/lib/ansible/modules/network/nxos/nxos_system.py +++ b/lib/ansible/modules/network/nxos/nxos_system.py @@ -279,10 +279,8 @@ def parse_name_servers(config, vrf_config, vrfs): objects = list() match = re.search('^ip name-server (.+)$', config, re.M) - if match: + if match and 'use-vrf' not in match.group(1): for addr in match.group(1).split(' '): - if addr == 'use-vrf' or addr in vrfs: - continue objects.append({'server': addr, 'vrf': None}) for vrf, cfg in iteritems(vrf_config): diff --git a/lib/ansible/modules/network/nxos/nxos_vlan.py b/lib/ansible/modules/network/nxos/nxos_vlan.py index 152c9c09589..cb497eb535b 100644 --- a/lib/ansible/modules/network/nxos/nxos_vlan.py +++ b/lib/ansible/modules/network/nxos/nxos_vlan.py @@ -74,7 +74,6 @@ options: description: - Set VLAN mode to classical ethernet or fabricpath. This is a valid option for Nexus 5000 and 7000 series. - default: ce choices: ['ce','fabricpath'] version_added: "2.4" aggregate: @@ -156,8 +155,10 @@ import time from copy import deepcopy from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands -from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import normalize_interface, nxos_argument_spec from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError +from ansible.module_utils.network.common.config import CustomNetworkConfig from ansible.module_utils.network.common.utils import remove_default_spec @@ -191,7 +192,7 @@ def is_default_name(obj, vlan_id): return False -def map_obj_to_commands(updates, module, os_platform): +def map_obj_to_commands(updates, module): commands = list() purge = module.params['purge'] want, have = updates @@ -201,15 +202,11 @@ def map_obj_to_commands(updates, module, os_platform): name = w['name'] interfaces = w.get('interfaces') or [] mapped_vni = w['mapped_vni'] + mode = w['mode'] vlan_state = w['vlan_state'] admin_state = w['admin_state'] state = w['state'] del w['state'] - if any(i in os_platform for i in ['5K', '7K']): - mode = w['mode'] - else: - w['mode'] = None - mode = w['mode'] obj_in_have = search_obj_in_list(vlan_id, have) @@ -258,7 +255,7 @@ def map_obj_to_commands(updates, module, os_platform): else: if not is_default_name(obj_in_have, vlan_id): commands.append('no name') - if key == 'vlan_state': + if key == 'vlan_state' and value: commands.append('state {0}'.format(value)) if key == 'mapped_vni': if value == 'default': @@ -271,7 +268,7 @@ def map_obj_to_commands(updates, module, os_platform): commands.append('no shutdown') elif value == 'down': commands.append('shutdown') - if key == 'mode': + if key == 'mode' and value: commands.append('mode {0}'.format(value)) if len(commands) > 1: commands.append('exit') @@ -367,6 +364,13 @@ def vlan_range_commands(module, have): return commands +def normalize(interfaces): + normalized = None + if interfaces: + normalized = [normalize_interface(i) for i in interfaces] + return normalized + + def map_params_to_obj(module): obj = [] if module.params['vlan_range']: @@ -382,19 +386,24 @@ def map_params_to_obj(module): d = item.copy() d['vlan_id'] = str(d['vlan_id']) d['mapped_vni'] = str(d['mapped_vni']) + d['interfaces'] = normalize(d['interfaces']) + d['associated_interfaces'] = normalize(d['associated_interfaces']) obj.append(d) else: + interfaces = normalize(module.params['interfaces']) + associated_interfaces = normalize(module.params['associated_interfaces']) + obj.append({ 'vlan_id': str(module.params['vlan_id']), 'name': module.params['name'], - 'interfaces': module.params['interfaces'], + 'interfaces': interfaces, 'vlan_state': module.params['vlan_state'], 'mapped_vni': str(module.params['mapped_vni']), 'state': module.params['state'], 'admin_state': module.params['admin_state'], 'mode': module.params['mode'], - 'associated_interfaces': module.params['associated_interfaces'] + 'associated_interfaces': associated_interfaces }) return obj @@ -408,49 +417,23 @@ def parse_admin_state(vlan): return 'down' -def parse_mode(os_platform, output, vlan_id): - if not any(i in os_platform for i in ['5K', '7K']): - return None - - try: - mtus = output['TABLE_mtuinfo']['ROW_mtuinfo'] - except KeyError: - return None - - if mtus: - if isinstance(mtus, list): - for mtu in mtus: - if mtu['vlanshowinfo-vlanid'] == vlan_id: - mode = mtu.get('vlanshowinfo-vlanmode') - if mode == 'ce-vlan': - return 'ce' - elif mode == 'fabricpath-vlan': - return 'fabricpath' - return None - - elif isinstance(mtus, dict): - if mtus['vlanshowinfo-vlanid'] == vlan_id: - mode = mtus.get('vlanshowinfo-vlanmode') - if mode == 'ce-vlan': - return 'ce' - elif mode == 'fabricpath-vlan': - return 'fabricpath' - return None +def parse_mode(config): + mode = None - else: - return None - else: - return None + if config: + match = re.search(r'mode (\S+)', config) + if match: + mode = match.group(1) + return mode -def parse_vni(module, vlan_id): +def parse_vni(config): vni = None - flags = ['| section vlan.{0}'.format(vlan_id)] - cfg = get_config(module, flags=flags) - match = re.search(r'vn-segment (\S+)', cfg, re.M) - if match: - vni = match.group(1) + if config: + match = re.search(r'vn-segment (\S+)', config) + if match: + vni = match.group(1) return str(vni) @@ -482,41 +465,138 @@ def parse_interfaces(module, vlan): return vlan_int -def parse_vlan_options(module, os_platform, output, vlan): +def parse_vlan_config(netcfg, vlan_id): + parents = ['vlan {0}'.format(vlan_id)] + config = netcfg.get_section(parents) + return config + + +def parse_vlan_options(module, netcfg, output, vlan): obj = {} vlan_id = vlan['vlanshowbr-vlanid-utf'] + config = parse_vlan_config(netcfg, vlan_id) + obj['vlan_id'] = str(vlan_id) obj['name'] = vlan.get('vlanshowbr-vlanname') obj['vlan_state'] = vlan.get('vlanshowbr-vlanstate') obj['admin_state'] = parse_admin_state(vlan) - obj['mode'] = parse_mode(os_platform, output, vlan_id) - obj['mapped_vni'] = parse_vni(module, vlan_id) + obj['mode'] = parse_mode(config) + obj['mapped_vni'] = parse_vni(config) obj['interfaces'] = parse_interfaces(module, vlan) return obj -def map_config_to_obj(module, os_platform): +def parse_vlan_non_structured(module, netcfg, vlans): + objs = list() + + for vlan in vlans: + vlan_match = re.search(r'(\d+)', vlan, re.M) + if vlan_match: + obj = {} + vlan_id = vlan_match.group(1) + obj['vlan_id'] = str(vlan_id) + + name_match = re.search(r'{0}\s*(\S+)'.format(vlan_id), vlan, re.M) + if name_match: + name = name_match.group(1) + obj['name'] = name + + state_match = re.search(r'{0}\s*{1}\s*(\S+)'.format(vlan_id, name), vlan, re.M) + if state_match: + vlan_state_match = state_match.group(1) + if vlan_state_match == 'suspended': + vlan_state = 'suspend' + admin_state = 'up' + elif vlan_state_match == 'sus/lshut': + vlan_state = 'suspend' + admin_state = 'down' + if vlan_state_match == 'active': + vlan_state = 'active' + admin_state = 'up' + if vlan_state_match == 'act/lshut': + vlan_state = 'active' + admin_state = 'down' + + obj['vlan_state'] = vlan_state + obj['admin_state'] = admin_state + + vlan = ','.join(vlan.splitlines()) + interfaces = list() + intfs_match = re.search(r'{0}\s*{1}\s*{2}\s*(.*)'.format(vlan_id, name, vlan_state_match), + vlan, re.M) + if intfs_match: + intfs = intfs_match.group(1) + intfs = intfs.split() + for i in intfs: + intf = normalize_interface(i.strip(',')) + interfaces.append(intf) + + if interfaces: + obj['interfaces'] = interfaces + else: + obj['interfaces'] = None + + config = parse_vlan_config(netcfg, vlan_id) + obj['mode'] = parse_mode(config) + obj['mapped_vni'] = parse_vni(config) + + objs.append(obj) + + return objs + + +def map_config_to_obj(module): objs = list() - output = run_commands(module, ['show vlan | json'])[0] - try: - vlans = output['TABLE_vlanbrief']['ROW_vlanbrief'] - except KeyError: - return objs - - if vlans: - if isinstance(vlans, list): - for vlan in vlans: - obj = parse_vlan_options(module, os_platform, output, vlan) - objs.append(obj) - - elif isinstance(vlans, dict): - obj = parse_vlan_options(module, os_platform, output, vlans) - objs.append(obj) + output = None + + command = ['show vlan brief | json'] + output = run_commands(module, command, check_rc='retry_json')[0] + if output: + netcfg = CustomNetworkConfig(indent=2, + contents=get_config(module, flags=['all'])) + + if isinstance(output, dict): + vlans = None + try: + vlans = output['TABLE_vlanbriefxbrief']['ROW_vlanbriefxbrief'] + except KeyError: + return objs + + if vlans: + if isinstance(vlans, list): + for vlan in vlans: + obj = parse_vlan_options(module, netcfg, output, vlan) + objs.append(obj) + elif isinstance(vlans, dict): + obj = parse_vlan_options(module, netcfg, output, vlans) + objs.append(obj) + else: + vlans = list() + splitted_line = re.split(r'\n(\d+)|\n{2}', output.strip()) + + for line in splitted_line: + if not line: + continue + if len(line) > 0: + line = line.strip() + if line[0].isdigit(): + match = re.search(r'(\d+)', line, re.M) + if match: + v = match.group(1) + pos1 = splitted_line.index(v) + pos2 = pos1 + 1 + vlaninfo = ''.join(splitted_line[pos1:pos2 + 1]) + vlans.append(vlaninfo) + + if vlans: + objs = parse_vlan_non_structured(module, netcfg, vlans) + else: + return objs return objs -def check_declarative_intent_params(want, module, os_platform, result): +def check_declarative_intent_params(want, module, result): have = None is_delay = False @@ -530,7 +610,7 @@ def check_declarative_intent_params(want, module, os_platform, result): is_delay = True if have is None: - have = map_config_to_obj(module, os_platform) + have = map_config_to_obj(module) for i in w['associated_interfaces']: obj_in_have = search_obj_in_list(w['vlan_id'], have) @@ -552,7 +632,7 @@ def main(): delay=dict(default=10, type='int'), state=dict(choices=['present', 'absent'], default='present', required=False), admin_state=dict(choices=['up', 'down'], required=False, default='up'), - mode=dict(choices=['ce', 'fabricpath'], required=False, default='ce'), + mode=dict(choices=['ce', 'fabricpath'], required=False), ) aggregate_spec = deepcopy(element_spec) @@ -579,22 +659,19 @@ def main(): mutually_exclusive=mutually_exclusive, supports_check_mode=True) - info = get_capabilities(module).get('device_info', {}) - os_platform = info.get('network_os_platform', '') - warnings = list() result = {'changed': False} if warnings: result['warnings'] = warnings - have = map_config_to_obj(module, os_platform) + have = map_config_to_obj(module) want = map_params_to_obj(module) if module.params['vlan_range']: commands = vlan_range_commands(module, have) result['commands'] = commands else: - commands = map_obj_to_commands((want, have), module, os_platform) + commands = map_obj_to_commands((want, have), module) result['commands'] = commands if commands: @@ -603,7 +680,7 @@ def main(): result['changed'] = True if want: - check_declarative_intent_params(want, module, os_platform, result) + check_declarative_intent_params(want, module, result) module.exit_json(**result) diff --git a/lib/ansible/plugins/cliconf/nxos.py b/lib/ansible/plugins/cliconf/nxos.py index b901ceeccfe..632eb117038 100644 --- a/lib/ansible/plugins/cliconf/nxos.py +++ b/lib/ansible/plugins/cliconf/nxos.py @@ -148,7 +148,7 @@ class Cliconf(CliconfBase): try: out = self.get(cmd) except AnsibleConnectionFailure as e: - if check_rc: + if check_rc is True: raise out = getattr(e, 'err', e) diff --git a/lib/ansible/plugins/httpapi/nxos.py b/lib/ansible/plugins/httpapi/nxos.py index cd64f81c540..ac781d1f5da 100644 --- a/lib/ansible/plugins/httpapi/nxos.py +++ b/lib/ansible/plugins/httpapi/nxos.py @@ -91,7 +91,7 @@ class HttpApi: try: out = self.send_request(commands) except ConnectionError as exc: - if check_rc: + if check_rc is True: raise out = to_text(exc) diff --git a/lib/ansible/plugins/terminal/iosxr.py b/lib/ansible/plugins/terminal/iosxr.py index 6a9657d0ab5..100b3d5863a 100644 --- a/lib/ansible/plugins/terminal/iosxr.py +++ b/lib/ansible/plugins/terminal/iosxr.py @@ -29,7 +29,7 @@ from ansible.errors import AnsibleConnectionFailure class TerminalModule(TerminalBase): terminal_stdout_re = [ - re.compile(br"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(br"[\r\n][\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), re.compile(br']]>]]>[\r\n]?') ] diff --git a/lib/ansible/plugins/terminal/nxos.py b/lib/ansible/plugins/terminal/nxos.py index 2db3dfdd456..438c5b3877a 100644 --- a/lib/ansible/plugins/terminal/nxos.py +++ b/lib/ansible/plugins/terminal/nxos.py @@ -30,7 +30,7 @@ from ansible.module_utils._text import to_bytes, to_text class TerminalModule(TerminalBase): terminal_stdout_re = [ - re.compile(br'[\r\n]?(?!\s*<)?(\x1b\S+)*[a-zA-Z_0-9]{1}[a-zA-Z0-9-_.]*[>|#](?:\s*)*(\x1b\S+)*$'), + re.compile(br'[\r\n](?!\s*<)?(\x1b\S+)*[a-zA-Z_0-9]{1}[a-zA-Z0-9-_.]*[>|#](?:\s*)*(\x1b\S+)*$'), re.compile(br'[\r\n]?[a-zA-Z0-9]{1}[a-zA-Z0-9-_.]*\(.+\)#(?:\s*)$') ] diff --git a/test/integration/targets/prepare_nxos_tests/tasks/main.yml b/test/integration/targets/prepare_nxos_tests/tasks/main.yml index 9d2e5cdd6cd..454ece66bfe 100644 --- a/test/integration/targets/prepare_nxos_tests/tasks/main.yml +++ b/test/integration/targets/prepare_nxos_tests/tasks/main.yml @@ -11,6 +11,12 @@ state: present connection: network_cli +- name: Enable lldp + nxos_config: + lines: + - feature lldp + ignore_errors: yes + # Gather the list of interfaces on this device and make the list # available for integration tests that need them. # diff --git a/test/units/modules/network/nxos/fixtures/nxos_vlan/config.cfg b/test/units/modules/network/nxos/fixtures/nxos_vlan/config.cfg index e69de29bb2d..905d309ff09 100644 --- a/test/units/modules/network/nxos/fixtures/nxos_vlan/config.cfg +++ b/test/units/modules/network/nxos/fixtures/nxos_vlan/config.cfg @@ -0,0 +1,4 @@ +vlan 1 + mode ce + state active + no shutdown diff --git a/test/units/modules/network/nxos/fixtures/nxos_vlan/show_vlan.txt b/test/units/modules/network/nxos/fixtures/nxos_vlan/show_vlan.txt deleted file mode 100644 index 58bcedda8f3..00000000000 --- a/test/units/modules/network/nxos/fixtures/nxos_vlan/show_vlan.txt +++ /dev/null @@ -1,18 +0,0 @@ -{ - "TABLE_vlanbrief": { - "ROW_vlanbrief": { - "vlanshowbr-vlanid": 16777216, - "vlanshowbr-vlanid-utf": 1, - "vlanshowbr-vlanname": "default", - "vlanshowbr-vlanstate": "active", - "vlanshowbr-shutstate": "noshutdown" - } - }, - "TABLE_mtuinfo": { - "ROW_mtuinfo": { - "vlanshowinfo-vlanid": 1, - "vlanshowinfo-media-type": "enet", - "vlanshowinfo-vlanmode": "ce-vlan" - } - } -} diff --git a/test/units/modules/network/nxos/fixtures/nxos_vlan/show_vlan_brief.txt b/test/units/modules/network/nxos/fixtures/nxos_vlan/show_vlan_brief.txt new file mode 100644 index 00000000000..ca90e5d4944 --- /dev/null +++ b/test/units/modules/network/nxos/fixtures/nxos_vlan/show_vlan_brief.txt @@ -0,0 +1,11 @@ +{ + "TABLE_vlanbriefxbrief": { + "ROW_vlanbriefxbrief": { + "vlanshowbr-vlanid": 16777216, + "vlanshowbr-vlanid-utf": 1, + "vlanshowbr-vlanname": "default", + "vlanshowbr-vlanstate": "active", + "vlanshowbr-shutstate": "noshutdown" + } + } +} diff --git a/test/units/modules/network/nxos/test_nxos_vlan.py b/test/units/modules/network/nxos/test_nxos_vlan.py index ba2469eebdb..dcc9f131b5e 100644 --- a/test/units/modules/network/nxos/test_nxos_vlan.py +++ b/test/units/modules/network/nxos/test_nxos_vlan.py @@ -42,16 +42,11 @@ class TestNxosVlanModule(TestNxosModule): self.mock_get_config = patch('ansible.modules.network.nxos.nxos_vlan.get_config') self.get_config = self.mock_get_config.start() - self.mock_get_capabilities = patch('ansible.modules.network.nxos.nxos_vlan.get_capabilities') - self.get_capabilities = self.mock_get_capabilities.start() - self.get_capabilities.return_value = {'network_api': 'cliconf'} - def tearDown(self): super(TestNxosVlanModule, self).tearDown() self.mock_run_commands.stop() self.mock_load_config.stop() self.mock_get_config.stop() - self.mock_get_capabilities.stop() def load_fixtures(self, commands=None, device=''): def load_from_file(*args, **kwargs):