From 08957cf46e449e953b7b84532af3dd47b3ed5f34 Mon Sep 17 00:00:00 2001 From: Kedar Kekan <4506537+kedarX@users.noreply.github.com> Date: Thu, 11 Jan 2018 10:08:11 +0530 Subject: [PATCH] cliconf and netconf refactor of iosxr_logging (#34495) * * cliconf and netconf refactor of iosxr_logging * * documentation issue fix * * adds required_if and mutually_exclusive in arg spec --- .../module_utils/network/iosxr/iosxr.py | 14 +- .../modules/network/iosxr/iosxr_banner.py | 4 +- .../modules/network/iosxr/iosxr_logging.py | 775 +++++++++++++----- .../targets/iosxr_logging/tasks/main.yaml | 1 + .../targets/iosxr_logging/tasks/netconf.yaml | 22 + .../iosxr_logging/tests/cli/basic.yaml | 99 ++- .../iosxr_logging/tests/netconf/basic.yaml | 174 ++++ test/sanity/pylint/ignore.txt | 1 - 8 files changed, 837 insertions(+), 253 deletions(-) create mode 100644 test/integration/targets/iosxr_logging/tasks/netconf.yaml create mode 100644 test/integration/targets/iosxr_logging/tests/netconf/basic.yaml diff --git a/lib/ansible/module_utils/network/iosxr/iosxr.py b/lib/ansible/module_utils/network/iosxr/iosxr.py index e40a3662da..cd05a2326f 100644 --- a/lib/ansible/module_utils/network/iosxr/iosxr.py +++ b/lib/ansible/module_utils/network/iosxr/iosxr.py @@ -65,6 +65,8 @@ NS_DICT = { 'INTERFACE-CONFIGURATIONS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"}, 'INFRA-STATISTICS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-infra-statsd-oper"}, 'INTERFACE-PROPERTIES_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-oper"}, + 'IP-DOMAIN_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-ip-domain-cfg"}, + 'SYSLOG_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-infra-syslog-cfg"}, } iosxr_provider_spec = { @@ -181,11 +183,17 @@ def build_xml_subtree(container_ele, xmap, param=None, opcode=None): child = etree.SubElement(parent, candidates[-1]) child.text = text + if meta.get('attrib', None) and opcode in ('delete', 'merge'): + child.set(BASE_1_0 + meta.get('attrib'), opcode) + if len(meta_subtree) > 1: for item in meta_subtree: container_ele.append(item) - return sub_root + if sub_root == container_ele: + return None + else: + return sub_root def build_xml(container, xmap=None, params=None, opcode=None): @@ -246,7 +254,9 @@ def build_xml(container, xmap=None, params=None, opcode=None): subtree_list = list() for param in to_list(params): - subtree_list.append(build_xml_subtree(container_ele, xmap, param, opcode=opcode)) + subtree_ele = build_xml_subtree(container_ele, xmap, param, opcode=opcode) + if subtree_ele is not None: + subtree_list.append(subtree_ele) for item in subtree_list: container_ele.append(item) diff --git a/lib/ansible/modules/network/iosxr/iosxr_banner.py b/lib/ansible/modules/network/iosxr/iosxr_banner.py index 8e9f81999b..c2c019435b 100644 --- a/lib/ansible/modules/network/iosxr/iosxr_banner.py +++ b/lib/ansible/modules/network/iosxr/iosxr_banner.py @@ -242,7 +242,9 @@ def main(): elif is_netconf(module): config_object = NCConfiguration(module) - result = config_object.run() + result = None + if config_object is not None: + result = config_object.run() module.exit_json(**result) diff --git a/lib/ansible/modules/network/iosxr/iosxr_logging.py b/lib/ansible/modules/network/iosxr/iosxr_logging.py index a03b6fb53b..88b4a7e794 100644 --- a/lib/ansible/modules/network/iosxr/iosxr_logging.py +++ b/lib/ansible/modules/network/iosxr/iosxr_logging.py @@ -16,318 +16,669 @@ DOCUMENTATION = """ --- module: iosxr_logging version_added: "2.4" -author: "Trishna Guha (@trishnaguha)" -short_description: Manage logging on network devices +author: + - "Trishna Guha (@trishnaguha)" + - "Kedar Kekan (@kedarX)" +short_description: Configuration management of system logging services on network devices description: - - This module provides declarative management of logging + - This module provides declarative management configuration of system logging (syslog) on Cisco IOS XR devices. -extends_documentation_fragment: iosxr notes: - - Tested against IOS XR 6.1.2 + - Tested against IOS XRv 6.1.2 options: dest: description: - - Destination of the logs. - choices: ['on', 'hostnameprefix', console', 'monitor', 'buffered'] + - Destination for system logging (syslog) messages. + choices: ['host', 'console', 'monitor', 'buffered', 'file'] name: description: - - If value of C(dest) is I(file) it indicates file-name, - for I(user) it indicates username and for I(host) indicates - the host name to be notified. + - When C(dest) = I(file) name indicates file-name + - When C(dest) = I(host) name indicates the host-name or ip-address of syslog server. + vrf: + description: + - vrf name when syslog server is configured, C(dest) = C(host) + default: default + version_added: 2.5 size: description: - - Size of buffer. The acceptable value is in range from 307200 to - 125000000 bytes. - default: 307200 + - Size of buffer when C(dest) = C(buffered). The acceptable value is in the range I(307200 to 125000000 bytes). Default 307200 + - Size of file when C(dest) = C(file). The acceptable value is in the range I(1 to 2097152)KB. Default 2 GB facility: description: - - Set logging facility. + - To configure the type of syslog facility in which system logging (syslog) messages are sent to syslog servers + Optional config for C(dest) = C(host) default: local7 + hostnameprefix: + description: + - To append a hostname prefix to system logging (syslog) messages logged to syslog servers. + Optional config for C(dest) = C(host) + version_added: 2.5 level: description: - - Set logging severity levels. + - Specifies the severity level for the logging. default: debugging + aliases: ['severity'] aggregate: - description: List of logging definitions. + description: List of syslog logging configuration definitions. state: description: - - State of the logging configuration. + - Existential state of the logging configuration on the node. default: present choices: ['present', 'absent'] """ EXAMPLES = """ -- name: configure hostnameprefix logging +- name: configure logging for syslog server host iosxr_logging: - dest: hostnameprefix - name: 172.16.0.1 + dest: host + name: 10.10.10.1 + level: critical state: present -- name: remove hostnameprefix logging configuration +- name: add hostnameprefix configuration iosxr_logging: - dest: hostnameprefix - name: 172.16.0.1 + hostnameprefix: host1 state: absent -- name: configure console logging level and facility +- name: add facility configuration + iosxr_logging: + facility: local1 + state: present + +- name: configure console logging level iosxr_logging: dest: console - facility: local7 level: debugging state: present -- name: enable logging to all +- name: configure monitor logging level iosxr_logging: - dest : on + dest: monitor + level: errors + state: present -- name: configure buffer size +- name: configure syslog to a file + iosxr_logging: + dest: file + name: file_name + size: 2048 + level: errors + state: present + +- name: configure buffered logging with size iosxr_logging: dest: buffered - size: 5000 + size: 5100000 - name: Configure logging using aggregate iosxr_logging: aggregate: - { dest: console, level: warning } - { dest: buffered, size: 4800000 } + - { dest: file, name: file3, size: 2048} + - { dest: host, name: host3, level: critical} - name: Delete logging using aggregate iosxr_logging: aggregate: - { dest: console, level: warning } - { dest: buffered, size: 4800000 } + - { dest: file, name: file3, size: 2048} + - { dest: host, name: host3, level: critical} state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device - returned: always + returned: always (empty list when no commands to send) type: list sample: + - logging 10.10.10.1 vrf default severity debugging - logging facility local7 - - logging hostnameprefix 172.16.0.1 + - logging hostnameprefix host1 + - logging console critical + - logging buffered 2097153 + - logging buffered warnings + - logging monitor errors + - logging file log_file maxfilesize 1024 severity info +xml: + description: NetConf rpc xml sent to device with transport C(netconf) + returned: always (empty list when no xml rpc to send) + type: list + version_added: 2.5 + sample: + - ' + + + + file1 + + 2097152 + 2 + + + + + ' """ import re - +import collections from copy import deepcopy from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.iosxr.iosxr import get_config, load_config -from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec +from ansible.module_utils.network.iosxr.iosxr import get_config, load_config, build_xml +from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, etree_findall +from ansible.module_utils.network.iosxr.iosxr import is_netconf, is_cliconf, etree_find from ansible.module_utils.network.common.utils import remove_default_spec -def validate_size(value, module): - if value: - if value and not int(307200) <= value <= int(125000000): - module.fail_json(msg='size must be between 307200 and 125000000') - else: - return value +severity_level = {'emergency': '0', + 'alert': '1', + 'critical': '2', + 'error': '3', + 'warning': '4', + 'notice': '5', + 'info': '6', + 'debug': '7', + 'disable': '15'} + +severity_transpose = {'emergencies': 'emergency', + 'alerts': 'alert', + 'critical': 'critical', + 'errors': 'error', + 'warning': 'warning', + 'notifications': 'notice', + 'informational': 'info', + 'debugging': 'debug'} -def map_obj_to_commands(updates, module): - commands = list() - want, have = updates - for w in want: - dest = w['dest'] - name = w['name'] - size = w['size'] - facility = w['facility'] - level = w['level'] - state = w['state'] - del w['state'] +class ConfigBase(object): + def __init__(self, module): + self._flag = None + self._module = module + self._result = {'changed': False, 'warnings': []} + self._want = list() + self._have = list() - if state == 'absent' and w in have: - if dest == 'hostnameprefix': - commands.append('no logging hostnameprefix {}'.format(name)) - elif dest: - commands.append('no logging {}'.format(dest)) - else: - module.fail_json(msg='dest must be among console, monitor, buffered, hostnameprefix, on') + def validate_size(self, value, type=None): + if value: + if type == 'buffer': + if value and not int(307200) <= value <= int(125000000): + self._module.fail_json(msg='buffer size must be between 307200 and 125000000') + elif type == 'file': + if value and not int(1) <= value <= int(2097152): + self._module.fail_json(msg='file size must be between 1 and 2097152') + return value - if facility: - commands.append('no logging facility {}'.format(facility)) + def map_params_to_obj(self, required_if=None): + aggregate = self._module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = self._module.params[key] - if state == 'present' and w not in have: - if facility: - commands.append('logging facility {}'.format(facility)) + d = item.copy() - if dest == 'hostnameprefix': - commands.append('logging hostnameprefix {}'.format(name)) + if d['dest'] not in ('host', 'file'): + d['name'] = None - elif dest == 'on': - commands.append('logging on') - - elif dest == 'buffered' and size: - commands.append('logging buffered {}'.format(size)) - - else: - dest_cmd = 'logging {}'.format(dest) - if level: - dest_cmd += ' {}'.format(level) - - commands.append(dest_cmd) - return commands - - -def parse_facility(line): - match = re.search(r'logging facility (\S+)', line, re.M) - if match: - facility = match.group(1) - else: - facility = 'local7' - - return facility - - -def parse_size(line, dest): - size = None - - if dest == 'buffered': - match = re.search(r'logging buffered (\S+)', line, re.M) - if match: - try: - int_size = int(match.group(1)) - except ValueError: - int_size = None - - if int_size: - if isinstance(int_size, int): - size = str(match.group(1)) + if d['dest'] == 'buffered': + if d['size'] is not None: + d['size'] = str(self.validate_size(d['size'], 'buffer')) + else: + d['size'] = str(307200) + elif d['dest'] == 'file': + if d['size'] is not None: + d['size'] = str(self.validate_size(d['size'], 'file')) + else: + d['size'] = str(2097152) else: - size = str(307200) + d['size'] = None - return size + if self._flag == 'NC': + d['level'] = severity_transpose[d['level']] + + self._want.append(d) + + else: + params = self._module.params + if params['dest'] not in ('host', 'file'): + params['name'] = None + + if params['dest'] == 'buffered': + if params['size'] is not None: + params['size'] = str(self.validate_size(params['size'], 'buffer')) + else: + params['size'] = str(307200) + elif params['dest'] == 'file': + if params['size'] is not None: + params['size'] = str(self.validate_size(params['size'], 'file')) + else: + params['size'] = str(2097152) + else: + params['size'] = None + + if self._flag == 'NC': + params['level'] = severity_transpose[params['level']] + + self._want.append({ + 'dest': params['dest'], + 'name': params['name'], + 'vrf': params['vrf'], + 'size': params['size'], + 'facility': params['facility'], + 'level': params['level'], + 'hostnameprefix': params['hostnameprefix'], + 'state': params['state'] + }) -def parse_name(line, dest): - if dest == 'hostnameprefix': +class CliConfiguration(ConfigBase): + def __init__(self, module): + super(CliConfiguration, self).__init__(module) + self._file_list = set() + self._host_list = set() + + def map_obj_to_commands(self): + commands = list() + for want_item in self._want: + dest = want_item['dest'] + name = want_item['name'] + size = want_item['size'] + facility = want_item['facility'] + level = want_item['level'] + vrf = want_item['vrf'] + hostnameprefix = want_item['hostnameprefix'] + state = want_item['state'] + del want_item['state'] + + have_size = None + have_console_level = None + have_monitor_level = None + have_prefix = None + have_facility = None + + for item in self._have: + if item['dest'] == 'buffered': + have_size = item['size'] + if item['dest'] == 'console': + have_console_level = item['level'] + if item['dest'] == 'monitor': + have_monitor_level = item['level'] + if item['dest'] is None and item['hostnameprefix'] is not None: + have_prefix = item['hostnameprefix'] + if item['dest'] is None and item['hostnameprefix'] is None and item['facility'] is not None: + have_facility = item['facility'] + + if state == 'absent': + if dest == 'host' and name in self._host_list: + commands.append('no logging {0} vrf {1}'.format(name, vrf)) + elif dest == 'file' and name in self._file_list: + commands.append('no logging file {0}'.format(name)) + elif dest == 'console' and have_console_level is not None: + commands.append('no logging {0}'.format(dest)) + elif dest == 'monitor' and have_monitor_level: + commands.append('no logging {0}'.format(dest)) + elif dest == 'buffered' and have_size: + commands.append('no logging {0}'.format(dest)) + + if dest is None and hostnameprefix is not None and have_prefix == hostnameprefix: + commands.append('no logging hostnameprefix {0}'.format(hostnameprefix)) + if dest is None and facility is not None and have_facility == facility: + commands.append('no logging facility {0}'.format(facility)) + + if state == 'present': + if dest == 'host' and name not in self._host_list: + if level == 'errors' or level == 'informational': + level = severity_transpose[level] + commands.append('logging {0} vrf {1} severity {2}'.format(name, vrf, level)) + elif dest == 'file' and name not in self._file_list: + if level == 'errors' or level == 'informational': + level = severity_transpose[level] + commands.append('logging file {0} maxfilesize {1} severity {2}'.format(name, size, level)) + elif dest == 'buffered' and (have_size is None or (have_size is not None and size != have_size)): + commands.append('logging buffered {0}'.format(size)) + elif dest == 'console' and (have_console_level is None or + (have_console_level is not None and have_console_level != level)): + commands.append('logging console {0}'.format(level)) + elif dest == 'monitor' and (have_monitor_level is None or + (have_monitor_level is not None and have_monitor_level != level)): + commands.append('logging monitor {0}'.format(level)) + + if dest is None and hostnameprefix is not None and (have_prefix is None or + (have_prefix is not None and hostnameprefix != have_prefix)): + commands.append('logging hostnameprefix {0}'.format(hostnameprefix)) + if dest is None and hostnameprefix is None and facility != have_facility: + commands.append('logging facility {0}'.format(facility)) + + self._result['commands'] = commands + if commands: + commit = not self._module.check_mode + diff = load_config(self._module, commands, commit=commit) + if diff: + self._result['diff'] = dict(prepared=diff) + self._result['changed'] = True + + def parse_facility(self, line): + match = re.search(r'logging facility (\S+)', line, re.M) + facility = None + if match: + facility = match.group(1) + + return facility + + def parse_size(self, line, dest): + size = None + + if dest == 'buffered': + match = re.search(r'logging buffered (\S+)', line, re.M) + if match: + try: + int_size = int(match.group(1)) + except ValueError: + int_size = None + + if int_size is not None: + if isinstance(int_size, int): + size = str(match.group(1)) + return size + + def parse_hostnameprefix(self, line): + prefix = None match = re.search(r'logging hostnameprefix (\S+)', line, re.M) if match: - name = match.group(1) - else: + prefix = match.group(1) + return prefix + + def parse_name(self, line, dest): name = None + if dest == 'file': + match = re.search(r'logging file (\S+)', line, re.M) + if match: + name = match.group(1) + elif dest == 'host': + match = re.search(r'logging (\S+)', line, re.M) + if match: + name = match.group(1) - return name + return name + def parse_level(self, line, dest): + level_group = ('emergencies', 'alerts', 'critical', 'errors', 'warning', + 'notifications', 'informational', 'debugging') -def parse_level(line, dest): - level_group = ('emergencies', 'alerts', 'critical', 'errors', 'warning', - 'notifications', 'informational', 'debugging') - - if dest == 'hostnameprefix': - level = 'debugging' - - else: - match = re.search(r'logging {} (\S+)'.format(dest), line, re.M) + level = None + match = re.search(r'logging {0} (\S+)'.format(dest), line, re.M) if match: if match.group(1) in level_group: level = match.group(1) - else: - level = 'debugging' - else: - level = 'debugging' - return level + return level + def parse_dest(self, line, group): + dest_group = ('console', 'monitor', 'buffered', 'file') + dest = None + if group in dest_group: + dest = group + elif 'vrf' in line: + dest = 'host' -def map_config_to_obj(module): + return dest - obj = [] - dest_group = ('console', 'hostnameprefix', 'monitor', 'buffered', 'on') + def parse_vrf(self, line, dest): + vrf = None + if dest == 'host': + match = re.search(r'logging (\S+) vrf (\S+)', line, re.M) + if match: + vrf = match.group(2) + return vrf - data = get_config(module, config_filter='logging') - lines = data.split("\n") + def map_config_to_obj(self): + data = get_config(self._module, config_filter='logging') + lines = data.split("\n") - for line in lines: - match = re.search(r'logging (\S+)', line, re.M) - if match: - if match.group(1) in dest_group: - dest = match.group(1) - obj.append({ + for line in lines: + match = re.search(r'logging (\S+)', line, re.M) + if match: + dest = self.parse_dest(line, match.group(1)) + name = self.parse_name(line, dest) + if dest == 'host' and name is not None: + self._host_list.add(name) + if dest == 'file' and name is not None: + self._file_list.add(name) + + self._have.append({ 'dest': dest, - 'name': parse_name(line, dest), - 'size': parse_size(line, dest), - 'facility': parse_facility(line), - 'level': parse_level(line, dest) + 'name': name, + 'size': self.parse_size(line, dest), + 'facility': self.parse_facility(line), + 'level': self.parse_level(line, dest), + 'vrf': self.parse_vrf(line, dest), + 'hostnameprefix': self.parse_hostnameprefix(line), }) - return obj + def run(self): + self.map_params_to_obj() + self.map_config_to_obj() + self.map_obj_to_commands() + + return self._result -def map_params_to_obj(module, required_if=None): - obj = [] +class NCConfiguration(ConfigBase): + def __init__(self, module): + super(NCConfiguration, self).__init__(module) + self._flag = 'NC' + self._log_file_meta = collections.OrderedDict() + self._log_host_meta = collections.OrderedDict() + self._log_console_meta = collections.OrderedDict() + self._log_monitor_meta = collections.OrderedDict() + self._log_buffered_size_meta = collections.OrderedDict() + self._log_buffered_level_meta = collections.OrderedDict() + self._log_facility_meta = collections.OrderedDict() + self._log_prefix_meta = collections.OrderedDict() - aggregate = module.params.get('aggregate') - if aggregate: - for item in aggregate: - for key in item: - if item.get(key) is None: - item[key] = module.params[key] + def map_obj_to_xml_rpc(self): + self._log_file_meta.update([ + ('files', {'xpath': 'syslog/files', 'tag': True, 'operation': 'edit'}), + ('file', {'xpath': 'syslog/files/file', 'tag': True, 'operation': 'edit', 'attrib': "operation"}), + ('a:name', {'xpath': 'syslog/files/file/file-name', 'operation': 'edit'}), + ('file-attrib', {'xpath': 'syslog/files/file/file-log-attributes', 'tag': True, 'operation': 'edit'}), + ('a:size', {'xpath': 'syslog/files/file/file-log-attributes/max-file-size', 'operation': 'edit'}), + ('a:level', {'xpath': 'syslog/files/file/file-log-attributes/severity', 'operation': 'edit'}), + ]) + self._log_host_meta.update([ + ('host-server', {'xpath': 'syslog/host-server', 'tag': True, 'operation': 'edit'}), + ('vrfs', {'xpath': 'syslog/host-server/vrfs', 'tag': True, 'operation': 'edit'}), + ('vrf', {'xpath': 'syslog/host-server/vrfs/vrf', 'tag': True, 'operation': 'edit'}), + ('a:vrf', {'xpath': 'syslog/host-server/vrfs/vrf/vrf-name', 'operation': 'edit'}), + ('ipv4s', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s', 'tag': True, 'operation': 'edit'}), + ('ipv4', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s/ipv4', 'tag': True, 'operation': 'edit', 'attrib': "operation"}), + ('a:name', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s/ipv4/address', 'operation': 'edit'}), + ('ipv4-sev', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s/ipv4/ipv4-severity-port', 'tag': True, 'operation': 'edit'}), + ('a:level', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s/ipv4/ipv4-severity-port/severity', 'operation': 'edit'}), + ]) + self._log_console_meta.update([ + ('a:enable-console', {'xpath': 'syslog/enable-console-logging', 'operation': 'edit', 'attrib': "operation"}), + ('console', {'xpath': 'syslog/console-logging', 'tag': True, 'operation': 'edit', 'attrib': "operation"}), + ('a:console-level', {'xpath': 'syslog/console-logging/logging-level', 'operation': 'edit'}), + ]) + self._log_monitor_meta.update([ + ('monitor', {'xpath': 'syslog/monitor-logging', 'tag': True, 'operation': 'edit', 'attrib': "operation"}), + ('a:monitor-level', {'xpath': 'syslog/monitor-logging/logging-level', 'operation': 'edit'}), + ]) + self._log_buffered_size_meta.update([ + ('buffered', {'xpath': 'syslog/buffered-logging', 'tag': True, 'operation': 'edit', 'attrib': "operation"}), + ('a:size', {'xpath': 'syslog/buffered-logging/buffer-size', 'operation': 'edit'}), + ]) + self._log_buffered_level_meta.update([ + ('buffered', {'xpath': 'syslog/buffered-logging', 'tag': True, 'operation': 'edit', 'attrib': "operation"}), + ('a:level', {'xpath': 'syslog/buffered-logging/logging-level', 'operation': 'edit'}), + ]) + self._log_facility_meta.update([ + ('facility', {'xpath': 'syslog/logging-facilities', 'tag': True, 'operation': 'edit', 'attrib': "operation"}), + ('a:facility', {'xpath': 'syslog/logging-facilities/facility-level', 'operation': 'edit'}), + ]) + self._log_prefix_meta.update([ + ('a:hostnameprefix', {'xpath': 'syslog/host-name-prefix', 'operation': 'edit', 'attrib': "operation"}), + ]) - module._check_required_if(required_if, item) - d = item.copy() + state = self._module.params['state'] - if d['dest'] != 'hostnameprefix': - d['name'] = None + _get_filter = build_xml('syslog', opcode="filter") + running = get_config(self._module, source='running', config_filter=_get_filter) - if d['dest'] == 'buffered': - if 'size' in d: - d['size'] = str(validate_size(d['size'], module)) - elif 'size' not in d: - d['size'] = str(307200) - else: - pass + file_ele = etree_findall(running, 'file') + file_list = list() + if len(file_ele): + for file in file_ele: + file_name = etree_find(file, 'file-name') + file_list.append(file_name.text if file_name is not None else None) + vrf_ele = etree_findall(running, 'vrf') + host_list = list() + for vrf in vrf_ele: + host_ele = etree_findall(vrf, 'ipv4') + for host in host_ele: + host_name = etree_find(host, 'address') + host_list.append(host_name.text if host_name is not None else None) - if d['dest'] != 'buffered': - d['size'] = None + console_ele = etree_find(running, 'console-logging') + console_level = etree_find(console_ele, 'logging-level') if console_ele is not None else None + have_console = console_level.text if console_level is not None else None - obj.append(d) + monitor_ele = etree_find(running, 'monitor-logging') + monitor_level = etree_find(monitor_ele, 'logging-level') if monitor_ele is not None else None + have_monitor = monitor_level.text if monitor_level is not None else None - else: - if module.params['dest'] != 'hostnameprefix': - module.params['name'] = None + buffered_ele = etree_find(running, 'buffered-logging') + buffered_size = etree_find(buffered_ele, 'buffer-size') if buffered_ele is not None else None + have_buffered = buffered_size.text if buffered_size is not None else None - if module.params['dest'] == 'buffered': - if not module.params['size']: - module.params['size'] = str(307200) - else: - module.params['size'] = None + facility_ele = etree_find(running, 'logging-facilities') + facility_level = etree_find(facility_ele, 'facility-level') if facility_ele is not None else None + have_facility = facility_level.text if facility_level is not None else None - if module.params['size'] is None: - obj.append({ - 'dest': module.params['dest'], - 'name': module.params['name'], - 'size': module.params['size'], - 'facility': module.params['facility'], - 'level': module.params['level'], - 'state': module.params['state'] - }) + prefix_ele = etree_find(running, 'host-name-prefix') + have_prefix = prefix_ele.text if prefix_ele is not None else None - else: - obj.append({ - 'dest': module.params['dest'], - 'name': module.params['name'], - 'size': str(validate_size(module.params['size'], module)), - 'facility': module.params['facility'], - 'level': module.params['level'], - 'state': module.params['state'] - }) + console_enable_ele = etree_find(running, 'enable-console-logging') + have_console_enable = console_enable_ele.text if console_enable_ele is not None else None - return obj + file_params = list() + host_params = list() + console_params = dict() + monitor_params = dict() + buffered_params = dict() + facility_params = dict() + prefix_params = dict() + + opcode = None + if state == 'absent': + opcode = "delete" + for item in self._want: + if item['dest'] == 'file' and item['name'] in file_list: + item['level'] = severity_level[item['level']] + file_params.append(item) + elif item['dest'] == 'host' and item['name'] in host_list: + item['level'] = severity_level[item['level']] + host_params.append(item) + elif item['dest'] == 'console' and have_console and have_console_enable: + console_params.update({'console-level': item['level']}) + elif item['dest'] == 'monitor' and have_monitor: + monitor_params.update({'monitor-level': item['level']}) + elif item['dest'] == 'buffered' and have_buffered: + buffered_params['size'] = str(item['size']) if item['size'] else None + buffered_params['level'] = item['level'] if item['level'] else None + elif item['dest'] is None and item['hostnameprefix'] is None and \ + item['facility'] is not None and have_facility: + facility_params.update({'facility': item['facility']}) + elif item['dest'] is None and item['hostnameprefix'] is not None and have_prefix: + prefix_params.update({'hostnameprefix': item['hostnameprefix']}) + elif state == 'present': + opcode = 'merge' + for item in self._want: + if item['dest'] == 'file': + item['level'] = severity_level[item['level']] + file_params.append(item) + elif item['dest'] == 'host': + item['level'] = severity_level[item['level']] + host_params.append(item) + elif item['dest'] == 'console': + console_params.update({'console-level': item['level']}) + elif item['dest'] == 'monitor': + monitor_params.update({'monitor-level': item['level']}) + elif item['dest'] == 'buffered': + buffered_params['size'] = str(item['size']) if item['size'] else None + buffered_params['level'] = item['level'] if item['level'] else None + elif item['dest'] is None and item['hostnameprefix'] is None and \ + item['facility'] is not None: + facility_params.update({'facility': item['facility']}) + elif item['dest'] is None and item['hostnameprefix'] is not None: + prefix_params.update({'hostnameprefix': item['hostnameprefix']}) + + self._result['xml'] = [] + _edit_filter_list = list() + if opcode: + if len(file_params): + _edit_filter_list.append(build_xml('syslog', xmap=self._log_file_meta, + params=file_params, opcode=opcode)) + if len(host_params): + _edit_filter_list.append(build_xml('syslog', xmap=self._log_host_meta, + params=host_params, opcode=opcode)) + if len(console_params): + _edit_filter_list.append(build_xml('syslog', xmap=self._log_console_meta, + params=console_params, opcode=opcode)) + if len(monitor_params): + _edit_filter_list.append(build_xml('syslog', xmap=self._log_monitor_meta, + params=monitor_params, opcode=opcode)) + if len(buffered_params): + _edit_filter_list.append(build_xml('syslog', xmap=self._log_buffered_size_meta, + params=buffered_params, opcode=opcode)) + _edit_filter_list.append(build_xml('syslog', xmap=self._log_buffered_level_meta, + params=buffered_params, opcode=opcode)) + if len(facility_params): + _edit_filter_list.append(build_xml('syslog', xmap=self._log_facility_meta, + params=facility_params, opcode=opcode)) + if len(prefix_params): + _edit_filter_list.append(build_xml('syslog', xmap=self._log_prefix_meta, + params=prefix_params, opcode=opcode)) + + diff = None + if len(_edit_filter_list): + commit = not self._module.check_mode + diff = load_config(self._module, _edit_filter_list, commit=commit, running=running, + nc_get_filter=_get_filter) + + if diff: + if self._module._diff: + self._result['diff'] = dict(prepared=diff) + + self._result['xml'] = _edit_filter_list + self._result['changed'] = True + + def run(self): + self.map_params_to_obj() + self.map_obj_to_xml_rpc() + + return self._result def main(): """ main entry point for module execution """ element_spec = dict( - dest=dict(type='str', choices=['on', 'hostnameprefix', 'console', 'monitor', 'buffered']), + dest=dict(type='str', choices=['host', 'console', 'monitor', 'buffered', 'file']), name=dict(type='str'), size=dict(type='int'), + vrf=dict(type='str', default='default'), facility=dict(type='str', default='local7'), - level=dict(type='str', default='debugging'), + hostnameprefix=dict(type='str'), + level=dict(type='str', default='informational', aliases=['severity'], + choices=['emergencies', 'alerts', 'critical', 'errors', 'warning', + 'notifications', 'informational', 'debugging']), state=dict(default='present', choices=['present', 'absent']), ) @@ -336,37 +687,37 @@ def main(): # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) + mutually_exclusive = [('dest', 'facility', 'hostnameprefix')] + + required_if = [('dest', 'host', ['name']), + ('dest', 'file', ['name']), + ('dest', 'buffered', ['size']), + ('dest', 'console', ['level']), + ('dest', 'monitor', ['level'])] + argument_spec = dict( - aggregate=dict(type='list', elements='dict', options=aggregate_spec), + aggregate=dict(type='list', elements='dict', options=aggregate_spec, + mutually_exclusive=mutually_exclusive, required_if=required_if), ) argument_spec.update(element_spec) argument_spec.update(iosxr_argument_spec) - required_if = [('dest', 'hostnameprefix', ['name'])] - module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, required_if=required_if, supports_check_mode=True) - warnings = list() - - result = {'changed': False} - - want = map_params_to_obj(module, required_if=required_if) - have = map_config_to_obj(module) - commands = map_obj_to_commands((want, have), module) - - result['commands'] = commands - result['warnings'] = warnings - - if commands: - commit = not module.check_mode - diff = load_config(module, commands, commit=commit) - if diff: - result['diff'] = dict(prepared=diff) - result['changed'] = True + config_object = None + if is_cliconf(module): + module.deprecate(msg="cli support for 'iosxr_logging' is deprecated. Use transport netconf instead", + version="4 releases from v2.5") + config_object = CliConfiguration(module) + elif is_netconf(module): + config_object = NCConfiguration(module) + if config_object: + result = config_object.run() module.exit_json(**result) if __name__ == '__main__': diff --git a/test/integration/targets/iosxr_logging/tasks/main.yaml b/test/integration/targets/iosxr_logging/tasks/main.yaml index 415c99d8b1..af08869c92 100644 --- a/test/integration/targets/iosxr_logging/tasks/main.yaml +++ b/test/integration/targets/iosxr_logging/tasks/main.yaml @@ -1,2 +1,3 @@ --- - { include: cli.yaml, tags: ['cli'] } +- { include: netconf.yaml, tags: ['netconf'] } diff --git a/test/integration/targets/iosxr_logging/tasks/netconf.yaml b/test/integration/targets/iosxr_logging/tasks/netconf.yaml new file mode 100644 index 0000000000..a5632f8461 --- /dev/null +++ b/test/integration/targets/iosxr_logging/tasks/netconf.yaml @@ -0,0 +1,22 @@ +--- +- name: collect all netconf test cases + find: + paths: "{{ role_path }}/tests/netconf" + patterns: "{{ testcase }}.yaml" + register: test_cases + delegate_to: localhost + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case (connection=netconf) + include: "{{ test_case_to_run }} ansible_connection=netconf" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run + +- name: run test case (connection=local) + include: "{{ test_case_to_run }} ansible_connection=local" + with_first_found: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/iosxr_logging/tests/cli/basic.yaml b/test/integration/targets/iosxr_logging/tests/cli/basic.yaml index 5ef97cd9ab..477d30481d 100644 --- a/test/integration/targets/iosxr_logging/tests/cli/basic.yaml +++ b/test/integration/targets/iosxr_logging/tests/cli/basic.yaml @@ -1,33 +1,33 @@ --- # Remove old logging entries so that they don't conflict with tests -- name: Remove host logging +- name: remove host logging iosxr_logging: - dest: hostnameprefix + dest: host name: 172.16.0.1 state: absent provider: "{{ cli }}" -- name: Remove console logging +- name: remove console logging iosxr_logging: dest: console - level: warning state: absent provider: "{{ cli }}" register: result -- name: Remove buffer +- name: remove buffered logging iosxr_logging: dest: buffered - size: 4800000 + size: 2097155 state: absent provider: "{{ cli }}" register: result # Start tests -- name: Set up host logging - iosxr_logging: - dest: hostnameprefix +- name: set up syslog host logging + iosxr_logging: &addhostlog + dest: host name: 172.16.0.1 + level: errors state: present provider: "{{ cli }}" register: result @@ -35,24 +35,19 @@ - assert: that: - 'result.changed == true' - - '"logging hostnameprefix 172.16.0.1" in result.commands' - - '"logging facility local7" in result.commands' + - '"logging 172.16.0.1 vrf default severity error" in result.commands' -- name: Set up host logging again (idempotent) - iosxr_logging: - dest: hostnameprefix - name: 172.16.0.1 - state: present - provider: "{{ cli }}" +- name: set up syslog host logging (idempotent) + iosxr_logging: *addhostlog register: result -- assert: +- assert: &false that: - 'result.changed == false' -- name: Delete/disable host logging - iosxr_logging: - dest: hostnameprefix +- name: delete/disable syslog host logging + iosxr_logging: &delhostlog + dest: host name: 172.16.0.1 state: absent provider: "{{ cli }}" @@ -61,22 +56,16 @@ - assert: that: - 'result.changed == true' - - '"no logging hostnameprefix 172.16.0.1" in result.commands' + - '"no logging 172.16.0.1 vrf default" in result.commands' -- name: Delete/disable host logging (idempotent) - iosxr_logging: - dest: hostnameprefix - name: 172.16.0.1 - state: absent - provider: "{{ cli }}" +- name: delete/disable syslog host logging (idempotent) + iosxr_logging: *delhostlog register: result -- assert: - that: - - 'result.changed == false' +- assert: *false -- name: Console logging with level warning - iosxr_logging: +- name: add console logging with level warning + iosxr_logging: &consolelog dest: console level: warning state: present @@ -88,10 +77,29 @@ - 'result.changed == true' - '"logging console warning" in result.commands' -- name: Configure Buffer size - iosxr_logging: +- name: console logging with level warning (idempotent) + iosxr_logging: *consolelog + register: result + +- assert: *false + +- name: remove console logging with level warning + iosxr_logging: + dest: console + level: warning + state: absent + provider: "{{ cli }}" + register: result + +- assert: &true + that: + - 'result.changed == true' + +- name: configure buffered logging size + iosxr_logging: &bufferlog dest: buffered size: 4800000 + state: present provider: "{{ cli }}" register: result @@ -100,11 +108,28 @@ - 'result.changed == true' - '"logging buffered 4800000" in result.commands' -- name: Change logging parameters using aggregate +- name: configure buffered logging size (idempotence) + iosxr_logging: *bufferlog + register: result + +- assert: *false + +- name: remove buffered logging size + iosxr_logging: + dest: buffered + size: 4800000 + state: absent + provider: "{{ cli }}" + register: result + +- assert: *true + +- name: change logging parameters using aggregate iosxr_logging: aggregate: - { dest: console, level: notifications } - { dest: buffered, size: 4700000 } + state: present provider: "{{ cli }}" register: result @@ -114,7 +139,7 @@ - '"logging buffered 4700000" in result.commands' - '"logging console notifications" in result.commands' -- name: remove logging as collection tearDown +- name: remove logging parameters using aggregate iosxr_logging: aggregate: - { dest: console, level: notifications } diff --git a/test/integration/targets/iosxr_logging/tests/netconf/basic.yaml b/test/integration/targets/iosxr_logging/tests/netconf/basic.yaml new file mode 100644 index 0000000000..91d4e36c09 --- /dev/null +++ b/test/integration/targets/iosxr_logging/tests/netconf/basic.yaml @@ -0,0 +1,174 @@ +--- +# Remove old logging entries so that they don't conflict with tests +- name: remove host logging + iosxr_logging: + dest: host + name: 172.16.0.1 + state: absent + provider: "{{ netconf }}" + +- name: remove console logging + iosxr_logging: + dest: console + state: absent + provider: "{{ netconf }}" + register: result + +- name: remove buffered logging + iosxr_logging: + dest: buffered + size: 2097155 + state: absent + provider: "{{ netconf }}" + register: result + +# Start tests +- name: set up syslog host logging + iosxr_logging: &addhostlog + dest: host + name: 172.16.0.1 + level: errors + state: present + provider: "{{ netconf }}" + register: result + +- assert: + that: + - 'result.changed == true' + - '"172.16.0.1" in result.xml[0]' + +- name: set up syslog host logging (idempotent) + iosxr_logging: *addhostlog + register: result + +- assert: &false + that: + - 'result.changed == false' + +- name: delete/disable syslog host logging + iosxr_logging: &delhostlog + dest: host + name: 172.16.0.1 + state: absent + provider: "{{ netconf }}" + register: result + +- assert: + that: + - 'result.changed == true' + - '"172.16.0.1" in result.xml[0]' + - '"delete" in result.xml[0]' + +- name: delete/disable syslog host logging (idempotent) + iosxr_logging: *delhostlog + register: result + +- assert: *false + +- name: add console logging with level warning + iosxr_logging: &consolelog + dest: console + level: warning + state: present + provider: "{{ netconf }}" + register: result + +- assert: + that: + - 'result.changed == true' + - '"console" in result.xml[0]' + - '"warning" in result.xml[0]' + +- name: console logging with level warning (idempotent) + iosxr_logging: *consolelog + register: result + +- assert: *false + +- name: remove console logging with level warning + iosxr_logging: + dest: console + level: warning + state: absent + provider: "{{ netconf }}" + register: result + +- assert: &true + that: + - 'result.changed == true' + +- name: configure buffered logging size + iosxr_logging: &bufferlog + dest: buffered + size: 4800000 + state: present + provider: "{{ netconf }}" + register: result + +- assert: + that: + - 'result.changed == true' + - '"buffered" in result.xml[0]' + - '"4800000" in result.xml[0]' + +- name: configure buffered logging size (idempotence) + iosxr_logging: *bufferlog + register: result + +- assert: *false + +- name: remove buffered logging size + iosxr_logging: + dest: buffered + size: 4800000 + state: absent + provider: "{{ netconf }}" + register: result + +- assert: *true + +- name: change logging parameters using aggregate + iosxr_logging: + aggregate: + - { dest: console, level: notifications } + - { dest: buffered, size: 4700000 } + - { dest: monitor, level: alerts } + - { dest: host, name: 10.10.10.1, level: errors } + - { dest: host, name: 10.10.10.2 } + - { dest: file, name: file1, size: 2048, level: critical} + - { dest: file, name: file2, size: 2048 } + - { facility: local3 } + - { hostnameprefix: host3 } + state: present + provider: "{{ netconf }}" + register: result + +- assert: + that: + - 'result.changed == true' + - '"file1" in result.xml[0]' + - '"file2" in result.xml[0]' + - '"10.10.10.1" in result.xml[1]' + - '"10.10.10.2" in result.xml[1]' + - '"notice" in result.xml[2]' + - '"alert" in result.xml[3]' + - '"4700000" in result.xml[4]' + - '"info" in result.xml[5]' + - '"local3" in result.xml[6]' + - '"host3" in result.xml[7]' + +- name: remove logging parameters using aggregate + iosxr_logging: + aggregate: + - { dest: console, level: notifications } + - { dest: buffered, size: 4700000 } + state: absent + provider: "{{ netconf }}" + register: result + +- assert: + that: + - 'result.changed == true' + - '"console" in result.xml[0]' + - '"buffered" in result.xml[1]' + - '"buffered" in result.xml[2]' diff --git a/test/sanity/pylint/ignore.txt b/test/sanity/pylint/ignore.txt index 666b0db9a7..e08f39099a 100644 --- a/test/sanity/pylint/ignore.txt +++ b/test/sanity/pylint/ignore.txt @@ -82,7 +82,6 @@ lib/ansible/modules/network/ios/ios_l3_interface.py ansible-format-automatic-spe lib/ansible/modules/network/ios/ios_vlan.py ansible-format-automatic-specification lib/ansible/modules/network/iosxr/iosxr_banner.py ansible-format-automatic-specification lib/ansible/modules/network/iosxr/iosxr_interface.py ansible-format-automatic-specification -lib/ansible/modules/network/iosxr/iosxr_logging.py ansible-format-automatic-specification lib/ansible/modules/network/nxos/nxos_logging.py ansible-format-automatic-specification lib/ansible/modules/network/nxos/nxos_snapshot.py ansible-format-automatic-specification lib/ansible/modules/network/vyos/vyos_vlan.py ansible-format-automatic-specification