diff --git a/lib/ansible/module_utils/network/nxos/nxos.py b/lib/ansible/module_utils/network/nxos/nxos.py index b44156a0b01..ceedfea2ed1 100644 --- a/lib/ansible/module_utils/network/nxos/nxos.py +++ b/lib/ansible/module_utils/network/nxos/nxos.py @@ -301,14 +301,16 @@ class LocalNxapi: if isinstance(commands, (list, set, tuple)): commands = ' ;'.join(commands) - msg = { - 'version': version, - 'type': command_type, - 'chunk': chunk, - 'sid': sid, - 'input': commands, - 'output_format': 'json' - } + # Order should not matter but some versions of NX-OS software fail + # to process the payload properly if 'input' gets serialized before + # 'type' and the payload of 'input' contains the word 'type'. + msg = collections.OrderedDict() + msg['version'] = version + msg['type'] = command_type + msg['chunk'] = chunk + msg['sid'] = sid + msg['input'] = commands + msg['output_format'] = 'json' return dict(ins_api=msg) @@ -448,7 +450,6 @@ class LocalNxapi: commands = 'config replace {0}'.format(replace) commands = to_list(commands) - msg, msg_timestamps = self.send_request(commands, output='config', check_status=True, return_error=return_error, opts=opts) if return_error: @@ -664,13 +665,18 @@ class HttpApi: raise ValueError("commit comment is not supported") def read_module_context(self, module_key): - if self._module_context.get(module_key): - return self._module_context[module_key] + try: + module_context = self._connection.read_module_context(module_key) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) - return None + return module_context def save_module_context(self, module_key, module_context): - self._module_context[module_key] = module_context + try: + self._connection.save_module_context(module_key, module_context) + except ConnectionError as exc: + self._module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) return None diff --git a/lib/ansible/modules/network/nxos/nxos_logging.py b/lib/ansible/modules/network/nxos/nxos_logging.py index be3d451d3c5..e37a02f3e88 100644 --- a/lib/ansible/modules/network/nxos/nxos_logging.py +++ b/lib/ansible/modules/network/nxos/nxos_logging.py @@ -84,6 +84,9 @@ options: purge: description: - Remove any switch logging configuration that does not match what has been configured + Not supported for ansible_connection local. + All nxos_logging tasks must use the same ansible_connection type. + type: bool default: no version_added: '2.8' @@ -182,6 +185,7 @@ from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_ from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, check_args, normalize_interface from ansible.module_utils.basic import AnsibleModule + STATIC_CLI = {'link-enable': 'logging event link-status enable', 'link-default': 'logging event link-status default', 'trunk-enable': 'logging event trunk-status enable', @@ -207,7 +211,7 @@ DEFAULT_LOGGING_LEVEL = {0: [], DEST_GROUP = ['console', 'logfile', 'module', 'monitor', 'server'] -def map_obj_to_commands(updates): +def map_obj_to_commands(module, updates): commands = list() want, have = updates @@ -295,8 +299,9 @@ def map_obj_to_commands(updates): commands.append('logging level {0} {1}'.format( w['facility'], STATIC_CLI[w['facility_link_status']])) else: - commands.append('logging level {0} {1}'.format(w['facility'], - w['facility_level'])) + if not match_facility_default(module, w['facility'], w['facility_level']): + commands.append('logging level {0} {1}'.format(w['facility'], + w['facility_level'])) if w['interface']: commands.append('logging source-interface {0} {1}'.format(*split_interface(w['interface']))) @@ -313,6 +318,30 @@ def map_obj_to_commands(updates): return commands +def match_facility_default(module, facility, want_level): + ''' Check wanted facility to see if it matches current device default ''' + + matches_default = False + # Sample output from show logging level command + # Facility Default Severity Current Session Severity + # -------- ---------------- ------------------------ + # bfd 5 5 + # + # 0(emergencies) 1(alerts) 2(critical) + # 3(errors) 4(warnings) 5(notifications) + # 6(information) 7(debugging) + + regexl = r'\S+\s+(\d+)\s+(\d+)' + cmd = {'command': 'show logging level {0}'.format(facility), 'output': 'text'} + facility_data = run_commands(module, cmd) + for line in facility_data[0].split('\n'): + mo = re.search(regexl, line) + if mo and int(mo.group(1)) == int(want_level) and int(mo.group(2)) == int(want_level): + matches_default = True + + return matches_default + + def split_interface(interface): match = re.search(r'(\D+)(\S*)', interface, re.M) if match: @@ -719,7 +748,7 @@ def main(): timestamp=dict(choices=['microseconds', 'milliseconds', 'seconds']), state=dict(default='present', choices=['present', 'absent']), aggregate=dict(type='list'), - purge=dict(default=False, type='bool') + purge=dict(default=False, type='bool'), ) argument_spec.update(nxos_argument_spec) @@ -742,7 +771,7 @@ def main(): merged_wants = merge_wants(read_module_context(module), want) have = map_config_to_obj(module) - commands = map_obj_to_commands((want, have)) + commands = map_obj_to_commands(module, (want, have)) result['commands'] = commands if commands: @@ -753,7 +782,7 @@ def main(): save_module_context(module, merged_wants) if module.params.get('purge'): - pcommands = map_obj_to_commands((outliers(have, merged_wants), have)) + pcommands = map_obj_to_commands(module, (outliers(have, merged_wants), have)) if pcommands: if not module.check_mode: load_config(module, pcommands) diff --git a/lib/ansible/plugins/httpapi/nxos.py b/lib/ansible/plugins/httpapi/nxos.py index 44a7345d834..2f8c40ba27b 100644 --- a/lib/ansible/plugins/httpapi/nxos.py +++ b/lib/ansible/plugins/httpapi/nxos.py @@ -17,6 +17,7 @@ version_added: "2.6" import json import re +import collections from ansible.module_utils._text import to_text from ansible.module_utils.connection import ConnectionError @@ -36,6 +37,18 @@ class HttpApi(HttpApiBase): def __init__(self, *args, **kwargs): super(HttpApi, self).__init__(*args, **kwargs) self._device_info = None + self._module_context = {} + + def read_module_context(self, module_key): + if self._module_context.get(module_key): + return self._module_context[module_key] + + return None + + def save_module_context(self, module_key, module_context): + self._module_context[module_key] = module_context + + return None def send_request(self, data, **message_kwargs): output = None @@ -201,12 +214,15 @@ def request_builder(commands, output, version='1.0', chunk='0', sid=None): if isinstance(commands, (list, set, tuple)): commands = ' ;'.join(commands) - msg = { - 'version': version, - 'type': command_type, - 'chunk': chunk, - 'sid': sid, - 'input': commands, - 'output_format': 'json' - } + # Order should not matter but some versions of NX-OS software fail + # to process the payload properly if 'input' gets serialized before + # 'type' and the payload of 'input' contains the word 'type'. + msg = collections.OrderedDict() + msg['version'] = version + msg['type'] = command_type + msg['chunk'] = chunk + msg['sid'] = sid + msg['input'] = commands + msg['output_format'] = 'json' + return json.dumps(dict(ins_api=msg))