diff --git a/lib/ansible/module_utils/network/onyx/onyx.py b/lib/ansible/module_utils/network/onyx/onyx.py index d439d9f57e7..ad667e74dfd 100644 --- a/lib/ansible/module_utils/network/onyx/onyx.py +++ b/lib/ansible/module_utils/network/onyx/onyx.py @@ -119,7 +119,6 @@ def show_cmd(module, cmd, json_fmt=True, fail_on_error=True): if fail_on_error: raise return None - if json_fmt: out = _parse_json_output(out) try: @@ -177,15 +176,18 @@ class BaseOnyxModule(object): def check_declarative_intent_params(self, result): return None + def _validate_key(self, param, key): + validator = getattr(self, 'validate_%s' % key) + if callable(validator): + validator(param.get(key)) + def validate_param_values(self, obj, param=None): if param is None: param = self._module.params for key in obj: # validate the param value (if validator func exists) try: - validator = getattr(self, 'validate_%s' % key) - if callable(validator): - validator(param.get(key)) + self._validate_key(param, key) except AttributeError: pass @@ -202,9 +204,16 @@ class BaseOnyxModule(object): except ValueError: return None + def _validate_range(self, attr_name, min_val, max_val, value): + if value is None: + return True + if not min_val <= int(value) <= max_val: + msg = '%s must be between %s and %s' % ( + attr_name, min_val, max_val) + self._module.fail_json(msg=msg) + def validate_mtu(self, value): - if value and not 1500 <= int(value) <= 9612: - self._module.fail_json(msg='mtu must be between 1500 and 9612') + self._validate_range('mtu', 1500, 9612, value) def generate_commands(self): pass diff --git a/lib/ansible/modules/network/onyx/onyx_igmp.py b/lib/ansible/modules/network/onyx/onyx_igmp.py new file mode 100644 index 00000000000..ad6e15134c2 --- /dev/null +++ b/lib/ansible/modules/network/onyx/onyx_igmp.py @@ -0,0 +1,225 @@ +#!/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: onyx_igmp +version_added: "2.7" +author: "Samer Deeb (@samerd)" +short_description: Configures IGMP globl parameters +description: + - This module provides declarative management of IGMP protocol params + on Mellanox ONYX network devices. +notes: + - Tested on ONYX 3.6.6107 +options: + state: + description: + - IGMP state. + required: true + choices: ['enabled', 'disabled'] + last_member_query_interval: + description: + - Configure the last member query interval, range 1-25 + mrouter_timeout: + description: + - Configure the mrouter timeout, range 60-600 + port_purge_timeout: + description: + - Configure the host port purge timeout, range 130-1225 + proxy_reporting: + description: + - Configure ip igmp snooping proxy and enable reporting mode + choices: ['enabled', 'disabled'] + report_suppression_interval: + description: + - Configure the report suppression interval, range 1-25 + unregistered_multicast: + description: + - Configure the unregistered multicast mode + Flood unregistered multicast + Forward unregistered multicast to mrouter ports + choices: ['flood', 'forward-to-mrouter-ports'] + default_version: + description: + - Configure the default operating version of the IGMP snooping + choices: ['V2','V3'] +""" + +EXAMPLES = """ +- name: configure igmp + onyx_igmp: + state: enabled + unregistered_multicast: flood +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device. + returned: always + type: list + sample: + - ip igmp snooping + - ip igmp snooping last-member-query-interval 10 + - ip igmp snooping mrouter-timeout 150 + - ip igmp snooping port-purge-timeout 150 +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + +from ansible.module_utils.network.onyx.onyx import show_cmd +from ansible.module_utils.network.onyx.onyx import BaseOnyxModule + + +class OnyxIgmpModule(BaseOnyxModule): + TIME_INTERVAL_REGEX = re.compile(r'^(\d+)\s+seconds') + + _RANGE_INTERVALS = dict( + last_member_query_interval=(1, 25, 'Last member query interval'), + mrouter_timeout=(60, 600, 'Mrouter timeout'), + port_purge_timeout=(130, 1225, 'Port purge timeout'), + report_suppression_interval=(1, 25, 'Report suppression interval'), + ) + + def init_module(self): + """ initialize module + """ + element_spec = dict( + state=dict(choices=['enabled', 'disabled'], required=True), + last_member_query_interval=dict(type='int'), + mrouter_timeout=dict(type='int'), + port_purge_timeout=dict(type='int'), + proxy_reporting=dict(choices=['enabled', 'disabled']), + report_suppression_interval=dict(type='int'), + unregistered_multicast=dict( + choices=['flood', 'forward-to-mrouter-ports']), + default_version=dict(choices=['V2', 'V3']), + ) + argument_spec = dict() + argument_spec.update(element_spec) + self._module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True) + + def _validate_key(self, param, key): + interval_params = self._RANGE_VALIDATORS.get(key) + if interval_params: + min_val, max_val = interval_params[0], interval_params[1] + value = param.get(key) + self._validate_range(key, min_val, max_val, value) + else: + super(OnyxIgmpModule, self)._validate_key(param, key) + + def get_required_config(self): + module_params = self._module.params + self._required_config = dict(module_params) + self.validate_param_values(self._required_config) + + def _set_igmp_config(self, igmp_config): + igmp_config = igmp_config[0] + if not igmp_config: + return + self._current_config['state'] = igmp_config.get( + 'IGMP snooping globally', 'disabled') + self._current_config['proxy_reporting'] = igmp_config.get( + 'Proxy-reporting globally', 'disabled') + self._current_config['default_version'] = igmp_config.get( + 'IGMP default version for new VLAN', 'V3') + self._current_config['unregistered_multicast'] = igmp_config.get( + 'IGMP snooping unregistered multicast', 'flood') + + for interval_name, interval_params in iteritems(self._RANGE_INTERVALS): + display_str = interval_params[2] + value = igmp_config.get(display_str, '') + match = self.TIME_INTERVAL_REGEX.match(value) + if match: + interval_value = int(match.group(1)) + else: + interval_value = None + self._current_config[interval_name] = interval_value + + def _show_igmp(self): + cmd = "show ip igmp snooping" + return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False) + + def load_current_config(self): + self._current_config = dict() + igmp_config = self._show_igmp() + if igmp_config: + self._set_igmp_config(igmp_config) + + def generate_commands(self): + state = self._required_config['state'] + if state == 'enabled': + self._generate_igmp_cmds() + else: + self._generate_no_igmp_cmds() + + def _generate_igmp_cmds(self): + curr_state = self._current_config.get('state', 'disabled') + if curr_state == 'disabled': + self._commands.append('ip igmp snooping') + for interval_name in self._RANGE_INTERVALS: + req_val = self._required_config.get(interval_name) + if not req_val: + continue + curr_value = self._current_config.get(interval_name) + if curr_value == req_val: + continue + interval_cmd = interval_name.replace('_', '-') + self._commands.append( + 'ip igmp snooping %s %s' % (interval_cmd, req_val)) + + req_val = self._required_config.get('unregistered_multicast') + if req_val: + curr_value = self._current_config.get( + 'unregistered_multicast', 'flood') + if req_val != curr_value: + self._commands.append( + 'ip igmp snooping unregistered multicast %s' % req_val) + + req_val = self._required_config.get('proxy_reporting') + if req_val: + curr_value = self._current_config.get( + 'proxy_reporting', 'disabled') + if req_val != curr_value: + cmd = 'ip igmp snooping proxy reporting' + if req_val == 'disabled': + cmd = 'no %s' % cmd + self._commands.append(cmd) + + req_val = self._required_config.get('default_version') + if req_val: + curr_value = self._current_config.get( + 'default_version', 'V3') + if req_val != curr_value: + version = req_val[1] # remove the 'V' and take the number only + self._commands.append( + 'ip igmp snooping version %s' % version) + + def _generate_no_igmp_cmds(self): + curr_state = self._current_config.get('state', 'disabled') + if curr_state != 'disabled': + self._commands.append('no ip igmp snooping') + + +def main(): + """ main entry point for module execution + """ + OnyxIgmpModule.main() + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/network/onyx/fixtures/onyx_igmp_show.cfg b/test/units/modules/network/onyx/fixtures/onyx_igmp_show.cfg new file mode 100644 index 00000000000..13b19d949ca --- /dev/null +++ b/test/units/modules/network/onyx/fixtures/onyx_igmp_show.cfg @@ -0,0 +1,14 @@ +[ + { + "Report suppression interval": "5 seconds", + "IGMP snooping unregistered multicast": "flood", + "IGMP snooping operationally": "disabled", + "Mrouter timeout": "125 seconds", + "IGMP default version for new VLAN": "V3", + "header": "IGMP snooping global configuration", + "Last member query interval": "1 seconds", + "IGMP snooping globally": "disabled", + "Proxy-reporting globally": "disabled", + "Port purge timeout": "260 seconds" + } +] diff --git a/test/units/modules/network/onyx/test_onyx_igmp.py b/test/units/modules/network/onyx/test_onyx_igmp.py new file mode 100644 index 00000000000..bc86ba8474b --- /dev/null +++ b/test/units/modules/network/onyx/test_onyx_igmp.py @@ -0,0 +1,127 @@ +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.compat.tests.mock import patch +from ansible.modules.network.onyx import onyx_igmp +from units.modules.utils import set_module_args +from .onyx_module import TestOnyxModule, load_fixture + + +class TestOnyxIgmpModule(TestOnyxModule): + + module = onyx_igmp + enabled = False + + def setUp(self): + self.enabled = False + super(TestOnyxIgmpModule, self).setUp() + self.mock_get_config = patch.object( + onyx_igmp.OnyxIgmpModule, "_show_igmp") + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + 'ansible.module_utils.network.onyx.onyx.load_config') + self.load_config = self.mock_load_config.start() + + def tearDown(self): + super(TestOnyxIgmpModule, self).tearDown() + self.mock_get_config.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None, transport='cli'): + config_file = 'onyx_igmp_show.cfg' + data = load_fixture(config_file) + if self.enabled: + data[0]['IGMP snooping globally'] = 'enabled' + self.get_config.return_value = data + self.load_config.return_value = None + + def test_igmp_no_change(self): + set_module_args(dict(state='disabled')) + self.execute_module(changed=False) + + def test_igmp_enable(self): + set_module_args(dict(state='enabled')) + commands = ['ip igmp snooping'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_last_member_query_interval(self): + set_module_args(dict(state='enabled', + last_member_query_interval=10)) + commands = ['ip igmp snooping', + 'ip igmp snooping last-member-query-interval 10'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_mrouter_timeout(self): + set_module_args(dict(state='enabled', + mrouter_timeout=100)) + commands = ['ip igmp snooping', + 'ip igmp snooping mrouter-timeout 100'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_port_purge_timeout(self): + set_module_args(dict(state='enabled', + port_purge_timeout=150)) + commands = ['ip igmp snooping', + 'ip igmp snooping port-purge-timeout 150'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_report_suppression_interval(self): + set_module_args(dict(state='enabled', + report_suppression_interval=10)) + commands = ['ip igmp snooping', + 'ip igmp snooping report-suppression-interval 10'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_proxy_reporting_disabled(self): + set_module_args(dict(state='enabled', + proxy_reporting='disabled')) + commands = ['ip igmp snooping'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_proxy_reporting_enabled(self): + set_module_args(dict(state='enabled', + proxy_reporting='enabled')) + commands = ['ip igmp snooping', + 'ip igmp snooping proxy reporting'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_unregistered_multicast_flood(self): + set_module_args(dict(state='enabled', + unregistered_multicast='flood')) + commands = ['ip igmp snooping'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_unregistered_multicast_forward(self): + set_module_args( + dict(state='enabled', + unregistered_multicast='forward-to-mrouter-ports')) + commands = [ + 'ip igmp snooping', + 'ip igmp snooping unregistered multicast forward-to-mrouter-ports' + ] + self.execute_module(changed=True, commands=commands) + + def test_igmp_version_v2(self): + set_module_args(dict(state='enabled', + default_version='V2')) + commands = ['ip igmp snooping', + 'ip igmp snooping version 2'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_version_v3(self): + set_module_args(dict(state='enabled', + default_version='V3')) + commands = ['ip igmp snooping'] + self.execute_module(changed=True, commands=commands) + + def test_igmp_disable(self): + self.enabled = True + set_module_args(dict(state='disabled')) + commands = ['no ip igmp snooping'] + self.execute_module(changed=True, commands=commands)