From b6290e1d156af608bd79118d209a64a051c55001 Mon Sep 17 00:00:00 2001 From: sushma-alethea <52454757+sushma-alethea@users.noreply.github.com> Date: Tue, 27 Aug 2019 08:58:33 +0530 Subject: [PATCH] icx: new module icx_logging (#60036) * new module * new terminal * new terminal * new cliconf * cliconf * icx cliconf * icx_cliconf * icx test units module * icx units module * icx banner unit test * PR changes resolved * changes resolved * Changes Resolved * check_running_config changes resolved * added notes * removed icx rst * new changes * deleted icx rst * icx .rst * modified platform_index.rst * modified platform_index.rst * changes resolved * PR comments resolved * Update platform_index.rst PR comment resolved * test * Revert "test" This reverts commit 99b72c661431bb2514253acaee246acb85260451. * new module icx_logging * new fixes * new fix * new fix * new fix * new changes * notes updated --- .../modules/network/icx/icx_logging.py | 582 ++++++++++++++++++ .../icx/fixtures/icx_logging_config.cfg | 17 + .../modules/network/icx/test_icx_logging.py | 149 +++++ 3 files changed, 748 insertions(+) create mode 100644 lib/ansible/modules/network/icx/icx_logging.py create mode 100644 test/units/modules/network/icx/fixtures/icx_logging_config.cfg create mode 100644 test/units/modules/network/icx/test_icx_logging.py diff --git a/lib/ansible/modules/network/icx/icx_logging.py b/lib/ansible/modules/network/icx/icx_logging.py new file mode 100644 index 00000000000..a1cfbc093f9 --- /dev/null +++ b/lib/ansible/modules/network/icx/icx_logging.py @@ -0,0 +1,582 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = """ +--- +module: icx_logging +version_added: "2.9" +author: "Ruckus Wireless (@Commscope)" +short_description: Manage logging on Ruckus ICX 7000 series switches +description: + - This module provides declarative management of logging + on Ruckus ICX 7000 series switches. +notes: + - Tested against ICX 10.1. + - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). +options: + dest: + description: + - Destination of the logs. + choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424'] + type: str + name: + description: + - ipv4 address/ipv6 address/name of syslog server. + type: str + udp_port: + description: + - UDP port of destination host(syslog server). + type: str + facility: + description: + - Specifies log facility to log messages from the device. + choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user', + 'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp'] + type: str + level: + description: + - Specifies the message level. + type: list + choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', + 'notifications', 'warnings'] + aggregate: + description: + - List of logging definitions. + type: list + suboptions: + dest: + description: + - Destination of the logs. + choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424'] + type: str + name: + description: + - ipv4 address/ipv6 address/name of syslog server. + type: str + udp_port: + description: + - UDP port of destination host(syslog server). + type: str + facility: + description: + - Specifies log facility to log messages from the device. + choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user', + 'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp'] + type: str + level: + description: + - Specifies the message level. + type: list + choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', + 'notifications', 'warnings'] + state: + description: + - State of the logging configuration. + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overriden, by specifying it as module parameter. + type: bool + state: + description: + - State of the logging configuration. + default: present + choices: ['present', 'absent'] + type: str + check_running_config: + description: + - Check running configuration. This can be set as environment variable. + Module will use environment variable value(default:True), unless it is overriden, by specifying it as module parameter. + type: bool + default: yes +""" + +EXAMPLES = """ +- name: Configure host logging. + icx_logging: + dest: host + name: 172.16.0.1 + udp_port: 5555 +- name: Remove host logging configuration. + icx_logging: + dest: host + name: 172.16.0.1 + udp_port: 5555 + state: absent +- name: Disables the real-time display of syslog messages. + icx_logging: + dest: console + state: absent +- name: Enables local syslog logging. + icx_logging: + dest : on + state: present +- name: configure buffer level. + icx_logging: + dest: buffered + level: critical +- name: Configure logging using aggregate + icx_logging: + aggregate: + - { dest: buffered, level: ['notifications','errors'] } +- name: remove logging using aggregate + icx_logging: + aggregate: + - { dest: console } + - { dest: host, name: 172.16.0.1, udp_port: 5555 } + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - logging host 172.16.0.1 + - logging console +""" + +import re +from copy import deepcopy +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible.module_utils.network.common.utils import remove_default_spec, validate_ip_v6_address +from ansible.module_utils.network.icx.icx import get_config, load_config + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + +def diff_in_list(want, have): + adds = set() + removes = set() + for w in want: + if w['dest'] == 'buffered': + for h in have: + if h['dest'] == 'buffered': + adds = w['level'] - h['level'] + removes = h['level'] - w['level'] + return adds, removes + return adds, removes + + +def map_obj_to_commands(updates): + dest_group = ('host', 'console', 'persistence', 'enable') + commands = list() + want, have = updates + + for w in want: + dest = w['dest'] + name = w['name'] + level = w['level'] + state = w['state'] + udp_port = w['udp_port'] + facility = w['facility'] + del w['state'] + del w['facility'] + + facility_name = '' + facility_level = '' + if name is not None and validate_ip_v6_address(name): + name = 'ipv6 ' + name + + if facility: + for item in have: + if item['dest'] == 'facility': + facility_name = item['dest'] + facility_level = item['facility'] + + if state == 'absent': + if have == []: + if facility: + commands.append('no logging facility') + + if dest == 'buffered': + for item in have: + if item['dest'] == 'buffered': + want_level = level + have_level = item['level'] + for item in want_level: + commands.append('no logging buffered {0}'.format(item)) + + if dest == 'host': + if name and udp_port: + commands.append('no logging host {0} udp-port {1}'.format(name, udp_port)) + elif name: + commands.append('no logging host {0}'.format(name)) + else: + if dest == 'rfc5424': + commands.append('no logging enable {0}'.format(dest)) + else: + if dest != 'buffered': + commands.append('no logging {0}'.format(dest)) + + if facility: + if facility_name == 'facility' and facility_level != 'user': + commands.append('no logging facility') + + if dest == 'buffered': + for item in have: + if item['dest'] == 'buffered': + want_level = level + have_level = item['level'] + for item in want_level: + if item in have_level: + commands.append('no logging buffered {0}'.format(item)) + + if w in have: + if dest == 'host': + if name and udp_port: + commands.append('no logging host {0} udp-port {1}'.format(name, udp_port)) + elif name: + commands.append('no logging host {0}'.format(name)) + else: + if dest == 'rfc5424': + commands.append('no logging enable {0}'.format(dest)) + else: + if dest != 'buffered': + commands.append('no logging {0}'.format(dest)) + + if state == 'present': + if facility: + if facility != facility_level: + commands.append('logging facility {0}'.format(facility)) + if w not in have: + if dest == 'host': + if name and udp_port: + commands.append('logging host {0} udp-port {1}'.format(name, udp_port)) + elif name: + commands.append('logging host {0}'.format(name)) + elif dest == 'buffered': + adds, removes = diff_in_list(want, have) + for item in adds: + commands.append('logging buffered {0}'.format(item)) + for item in removes: + commands.append('no logging buffered {0}'.format(item)) + elif dest == 'rfc5424': + commands.append('logging enable {0}'.format(dest)) + else: + commands.append('logging {0}'.format(dest)) + + return commands + + +def parse_port(line, dest): + port = None + if dest == 'host': + match = re.search(r'logging host \S+\s+udp-port\s+(\d+)', line, re.M) + if match: + port = match.group(1) + else: + match_port = re.search(r'logging host ipv6 \S+\s+udp-port\s+(\d+)', line, re.M) + if match_port: + port = match_port.group(1) + return port + + +def parse_name(line, dest): + name = None + if dest == 'host': + match = re.search(r'logging host (\S+)', line, re.M) + if match: + if match.group(1) == 'ipv6': + ipv6_add = re.search(r'logging host ipv6 (\S+)', line, re.M) + name = ipv6_add.group(1) + else: + name = match.group(1) + + return name + + +def parse_address(line, dest): + if dest == 'host': + match = re.search(r'^logging host ipv6 (\S+)', line.strip(), re.M) + if match: + return True + return False + + +def map_config_to_obj(module): + obj = [] + facility = '' + addr6 = False + dest_group = ('host', 'console', 'buffered', 'persistence', 'enable') + dest_level = ('alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', 'notifications', 'warnings') + buff_level = list() + if module.params['check_running_config'] is False: + return [] + data = get_config(module, flags=['| include logging']) + facility_match = re.search(r'^logging facility (\S+)', data, re.M) + if facility_match: + facility = facility_match.group(1) + obj.append({ + 'dest': 'facility', + 'facility': facility + }) + else: + obj.append({ + 'dest': 'facility', + 'facility': 'user' + }) + for line in data.split('\n'): + match = re.search(r'^logging (\S+)', line.strip(), re.M) + if match: + + if match.group(1) in dest_group: + dest = match.group(1) + if dest == 'host': + obj.append({ + 'dest': dest, + 'name': parse_name(line.strip(), dest), + 'udp_port': parse_port(line, dest), + 'level': None, + 'addr6': parse_address(line, dest) + + }) + elif dest == 'buffered': + obj.append({ + 'dest': dest, + 'level': None, + 'name': None, + 'udp_port': None, + 'addr6': False + }) + else: + if dest == 'enable': + dest = 'rfc5424' + obj.append({ + 'dest': dest, + 'level': None, + 'name': None, + 'udp_port': None, + 'addr6': False + }) + else: + + ip_match = re.search(r'^no logging buffered (\S+)', line, re.M) + if ip_match: + dest = 'buffered' + buff_level.append(ip_match.group(1)) + if 'no logging on' not in data: + obj.append({ + 'dest': 'on', + 'level': None, + 'name': None, + 'udp_port': None, + 'addr6': False + + }) + levels = set() + for items in dest_level: + if items not in buff_level: + levels.add(items) + obj.append({ + 'dest': 'buffered', + 'level': levels, + 'name': None, + 'udp_port': None, + 'addr6': False + + }) + return obj + + +def count_terms(check, param=None): + count = 0 + for term in check: + if param[term] is not None: + count += 1 + return count + + +def check_required_if(module, spec, param): + for sp in spec: + missing = [] + max_missing_count = 0 + is_one_of = False + if len(sp) == 4: + key, val, requirements, is_one_of = sp + else: + key, val, requirements = sp + + if is_one_of: + max_missing_count = len(requirements) + term = 'any' + else: + term = 'all' + + if key in param and param[key] == val: + for check in requirements: + count = count_terms((check,), param) + if count == 0: + missing.append(check) + if len(missing) and len(missing) >= max_missing_count: + msg = "%s is %s but %s of the following are missing: %s" % (key, val, term, ', '.join(missing)) + module.fail_json(msg=msg) + + +def map_params_to_obj(module, required_if=None): + obj = [] + addr6 = False + aggregate = module.params.get('aggregate') + + if aggregate: + for item in aggregate: + if item['name'] is not None and validate_ip_v6_address(item['name']): + addr6 = True + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + check_required_if(module, required_if, item) + item.update({'addr6': addr6}) + + d = item.copy() + d['level'] = set(d['level']) if d['level'] is not None else None + if d['dest'] != 'host': + d['name'] = None + d['udp_port'] = None + + if d['dest'] != 'buffered': + d['level'] = None + del d['check_running_config'] + obj.append(d) + + else: + if module.params['name'] is not None and validate_ip_v6_address(module.params['name']): + addr6 = True + if module.params['dest'] != 'host': + module.params['name'] = None + module.params['udp_port'] = None + + if module.params['dest'] != 'buffered': + module.params['level'] = None + + obj.append({ + 'dest': module.params['dest'], + 'name': module.params['name'], + 'udp_port': module.params['udp_port'], + 'level': set(module.params['level']) if module.params['level'] else None, + 'facility': module.params['facility'], + 'state': module.params['state'], + 'addr6': addr6 + }) + return obj + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + dest=dict( + type='str', + choices=[ + 'on', + 'host', + 'console', + 'buffered', + 'persistence', + 'rfc5424']), + name=dict( + type='str'), + udp_port=dict(), + level=dict( + type='list', + choices=[ + 'alerts', + 'critical', + 'debugging', + 'emergencies', + 'errors', + 'informational', + 'notifications', + 'warnings']), + facility=dict( + typr='str', + choices=[ + 'auth', + 'cron', + 'daemon', + 'kern', + 'local0', + 'local1', + 'local2', + 'local3', + 'local4', + 'local5', + 'local6', + 'local7', + 'user', + 'lpr', + 'mail', + 'news', + 'syslog', + 'sys9', + 'sys10', + 'sys11', + 'sys12', + 'sys13', + 'sys14', + 'user', + 'uucp']), + state=dict( + default='present', + choices=[ + 'present', + 'absent']), + check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))) + + aggregate_spec = deepcopy(element_spec) + + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + ) + + argument_spec.update(element_spec) + required_if = [('dest', 'host', ['name']), + ('dest', 'buffered', ['level'])] + module = AnsibleModule(argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + warnings = list() + + exec_command(module, 'skip') + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module, required_if=required_if) + have = map_config_to_obj(module) + result['want'] = want + result['have'] = have + + commands = map_obj_to_commands((want, have)) + result['commands'] = commands + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/network/icx/fixtures/icx_logging_config.cfg b/test/units/modules/network/icx/fixtures/icx_logging_config.cfg new file mode 100644 index 00000000000..da943263532 --- /dev/null +++ b/test/units/modules/network/icx/fixtures/icx_logging_config.cfg @@ -0,0 +1,17 @@ +logging host 172.16.10.21 +logging host 172.16.10.21 udp-port 2000 +logging host 172.16.10.22 +logging host 172.16.10.23 udp-port 2500 +logging host 172.16.10.55 udp-port 2500 +logging facility local1 +logging host ipv6 2001:db8::1 udp-port 5500 +logging buffered 200 +no logging buffered critical +no logging buffered debugging +no logging buffered emergencies +no logging buffered errors +no logging buffered informational +no logging buffered notifications +logging enable rfc5424 +logging console +logging persistence \ No newline at end of file diff --git a/test/units/modules/network/icx/test_icx_logging.py b/test/units/modules/network/icx/test_icx_logging.py new file mode 100644 index 00000000000..f04c92c45ef --- /dev/null +++ b/test/units/modules/network/icx/test_icx_logging.py @@ -0,0 +1,149 @@ +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from units.compat.mock import patch +from ansible.modules.network.icx import icx_logging +from units.modules.utils import set_module_args +from .icx_module import TestICXModule, load_fixture + + +class TestICXLoggingModule(TestICXModule): + + module = icx_logging + + def setUp(self): + super(TestICXLoggingModule, self).setUp() + + self.mock_get_config = patch('ansible.modules.network.icx.icx_logging.get_config') + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch('ansible.modules.network.icx.icx_logging.load_config') + self.load_config = self.mock_load_config.start() + + self.mock_exec_command = patch('ansible.modules.network.icx.icx_logging.exec_command') + self.exec_command = self.mock_exec_command.start() + + self.set_running_config() + + def tearDown(self): + super(TestICXLoggingModule, self).tearDown() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_exec_command.stop() + + def load_fixtures(self, commands=None): + compares = None + + def load_file(*args, **kwargs): + module = args + for arg in args: + if arg.params['check_running_config'] is True: + return load_fixture('icx_logging_config.cfg').strip() + else: + return '' + + self.get_config.side_effect = load_file + self.load_config.return_value = None + + def test_icx_logging_set_host(self): + set_module_args(dict(dest='host', name='172.16.10.15')) + if not self.ENV_ICX_USE_DIFF: + commands = ['logging host 172.16.10.15'] + self.execute_module(changed=True, commands=commands) + else: + commands = ['logging host 172.16.10.15'] + self.execute_module(changed=True, commands=commands) + + def test_icx_logging_set_ipv6_host(self): + set_module_args(dict(dest='host', name='2001:db8::1')) + if not self.ENV_ICX_USE_DIFF: + commands = ['logging host 2001:db8::1'] + else: + commands = ['logging host 2001:db8::1'] + + def test_icx_logging_set_host_udp_port(self): + set_module_args(dict(dest='host', name='172.16.10.15', udp_port=2500)) + if not self.ENV_ICX_USE_DIFF: + commands = ['logging host 172.16.10.15 udp-port 2500'] + self.execute_module(changed=True, commands=commands) + else: + commands = ['logging host 172.16.10.15 udp-port 2500'] + self.execute_module(changed=True, commands=commands) + + def test_icx_logging_remove_console(self): + set_module_args(dict(dest='console', state='absent')) + if not self.ENV_ICX_USE_DIFF: + commands = ['no logging console'] + self.execute_module(changed=True, commands=commands) + else: + commands = ['no logging console'] + self.execute_module(changed=True, commands=commands) + + def test_icx_logging_remove_on(self): + set_module_args(dict(dest='on', state='absent')) + if not self.ENV_ICX_USE_DIFF: + commands = ['no logging on'] + self.exec_command(changed=True, commands=commands) + else: + commands = ['no logging on'] + self.exec_command(changed=True, commands=commands) + + def test_icx_logging_set_aggregate(self): + aggregate = [ + dict(dest='host', name='172.16.10.16', udp_port=2500, facility='local0'), + dict(dest='host', name='2001:db8::1', udp_port=5000) + ] + set_module_args(dict(aggregate=aggregate, state='present')) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'logging facility local0', + 'logging host 172.16.10.16 udp-port 2500', + 'logging host ipv6 2001:db8::1 udp-port 5000' + ] + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'logging facility local0', + 'logging host 172.16.10.16 udp-port 2500', + 'logging host ipv6 2001:db8::1 udp-port 5000' + ] + self.assertEqual(result['commands'], expected_commands) + + def test_icx_logging_set_aggregate_remove(self): + aggregate = [ + dict(dest='host', name='172.16.10.55', udp_port=2500, facility='local0'), + dict(dest='host', name='2001:db8::1', udp_port=5500) + ] + set_module_args(dict(aggregate=aggregate, state='absent')) + if not self.ENV_ICX_USE_DIFF: + result = self.execute_module(changed=True) + expected_commands = [ + 'no logging facility', + 'no logging host 172.16.10.55 udp-port 2500', + 'no logging host ipv6 2001:db8::1 udp-port 5500' + ] + + self.assertEqual(result['commands'], expected_commands) + else: + result = self.execute_module(changed=True) + expected_commands = [ + 'no logging facility', + 'no logging host 172.16.10.55 udp-port 2500', + 'no logging host ipv6 2001:db8::1 udp-port 5500' + ] + + self.assertEqual(result['commands'], expected_commands) + + def test_icx_logging_compare(self): + set_module_args(dict(dest='host', name='172.16.10.21', check_running_config=True)) + if self.get_running_config(compare=True): + if not self.ENV_ICX_USE_DIFF: + self.execute_module(changed=False) + else: + self.execute_module(changed=False)