#!/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_traffic_class author: "Anas Badaha (@anasb)" short_description: Configures Traffic Class description: - This module provides declarative management of Traffic Class configuration on Mellanox ONYX network devices. options: state: description: - enable congestion control on interface. choices: ['enabled', 'disabled'] default: enabled interfaces: description: - list of interfaces name. required: true tc: description: - traffic class, range 0-7. required: true congestion_control: description: - configure congestion control on interface. suboptions: control: description: - congestion control type. choices: ['red', 'ecn', 'both'] required: true threshold_mode: description: - congestion control threshold mode. choices: ['absolute', 'relative'] required: true min_threshold: description: - Set minimum-threshold value (in KBs) for marking traffic-class queue. required: true max_threshold: description: - Set maximum-threshold value (in KBs) for marking traffic-class queue. required: true dcb: description: - configure dcb control on interface. suboptions: mode: description: - dcb control mode. choices: ['strict', 'wrr'] required: true weight: description: - Relevant only for wrr mode. ''' EXAMPLES = """ - name: configure traffic class onyx_traffic_class: interfaces: - Eth1/1 - Eth1/2 tc: 3 congestion_control: control: ecn threshold_mode: absolute min_threshold: 500 max_threshold: 1500 dcb: mode: strict """ RETURN = """ commands: description: The list of configuration mode commands to send to the device. returned: always type: list sample: - interface ethernet 1/15 traffic-class 3 congestion-control ecn minimum-absolute 150 maximum-absolute 1500 - interface ethernet 1/16 traffic-class 3 congestion-control ecn minimum-absolute 150 maximum-absolute 1500 - interface mlag-port-channel 7 traffic-class 3 congestion-control ecn minimum-absolute 150 maximum-absolute 1500 - interface port-channel 1 traffic-class 3 congestion-control ecn minimum-absolute 150 maximum-absolute 1500 - interface ethernet 1/15 traffic-class 3 dcb ets strict - interface ethernet 1/16 traffic-class 3 dcb ets strict - interface mlag-port-channel 7 traffic-class 3 dcb ets strict - interface port-channel 1 traffic-class 3 dcb ets strict """ import re from ansible.module_utils.six import iteritems from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.general.plugins.module_utils.network.onyx.onyx import show_cmd from ansible_collections.community.general.plugins.module_utils.network.onyx.onyx import BaseOnyxModule class OnyxTrafficClassModule(BaseOnyxModule): 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 """ congestion_control_spec = dict(control=dict(choices=['red', 'ecn', 'both'], required=True), threshold_mode=dict(choices=['absolute', 'relative'], required=True), min_threshold=dict(type=int, required=True), max_threshold=dict(type=int, required=True)) dcb_spec = dict(mode=dict(choices=['strict', 'wrr'], required=True), weight=dict(type=int)) element_spec = dict( interfaces=dict(type='list', required=True), tc=dict(type=int, required=True), congestion_control=dict(type='dict', options=congestion_control_spec), dcb=dict(type='dict', options=dcb_spec), state=dict(choices=['enabled', 'disabled'], default='enabled')) 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 validate_tc(self, value): if value and not 0 <= int(value) <= 7: self._module.fail_json(msg='tc value must be between 0 and 7') def validate_param_values(self, obj, param=None): dcb = obj.get("dcb") if dcb is not None: dcb_mode = dcb.get("mode") weight = dcb.get("weight") if dcb_mode == "wrr" and weight is None: self._module.fail_json(msg='User should send weight attribute when dcb mode is wrr') super(OnyxTrafficClassModule, self).validate_param_values(obj, param) 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_congestion_control_config(self, interface_congestion_control_config, interface, if_type, if_id): tc = self._required_config.get("tc") interface_dcb_ets = self._show_interface_dcb_ets(if_type, if_id)[0].get(interface) if interface_dcb_ets is None: dcb = dict() else: ets_per_tc = interface_dcb_ets[2].get("ETS per TC") tc_config = ets_per_tc[0].get(str(tc)) dcb_mode = tc_config[0].get("S.Mode") dcb_weight = int(tc_config[0].get("W")) dcb = dict(mode=dcb_mode.lower(), weight=dcb_weight) interface_congestion_control_config = interface_congestion_control_config[tc + 1] mode = interface_congestion_control_config.get("Mode") if mode == "none": self._current_config[interface] = dict(state="disabled", dcb=dcb, if_type=if_type, if_id=if_id) return threshold_mode = interface_congestion_control_config.get("Threshold mode") max_threshold = interface_congestion_control_config.get("Maximum threshold") min_threshold = interface_congestion_control_config.get("Minimum threshold") if threshold_mode == "absolute": delimiter = ' ' else: delimiter = '%' min_value = int(min_threshold.split(delimiter)[0]) max_malue = int(max_threshold.split(delimiter)[0]) congestion_control = dict(control=mode.lower(), threshold_mode=threshold_mode, min_threshold=min_value, max_threshold=max_malue) self._current_config[interface] = dict(state="enabled", congestion_control=congestion_control, dcb=dcb, if_type=if_type, if_id=if_id) def _show_interface_congestion_control(self, if_type, interface): cmd = "show interfaces {0} {1} congestion-control".format(if_type, interface) return show_cmd(self._module, cmd, json_fmt=True, fail_on_error=False) def _show_interface_dcb_ets(self, if_type, interface): cmd = "show dcb ets 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_congestion_control_config = self._show_interface_congestion_control(if_type, if_id) if interface_congestion_control_config is not None: self._set_interface_congestion_control_config(interface_congestion_control_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): state = self._required_config.get("state") tc = self._required_config.get("tc") interfaces = self._required_config.get("interfaces") for interface in interfaces: current_interface = self._current_config.get(interface) current_state = current_interface.get("state") if_type = current_interface.get("if_type") if_id = current_interface.get("if_id") if state == "disabled": if current_state == "enabled": self._commands.append('interface {0} {1} no traffic-class {2} congestion-control'.format(if_type, if_id, tc)) continue congestion_control = self._required_config.get("congestion_control") if congestion_control is not None: control = congestion_control.get("control") current_congestion_control = current_interface.get("congestion_control") threshold_mode = congestion_control.get("threshold_mode") min_threshold = congestion_control.get("min_threshold") max_threshold = congestion_control.get("max_threshold") if current_congestion_control is None: self._threshold_mode_generate_cmds_mappers(threshold_mode, if_type, if_id, tc, control, min_threshold, max_threshold) else: current_control = current_congestion_control.get("control") curr_threshold_mode = current_congestion_control.get("threshold_mode") curr_min_threshold = current_congestion_control.get("min_threshold") curr_max_threshold = current_congestion_control.get("max_threshold") if control != current_control: self._threshold_mode_generate_cmds_mappers(threshold_mode, if_type, if_id, tc, control, min_threshold, max_threshold) else: if threshold_mode != curr_threshold_mode: self._threshold_mode_generate_cmds_mappers(threshold_mode, if_type, if_id, tc, control, min_threshold, max_threshold) elif min_threshold != curr_min_threshold or max_threshold != curr_max_threshold: self._threshold_mode_generate_cmds_mappers(threshold_mode, if_type, if_id, tc, control, min_threshold, max_threshold) dcb = self._required_config.get("dcb") if dcb is not None: dcb_mode = dcb.get("mode") current_dcb = current_interface.get("dcb") current_dcb_mode = current_dcb.get("mode") if dcb_mode == "strict" and dcb_mode != current_dcb_mode: self._commands.append('interface {0} {1} traffic-class {2} ' 'dcb ets {3}'.format(if_type, if_id, tc, dcb_mode)) elif dcb_mode == "wrr": weight = dcb.get("weight") current_weight = current_dcb.get("weight") if dcb_mode != current_dcb_mode or weight != current_weight: self._commands.append('interface {0} {1} traffic-class {2} ' 'dcb ets {3} {4}'.format(if_type, if_id, tc, dcb_mode, weight)) def _threshold_mode_generate_cmds_mappers(self, threshold_mode, if_type, if_id, tc, control, min_threshold, max_threshold): if threshold_mode == 'absolute': self._generate_congestion_control_absolute_cmds(if_type, if_id, tc, control, min_threshold, max_threshold) else: self._generate_congestion_control_relative_cmds(if_type, if_id, tc, control, min_threshold, max_threshold) def _generate_congestion_control_absolute_cmds(self, if_type, if_id, tc, control, min_absolute, max_absolute): self._commands.append('interface {0} {1} traffic-class {2} ' 'congestion-control {3} minimum-absolute {4} ' 'maximum-absolute {5}'.format(if_type, if_id, tc, control, min_absolute, max_absolute)) def _generate_congestion_control_relative_cmds(self, if_type, if_id, tc, control, min_relative, max_relative): self._commands.append('interface {0} {1} traffic-class {2} ' 'congestion-control {3} minimum-relative {4} ' 'maximum-relative {5}'.format(if_type, if_id, tc, control, min_relative, max_relative)) def main(): """ main entry point for module execution """ OnyxTrafficClassModule.main() if __name__ == '__main__': main()