From 5a7bce1f8dfd581228c9ee4defd79b9936b8833d Mon Sep 17 00:00:00 2001 From: anasbadaha <43231942+anasbadaha@users.noreply.github.com> Date: Fri, 17 May 2019 21:20:56 +0300 Subject: [PATCH] Adding New Model onyx_qos for Configuring QoS on Onyx Switches (#55127) * Adding New Model onyx_qos for Configuring QoS on Onyx Switches Signed-off-by: Anas Badaha * Fix Pep8 Failures in onyx_qos Signed-off-by: Anas Badaha * Fix Pep8 Failures phase 2 Signed-off-by: Anas Badaha * Fix Samer's Comments on onyx_qos Module Signed-off-by: Anas Badaha * Fix Shippable Comments Phase 3 Signed-off-by: Anas Badaha * Fix Current Version 2.9 Signed-off-by: Anas Badaha --- lib/ansible/modules/network/onyx/onyx_qos.py | 236 ++++++++++++++++++ .../fixtures/show_qos_interface_ethernet.cfg | 134 ++++++++++ .../modules/network/onyx/test_onyx_qos.py | 52 ++++ 3 files changed, 422 insertions(+) create mode 100644 lib/ansible/modules/network/onyx/onyx_qos.py create mode 100644 test/units/modules/network/onyx/fixtures/show_qos_interface_ethernet.cfg create mode 100644 test/units/modules/network/onyx/test_onyx_qos.py diff --git a/lib/ansible/modules/network/onyx/onyx_qos.py b/lib/ansible/modules/network/onyx/onyx_qos.py new file mode 100644 index 0000000000..1812e5c5d7 --- /dev/null +++ b/lib/ansible/modules/network/onyx/onyx_qos.py @@ -0,0 +1,236 @@ +#!/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_qos +version_added: "2.9" +author: "Anas Badaha (@anasb)" +short_description: Configures QoS +description: + - This module provides declarative management of Onyx QoS configuration + on Mellanox ONYX network devices. +notes: + - Tested on ONYX 3.6.8130 +options: + interfaces: + description: + - list of interfaces name. + required: true + trust: + description: + - trust type. + choices: ['L2', 'L3', 'both'] + default: L2 + rewrite_pcp: + description: + - rewrite with type pcp. + choices: ['enabled', 'disabled'] + default: disabled + rewrite_dscp: + description: + - rewrite with type dscp. + choices: ['enabled', 'disabled'] + default: disabled +""" + +EXAMPLES = """ +- name: configure QoS + onyx_QoS: + interfaces: + - Mpo7 + - Mpo7 + trust: L3 + rewrite_pcp: disabled + rewrite_dscp: enabled + +- name: configure QoS + onyx_QoS: + interfaces: + - Eth1/1 + - Eth1/2 + trust: both + rewrite_pcp: disabled + rewrite_dscp: enabled +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device. + returned: always + type: list + sample: + - interface ethernet 1/16 qos trust L3 + - interface mlag-port-channel 7 qos trust L3 + - interface port-channel 1 qos trust L3 + - interface mlag-port-channel 7 qos trust L2 + - interface mlag-port-channel 7 qos rewrite dscp + - interface ethernet 1/16 qos rewrite pcp + - interface ethernet 1/1 no qos rewrite pcp +""" + +import re +from ansible.module_utils.six import iteritems +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.onyx.onyx import show_cmd +from ansible.module_utils.network.onyx.onyx import BaseOnyxModule + + +class OnyxQosModule(BaseOnyxModule): + TRUST_CMD = "interface {0} {1} qos trust {2}" + NO_REWRITE_PCP_CMD = "interface {0} {1} no qos rewrite pcp" + NO_REWRITE_DSCP_CMD = "interface {0} {1} no qos rewrite dscp" + REWRITE_PCP_CMD = "interface {0} {1} qos rewrite pcp" + REWRITE_DSCP_CMD = "interface {0} {1} qos rewrite dscp" + + REWRITE_PCP = "pcp" + REWRITE_DSCP = "dscp" + + IF_ETH_REGEX = re.compile(r"^Eth(\d+\/\d+|Eth\d+\/\d+\d+)$") + IF_PO_REGEX = re.compile(r"^Po(\d+)$") + MLAG_NAME_REGEX = re.compile(r"^Mpo(\d+)$") + + IF_TYPE_ETH = "ethernet" + PORT_CHANNEL = "port-channel" + MLAG_PORT_CHANNEL = "mlag-port-channel" + + IF_TYPE_MAP = { + IF_TYPE_ETH: IF_ETH_REGEX, + PORT_CHANNEL: IF_PO_REGEX, + MLAG_PORT_CHANNEL: MLAG_NAME_REGEX + } + + def init_module(self): + """ initialize module + """ + element_spec = dict( + interfaces=dict(type='list', required=True), + trust=dict(choices=['L2', 'L3', 'both'], default='L2'), + rewrite_pcp=dict(choices=['enabled', 'disabled'], default='disabled'), + rewrite_dscp=dict(choices=['enabled', 'disabled'], default='disabled') + ) + argument_spec = dict() + argument_spec.update(element_spec) + self._module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True) + + def get_required_config(self): + module_params = self._module.params + self._required_config = dict(module_params) + self.validate_param_values(self._required_config) + + def _get_interface_type(self, if_name): + if_type = None + if_id = None + for interface_type, interface_regex in iteritems(self.IF_TYPE_MAP): + match = interface_regex.match(if_name) + if match: + if_type = interface_type + if_id = match.group(1) + break + return if_type, if_id + + def _set_interface_qos_config(self, interface_qos_config, interface, if_type, if_id): + interface_qos_config = interface_qos_config[0].get(interface) + trust = interface_qos_config[0].get("Trust mode") + rewrite_dscp = interface_qos_config[0].get("DSCP rewrite") + rewrite_pcp = interface_qos_config[0].get("PCP,DEI rewrite") + + self._current_config[interface] = dict(trust=trust, rewrite_dscp=rewrite_dscp, + rewrite_pcp=rewrite_pcp, if_type=if_type, if_id=if_id) + + def _show_interface_qos(self, if_type, interface): + cmd = "show qos interface {0} {1}".format(if_type, interface) + return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False) + + def load_current_config(self): + self._current_config = dict() + for interface in self._required_config.get("interfaces"): + if_type, if_id = self._get_interface_type(interface) + if not if_id: + self._module.fail_json( + msg='unsupported interface: {0}'.format(interface)) + interface_qos_config = self._show_interface_qos(if_type, if_id) + if interface_qos_config is not None: + self._set_interface_qos_config(interface_qos_config, interface, if_type, if_id) + else: + self._module.fail_json( + msg='Interface {0} does not exist on switch'.format(interface)) + + def generate_commands(self): + trust = self._required_config.get("trust") + rewrite_pcp = self._required_config.get("rewrite_pcp") + rewrite_dscp = self._required_config.get("rewrite_dscp") + for interface in self._required_config.get("interfaces"): + ignored1, ignored2, current_trust, if_type, if_id = self._get_current_rewrite_config(interface) + self._add_interface_trust_cmds(if_type, if_id, interface, trust, current_trust) + self._add_interface_rewrite_cmds(if_type, if_id, interface, + rewrite_pcp, rewrite_dscp) + + def _get_current_rewrite_config(self, interface): + current_interface_qos_config = self._current_config.get(interface) + current_rewrite_pcp = current_interface_qos_config.get('rewrite_pcp') + current_rewrite_dscp = current_interface_qos_config.get('rewrite_dscp') + if_type = current_interface_qos_config.get("if_type") + if_id = current_interface_qos_config.get("if_id") + current_trust = current_interface_qos_config.get('trust') + + return current_rewrite_pcp, current_rewrite_dscp, current_trust, if_type, if_id + + def _add_interface_trust_cmds(self, if_type, if_id, interface, trust, current_trust): + + current_rewrite_pcp, current_rewrite_dscp, ignored1, ignored2, ignored3 = self._get_current_rewrite_config( + interface) + + if trust == "L3" and trust != current_trust: + self._add_no_rewrite_cmd(if_type, if_id, interface, self.REWRITE_DSCP, current_rewrite_dscp) + self._commands.append(self.TRUST_CMD.format(if_type, if_id, trust)) + elif trust == "L2" and trust != current_trust: + self._add_no_rewrite_cmd(if_type, if_id, interface, self.REWRITE_PCP, current_rewrite_pcp) + self._commands.append(self.TRUST_CMD.format(if_type, if_id, trust)) + elif trust == "both" and trust != current_trust: + self._add_no_rewrite_cmd(if_type, if_id, interface, self.REWRITE_DSCP, current_rewrite_dscp) + self._add_no_rewrite_cmd(if_type, if_id, interface, self.REWRITE_PCP, current_rewrite_pcp) + self._commands.append(self.TRUST_CMD.format(if_type, if_id, trust)) + + def _add_interface_rewrite_cmds(self, if_type, if_id, interface, rewrite_pcp, rewrite_dscp): + current_rewrite_pcp, current_rewrite_dscp, ignored1, ignored2, ignored3 = self._get_current_rewrite_config( + interface) + + if rewrite_pcp == "enabled" and rewrite_pcp != current_rewrite_pcp: + self._commands.append(self.REWRITE_PCP_CMD.format(if_type, if_id)) + elif rewrite_pcp == "disabled" and rewrite_pcp != current_rewrite_pcp: + self._commands.append(self.NO_REWRITE_PCP_CMD.format(if_type, if_id)) + + if rewrite_dscp == "enabled" and rewrite_dscp != current_rewrite_dscp: + self._commands.append(self.REWRITE_DSCP_CMD.format(if_type, if_id)) + elif rewrite_dscp == "disabled" and rewrite_dscp != current_rewrite_dscp: + self._commands.append(self.NO_REWRITE_DSCP_CMD.format(if_type, if_id)) + + def _add_no_rewrite_cmd(self, if_type, if_id, interface, rewrite_type, current_rewrite): + if rewrite_type == self.REWRITE_PCP and current_rewrite == "enabled": + self._commands.append(self.NO_REWRITE_PCP_CMD.format(if_type, if_id)) + self._current_config[interface]["rewrite_pcp"] = "disabled" + elif rewrite_type == self.REWRITE_DSCP and current_rewrite == "enabled": + self._commands.append(self.NO_REWRITE_DSCP_CMD.format(if_type, if_id)) + self._current_config[interface]["rewrite_dscp"] = "disabled" + + +def main(): + """ main entry point for module execution + """ + OnyxQosModule.main() + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/network/onyx/fixtures/show_qos_interface_ethernet.cfg b/test/units/modules/network/onyx/fixtures/show_qos_interface_ethernet.cfg new file mode 100644 index 0000000000..cf2bba8f20 --- /dev/null +++ b/test/units/modules/network/onyx/fixtures/show_qos_interface_ethernet.cfg @@ -0,0 +1,134 @@ +[ + { + "Eth1/1": [ + { + "PCP,DEI rewrite": "enabled", + "Default switch-priority": "0", + "IP PCP;DEI rewrite": "enable", + "Default DEI": "0", + "Default PCP": "0", + "Trust mode": "both", + "DSCP rewrite": "disabled" + }, + { + "PCP(DEI); DSCP to switch-priority mapping": [ + { + "2(0) 2(1)": [ + { + "switch-priority": "2", + "DSCP": "16 17 18 19 20 21 22 23" + } + ], + "3(0) 3(1)": [ + { + "switch-priority": "3", + "DSCP": "24 25 26 27 28 29 30 31" + } + ], + "5(0) 5(1)": [ + { + "switch-priority": "5", + "DSCP": "40 41 42 43 44 45 46 47" + } + ], + "0(0) 0(1)": [ + { + "switch-priority": "0", + "DSCP": "0 1 2 3 4 5 6 7" + } + ], + "7(0) 7(1)": [ + { + "switch-priority": "7", + "DSCP": "56 57 58 59 60 61 62 63" + } + ], + "4(0) 4(1)": [ + { + "switch-priority": "4", + "DSCP": "32 33 34 35 36 37 38 39" + } + ], + "6(0) 6(1)": [ + { + "switch-priority": "6", + "DSCP": "48 49 50 51 52 53 54 55" + } + ], + "1(0) 1(1)": [ + { + "switch-priority": "1", + "DSCP": "8 9 10 11 12 13 14 15" + } + ] + } + ] + }, + { + "PCP(DEI); DSCP rewrite mapping (switch-priority to PCP(DEI); DSCP; traffic-class)": [ + { + "Egress Interface": "Eth1/1" + }, + { + "1": [ + { + "PCP(DEI)": "1(0)", + "TC": "1", + "DSCP": "8" + } + ], + "0": [ + { + "PCP(DEI)": "0(0)", + "TC": "0", + "DSCP": "0" + } + ], + "3": [ + { + "PCP(DEI)": "3(0)", + "TC": "3", + "DSCP": "24" + } + ], + "2": [ + { + "PCP(DEI)": "2(0)", + "TC": "2", + "DSCP": "16" + } + ], + "5": [ + { + "PCP(DEI)": "5(0)", + "TC": "5", + "DSCP": "40" + } + ], + "4": [ + { + "PCP(DEI)": "4(0)", + "TC": "4", + "DSCP": "32" + } + ], + "7": [ + { + "PCP(DEI)": "7(0)", + "TC": "7", + "DSCP": "56" + } + ], + "6": [ + { + "PCP(DEI)": "6(0)", + "TC": "6", + "DSCP": "48" + } + ] + } + ] + } + ] + } +] diff --git a/test/units/modules/network/onyx/test_onyx_qos.py b/test/units/modules/network/onyx/test_onyx_qos.py new file mode 100644 index 0000000000..7d578cec49 --- /dev/null +++ b/test/units/modules/network/onyx/test_onyx_qos.py @@ -0,0 +1,52 @@ +# +# 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 units.compat.mock import patch +from ansible.modules.network.onyx import onyx_qos +from units.modules.utils import set_module_args +from .onyx_module import TestOnyxModule, load_fixture + + +class TestOnyxQosModule(TestOnyxModule): + + module = onyx_qos + + def setUp(self): + super(TestOnyxQosModule, self).setUp() + self.mock_get_if_qos_config = patch.object( + onyx_qos.OnyxQosModule, "_show_interface_qos") + self.get_if_qos_config = self.mock_get_if_qos_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(TestOnyxQosModule, self).tearDown() + self.mock_get_if_qos_config.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None, transport='cli'): + qos_interface_ethernet_config_file = 'show_qos_interface_ethernet.cfg' + qos_interface_ethernet_data = load_fixture(qos_interface_ethernet_config_file) + self.get_if_qos_config.return_value = qos_interface_ethernet_data + self.load_config.return_value = None + + def test_qos_interface_ethernet_no_change(self): + set_module_args(dict(interfaces=["Eth1/1"], trust="both", rewrite_pcp="enabled", + rewrite_dscp="disabled")) + self.execute_module(changed=False) + + def test_qos_interface_ethernet_with_change(self): + set_module_args(dict(interfaces=["Eth1/1"], trust="L2", rewrite_pcp="disabled", + rewrite_dscp="enabled")) + commands = ["interface ethernet 1/1 no qos rewrite pcp", + "interface ethernet 1/1 qos trust L2", + "interface ethernet 1/1 qos rewrite dscp" + ] + self.execute_module(changed=True, commands=commands)