From 1f55387f9272919f231b72e98b15dbbd68c83c5f Mon Sep 17 00:00:00 2001 From: Shade Talabi Date: Wed, 4 Dec 2024 15:33:35 -0800 Subject: [PATCH] Add FBS classifiers resource module --- .../network/sonic/argspec/facts/facts.py | 1 + .../sonic/argspec/fbs_classifiers/__init__.py | 0 .../fbs_classifiers/fbs_classifiers.py | 122 ++++ .../sonic/config/fbs_classifiers/__init__.py | 0 .../config/fbs_classifiers/fbs_classifiers.py | 598 ++++++++++++++++ .../module_utils/network/sonic/facts/facts.py | 2 + .../sonic/facts/fbs_classifiers/__init__.py | 0 .../facts/fbs_classifiers/fbs_classifiers.py | 224 ++++++ plugins/modules/sonic_facts.py | 1 + plugins/modules/sonic_fbs_classifiers.py | 484 +++++++++++++ .../sonic_fbs_classifiers/defaults/main.yml | 229 +++++++ .../sonic_fbs_classifiers/meta/main.yaml | 5 + .../sonic_fbs_classifiers/tasks/cleanup.yaml | 11 + .../sonic_fbs_classifiers/tasks/main.yml | 14 + .../tasks/preparation.yaml | 18 + .../tasks/tasks_template.yaml | 21 + tests/regression/test.yaml | 1 + .../sonic/fixtures/sonic_fbs_classifiers.yaml | 636 ++++++++++++++++++ .../sonic/test_sonic_fbs_classifiers.py | 87 +++ 19 files changed, 2454 insertions(+) create mode 100644 plugins/module_utils/network/sonic/argspec/fbs_classifiers/__init__.py create mode 100644 plugins/module_utils/network/sonic/argspec/fbs_classifiers/fbs_classifiers.py create mode 100644 plugins/module_utils/network/sonic/config/fbs_classifiers/__init__.py create mode 100644 plugins/module_utils/network/sonic/config/fbs_classifiers/fbs_classifiers.py create mode 100644 plugins/module_utils/network/sonic/facts/fbs_classifiers/__init__.py create mode 100644 plugins/module_utils/network/sonic/facts/fbs_classifiers/fbs_classifiers.py create mode 100644 plugins/modules/sonic_fbs_classifiers.py create mode 100644 tests/regression/roles/sonic_fbs_classifiers/defaults/main.yml create mode 100644 tests/regression/roles/sonic_fbs_classifiers/meta/main.yaml create mode 100644 tests/regression/roles/sonic_fbs_classifiers/tasks/cleanup.yaml create mode 100644 tests/regression/roles/sonic_fbs_classifiers/tasks/main.yml create mode 100644 tests/regression/roles/sonic_fbs_classifiers/tasks/preparation.yaml create mode 100644 tests/regression/roles/sonic_fbs_classifiers/tasks/tasks_template.yaml create mode 100644 tests/unit/modules/network/sonic/fixtures/sonic_fbs_classifiers.yaml create mode 100644 tests/unit/modules/network/sonic/test_sonic_fbs_classifiers.py diff --git a/plugins/module_utils/network/sonic/argspec/facts/facts.py b/plugins/module_utils/network/sonic/argspec/facts/facts.py index ecb0c3763..871bb7555 100644 --- a/plugins/module_utils/network/sonic/argspec/facts/facts.py +++ b/plugins/module_utils/network/sonic/argspec/facts/facts.py @@ -82,6 +82,7 @@ def __init__(self, **kwargs): 'mgmt_servers', 'ospf_area', 'ssh', + 'fbs_classifiers' ] argument_spec = { diff --git a/plugins/module_utils/network/sonic/argspec/fbs_classifiers/__init__.py b/plugins/module_utils/network/sonic/argspec/fbs_classifiers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/argspec/fbs_classifiers/fbs_classifiers.py b/plugins/module_utils/network/sonic/argspec/fbs_classifiers/fbs_classifiers.py new file mode 100644 index 000000000..bfb46c250 --- /dev/null +++ b/plugins/module_utils/network/sonic/argspec/fbs_classifiers/fbs_classifiers.py @@ -0,0 +1,122 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the sonic_fbs_classifiers module +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Fbs_classifiersArgs(object): # pylint: disable=R0903 + """The arg spec for the sonic_fbs_classifiers module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'mutually_exclusive': [['match_acl', 'match_hdr_fields']], + 'options': { + 'class_name': {'required': True, 'type': 'str'}, + 'class_description': {'type': 'str'}, + 'match_acl': { + 'options': { + 'acl_name': {'required': True, 'type': 'str'}, + 'acl_type': {'choices': ['ip', 'ipv6', 'mac'], 'required': True, 'type': 'str'} + }, + 'type': 'dict' + }, + 'match_hdr_fields': { + 'mutually_exclusive': [['ipv4', 'ipv6']], + 'options': { + 'ip': { + 'options': { + 'dscp': {'type': 'int'}, + 'protocol': { + 'choices': ['auth', 'gre', 'icmp', 'icmpv6', 'igmp', 'l2tp', 'pim', 'rsvp', 'tcp', 'udp'], + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'ipv4': { + 'options': { + 'destination_address': {'type': 'str'}, + 'source_address': {'type': 'str'} + }, + 'type': 'dict' + }, + 'ipv6': { + 'options': { + 'destination_address': {'type': 'str'}, + 'source_address': {'type': 'str'} + }, + 'type': 'dict' + }, + 'l2': { + 'options': { + 'dei': {'type': 'int'}, + 'destination_mac': {'type': 'str'}, + 'destination_mac_mask': {'type': 'str'}, + 'ethertype': { + 'choices': ['arp', 'ipv4', 'ipv6', 'lldp', 'mpls', 'roce', 'vlan'], + 'type': 'str' + }, + 'pcp': {'type': 'int'}, + 'source_mac': {'type': 'str'}, + 'source_mac_mask': {'type': 'str'}, + 'vlanid': {'type': 'int'} + }, + 'type': 'dict' + }, + 'transport': { + 'options': { + 'destination_port': {'type': 'str'}, + 'icmp_code': {'type': 'int'}, + 'icmp_type': {'type': 'int'}, + 'source_port': {'type': 'str'}, + 'tcp_flags': { + 'choices': ['ack', 'fin', 'psh', 'rst', 'syn', 'urg'], + 'elements': 'str', + 'type': 'list' + } + }, + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'match_type': {'choices': ['acl', 'fields'], 'type': 'str'} + }, + 'type': 'list' + }, + 'state': { + 'choices': ['merged', 'deleted', 'replaced', 'overridden'], + 'default': 'merged', + 'type': 'str' + } + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/sonic/config/fbs_classifiers/__init__.py b/plugins/module_utils/network/sonic/config/fbs_classifiers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/config/fbs_classifiers/fbs_classifiers.py b/plugins/module_utils/network/sonic/config/fbs_classifiers/fbs_classifiers.py new file mode 100644 index 000000000..8f3f4e2df --- /dev/null +++ b/plugins/module_utils/network/sonic/config/fbs_classifiers/fbs_classifiers.py @@ -0,0 +1,598 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic_fbs_classifiers class +It is in this file where the current configuration (as list) +is compared to the provided configuration (as list) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + get_diff, + remove_empties_from_list, + update_states +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + edit_config, + to_request +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( + __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF, + get_new_config, + get_formatted_config_diff +) + +FBS_CLASSIFIERS_PATH = 'data/openconfig-fbs-ext:fbs/classifiers' +PATCH = 'patch' +DELETE = 'delete' +TEST_KEYS = [ + {'config': {'class_name': ''}} +] +TEST_KEYS_generate_config = [ + {'config': {'class_name': '', '__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}} +] +enum_dict = { + 'ack': 'TCP_ACK', + 'acl': 'MATCH_ACL', + 'acl_ipv6': 'ACL_IPV6', + 'arp': 'ETHERTYPE_ARP', + 'auth': 'IP_AUTH', + 'fields': 'MATCH_FIELDS', + 'fin': 'TCP_FIN', + 'gre': 'IP_GRE', + 'icmp': 'IP_ICMP', + 'icmpv6': 58, + 'igmp': 'IP_IGMP', + 'ip': 'ACL_IPV4', + 'ipv4': 'ETHERTYPE_IPV4', + 'ipv6': 'ETHERTYPE_IPV6', + 'l2tp': 'IP_L2TP', + 'lldp': 'ETHERTYPE_LLDP', + 'mac': 'ACL_L2', + 'mpls': 'ETHERTYPE_MPLS', + 'pim': 'IP_PIM', + 'psh': 'TCP_PSH', + 'roce': 'ETHERTYPE_ROCE', + 'rst': 'TCP_RST', + 'rsvp': 'IP_RSVP', + 'syn': 'TCP_SYN', + 'tcp': 'IP_TCP', + 'udp': 'IP_UDP', + 'urg': 'TCP_URG', + 'vlan': 'ETHERTYPE_VLAN' +} + + +class Fbs_classifiers(ConfigBase): + """ + The sonic_fbs_classifiers class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'fbs_classifiers', + ] + + def __init__(self, module): + super(Fbs_classifiers, self).__init__(module) + + def get_fbs_classifiers_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A list + :returns: The current configuration as a list + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + fbs_classifiers_facts = facts['ansible_network_resources'].get('fbs_classifiers') + if not fbs_classifiers_facts: + return [] + return fbs_classifiers_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = [] + commands = [] + + existing_fbs_classifiers_facts = self.get_fbs_classifiers_facts() + commands, requests = self.set_config(existing_fbs_classifiers_facts) + if commands and len(requests) > 0: + if not self._module.check_mode: + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + result['changed'] = True + result['commands'] = commands + + changed_fbs_classifiers_facts = self.get_fbs_classifiers_facts() + + result['before'] = existing_fbs_classifiers_facts + if result['changed']: + result['after'] = changed_fbs_classifiers_facts + + new_config = changed_fbs_classifiers_facts + old_config = existing_fbs_classifiers_facts + if self._module.check_mode: + result.pop('after', None) + new_config = get_new_config(commands, existing_fbs_classifiers_facts, TEST_KEYS_generate_config) + self.post_process_generated_config(new_config) + result['after(generated)'] = new_config + if self._module._diff: + self.sort_lists_in_config(new_config) + self.sort_lists_in_config(old_config) + result['diff'] = get_formatted_config_diff(old_config, + new_config, + self._module._verbosity) + + result['warnings'] = warnings + return result + + def set_config(self, existing_fbs_classifiers_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = remove_empties_from_list(self._module.params['config']) + have = existing_fbs_classifiers_facts + self.sort_lists_in_config(want) + self.sort_lists_in_config(have) + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + requests = [] + state = self._module.params['state'] + diff = get_diff(want, have, TEST_KEYS) + + if state == 'merged': + commands, requests = self._state_merged(diff) + elif state == 'replaced': + commands, requests = self._state_replaced(want, have, diff) + elif state == 'overridden': + commands, requests = self._state_overridden(want, have) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have, diff) + return commands, requests + + def _state_merged(self, diff): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = diff + requests = self.get_modify_classifiers_request(commands) + + if commands and len(requests) > 0: + commands = update_states(commands, 'merged') + else: + commands = [] + + return commands, requests + + def _state_replaced(self, want, have, diff): + """ The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + mod_commands = [] + replaced_config, requests = self.get_replaced_config(want, have) + + if replaced_config: + commands.extend(update_states(replaced_config, 'deleted')) + mod_commands = want + else: + mod_commands = diff + + if mod_commands: + mod_request = self.get_modify_classifiers_request(mod_commands) + + if mod_request: + requests.append(mod_request) + commands.extend(update_states(mod_commands, 'replaced')) + + return commands, requests + + def _state_overridden(self, want, have): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + requests = [] + + if have and have != want: + is_delete_all = True + del_requests = self.get_delete_classifiers_requests(have, is_delete_all) + requests.extend(del_requests) + commands.extend(update_states(have, 'deleted')) + have = [] + + if not have and want: + mod_commands = want + mod_request = self.get_modify_classifiers_request(mod_commands) + + if mod_request: + requests.append(mod_request) + commands.extend(update_states(mod_commands, 'overridden')) + + return commands, requests + + def _state_deleted(self, want, have, diff): + """ The command generator when state is deleted + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + is_delete_all = False + requests = [] + + if not want: + commands = deepcopy(have) + is_delete_all = True + else: + commands = get_diff(want, diff, TEST_KEYS) + + if commands: + requests = self.get_delete_classifiers_requests(commands, is_delete_all) + if len(requests) > 0: + commands = update_states(commands, 'deleted') + else: + commands = [] + + return commands, requests + + def get_modify_classifiers_request(self, commands): + """Returns a patch request to modify the FBS classifiers configuration""" + request = None + classifier_list = [] + + for classifier in commands: + classifier_dict = {} + class_name = classifier.get('class_name') + class_description = classifier.get('class_description') + match_acl = classifier.get('match_acl') + match_hdr_fields = classifier.get('match_hdr_fields') + match_type = classifier.get('match_type') + + if class_name: + classifier_dict.update({'class-name': class_name, 'config': {'name': class_name}}) + if class_description: + classifier_dict['config']['description'] = class_description + if match_type: + classifier_dict['config']['match-type'] = enum_dict[match_type] + if match_acl: + config_dict = {} + acl_name = match_acl.get('acl_name') + acl_type = match_acl.get('acl_type') + + if acl_name: + config_dict['acl-name'] = acl_name + if acl_type: + if acl_type == 'ipv6': + acl_type = 'acl_' + acl_type + config_dict['acl-type'] = enum_dict[acl_type] + classifier_dict['match-acl'] = {'config': config_dict} + if match_hdr_fields: + match_hdr_fields_dict = {} + ip = match_hdr_fields.get('ip') + l2 = match_hdr_fields.get('l2') + transport = match_hdr_fields.get('transport') + + if ip: + config_dict = {} + dscp = ip.get('dscp') + protocol = ip.get('protocol') + + if dscp is not None: + config_dict['dscp'] = dscp + if protocol: + config_dict['protocol'] = enum_dict[protocol] + match_hdr_fields_dict['ip'] = {'config': config_dict} + if l2: + config_dict = {} + dei = l2.get('dei') + destination_mac = l2.get('destination_mac') + destination_mac_mask = l2.get('destination_mac_mask') + ethertype = l2.get('ethertype') + pcp = l2.get('pcp') + source_mac = l2.get('source_mac') + source_mac_mask = l2.get('source_mac_mask') + vlanid = l2.get('vlanid') + + if dei is not None: + config_dict['dei'] = dei + if destination_mac: + config_dict['destination-mac'] = destination_mac + if destination_mac_mask: + config_dict['destination-mac-mask'] = destination_mac_mask + if ethertype: + config_dict['ethertype'] = enum_dict[ethertype] + if pcp is not None: + config_dict['pcp'] = pcp + if source_mac: + config_dict['source-mac'] = source_mac + if source_mac_mask: + config_dict['source-mac-mask'] = source_mac_mask + if vlanid: + config_dict['vlanid'] = vlanid + match_hdr_fields_dict['l2'] = {'config': config_dict} + if transport: + config_dict = {} + destination_port = transport.get('destination_port') + icmp_code = transport.get('icmp_code') + icmp_type = transport.get('icmp_type') + source_port = transport.get('source_port') + tcp_flags = transport.get('tcp_flags') + + if destination_port: + if destination_port.isnumeric(): + destination_port = int(destination_port) + config_dict['destination-port'] = destination_port + if icmp_code: + config_dict['icmp-code'] = icmp_code + if icmp_type: + config_dict['icmp-type'] = icmp_type + if source_port: + if source_port.isnumeric(): + source_port = int(source_port) + config_dict['source-port'] = source_port + if tcp_flags: + converted_flags = [] + for flag in tcp_flags: + converted_flags.append(enum_dict[flag]) + config_dict['tcp-flags'] = converted_flags + match_hdr_fields_dict['transport'] = {'config': config_dict} + for ip_type in ('ipv4', 'ipv6'): + ip_cfg = match_hdr_fields.get(ip_type) + if ip_cfg: + config_dict = {} + destination_address = ip_cfg.get('destination_address') + source_address = ip_cfg.get('source_address') + + if destination_address: + config_dict['destination-address'] = destination_address + if source_address: + config_dict['source-address'] = source_address + match_hdr_fields_dict[ip_type] = {'config': config_dict} + classifier_dict['match-hdr-fields'] = match_hdr_fields_dict + + if classifier_dict: + classifier_list.append(classifier_dict) + if classifier_list: + payload = {'openconfig-fbs-ext:classifiers': {'classifier': classifier_list}} + request = {'path': FBS_CLASSIFIERS_PATH, 'method': PATCH, 'data': payload} + + return request + + def get_delete_classifiers_requests(self, commands, is_delete_all): + """Returns a list of delete requests to delete the specified FBS classifiers configuration""" + requests = [] + + if is_delete_all: + requests.append(self.get_delete_classifiers_request()) + return requests + + for classifier in commands: + class_name = classifier.get('class_name') + class_description = classifier.get('class_description') + match_acl = classifier.get('match_acl') + match_hdr_fields = classifier.get('match_hdr_fields') + match_type = classifier.get('match_type') + + if class_name and not class_description and not match_acl and not match_hdr_fields and not match_type: + requests.append(self.get_delete_classifiers_request(class_name)) + if class_description: + attr_path = '/config/description' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if match_type: + self._module.fail_json(msg='Deletion of match_type not supported') + if match_acl: + attr_path = '/match-acl' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if match_hdr_fields: + ip = match_hdr_fields.get('ip') + l2 = match_hdr_fields.get('l2') + transport = match_hdr_fields.get('transport') + + if l2: + dei = l2.get('dei') + destination_mac = l2.get('destination_mac') + destination_mac_mask = l2.get('destination_mac_mask') + ethertype = l2.get('ethertype') + pcp = l2.get('pcp') + source_mac = l2.get('source_mac') + source_mac_mask = l2.get('source_mac_mask') + vlanid = l2.get('vlanid') + + if dei is not None: + attr_path = '/match-hdr-fields/l2/config/dei' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if destination_mac: + attr_path = '/match-hdr-fields/l2/config/destination-mac' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if destination_mac_mask: + attr_path = '/match-hdr-fields/l2/config/destination-mac-mask' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if ethertype: + attr_path = '/match-hdr-fields/l2/config/ethertype' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if pcp is not None: + attr_path = '/match-hdr-fields/l2/config/pcp' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if source_mac: + attr_path = '/match-hdr-fields/l2/config/source-mac' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if source_mac_mask: + attr_path = '/match-hdr-fields/l2/config/source-mac-mask' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if vlanid: + attr_path = '/match-hdr-fields/l2/config/vlanid' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if transport: + destination_port = transport.get('destination_port') + icmp_code = transport.get('icmp_code') + icmp_type = transport.get('icmp_type') + source_port = transport.get('source_port') + tcp_flags = transport.get('tcp_flags') + + if destination_port: + attr_path = '/match-hdr-fields/transport/config/destination-port' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if icmp_code: + attr_path = '/match-hdr-fields/transport/config/icmp-code' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if icmp_type: + attr_path = '/match-hdr-fields/transport/config/icmp-type' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if source_port: + attr_path = '/match-hdr-fields/transport/config/source-port' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if tcp_flags: + for flag in tcp_flags: + attr_path = '/match-hdr-fields/transport/config/tcp-flags=%s' % (enum_dict[flag]) + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + for ip_type in ('ipv4', 'ipv6'): + ip_cfg = match_hdr_fields.get(ip_type) + if ip_cfg: + destination_address = ip_cfg.get('destination_address') + source_address = ip_cfg.get('source_address') + + if destination_address: + attr_path = '/match-hdr-fields/%s/config/destination-address' % (ip_type) + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if source_address: + attr_path = '/match-hdr-fields/%s/config/source-address' % (ip_type) + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + # IP must be deleted last due to dependecies + if ip: + dscp = ip.get('dscp') + protocol = ip.get('protocol') + + if dscp is not None: + attr_path = '/match-hdr-fields/ip/config/dscp' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + if protocol: + attr_path = '/match-hdr-fields/ip/config/protocol' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + + return requests + + def get_delete_classifiers_request(self, class_name=None, attr_path=None): + url = FBS_CLASSIFIERS_PATH + + if class_name: + url += '/classifier=%s' % (class_name) + if attr_path: + url += attr_path + request = {'path': url, 'method': DELETE} + return request + + def get_replaced_config(self, want, have): + config_list = [] + requests = [] + classifier_dict = {classifier.get('class_name'): classifier for classifier in have} + + for classifier in want: + config_dict = {} + class_name = classifier.get('class_name') + cfg_classifier = classifier_dict.get(class_name) + + if not cfg_classifier: + continue + class_description = classifier.get('class_description') + match_type = classifier.get('match_type') + cfg_class_description = cfg_classifier.get('class_description') + cfg_match_type = cfg_classifier.get('match_type') + + if ((class_description and cfg_class_description and class_description != cfg_class_description) or + (match_type and cfg_match_type and match_type != cfg_match_type)): + requests.append(self.get_delete_classifiers_request(class_name)) + config_list.append(cfg_classifier) + break + + match_acl = classifier.get('match_acl') + match_hdr_fields = classifier.get('match_hdr_fields') + cfg_match_acl = cfg_classifier.get('match_acl') + cfg_match_hdr_fields = cfg_classifier.get('match_hdr_fields') + + if match_acl and cfg_match_acl and match_acl != cfg_match_acl: + attr_path = '/match-acl' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + config_dict.update({'class_name': class_name, 'match_acl': cfg_match_acl}) + if match_hdr_fields and cfg_match_hdr_fields and match_hdr_fields != cfg_match_hdr_fields: + attr_path = '/match-hdr-fields' + requests.append(self.get_delete_classifiers_request(class_name, attr_path)) + config_dict.update({'class_name': class_name, 'match_hdr_fields': cfg_match_hdr_fields}) + if config_dict: + config_list.append(config_dict) + + return config_list, requests + + def sort_lists_in_config(self, config): + if config: + config.sort(key=lambda x: x['class_name']) + for classifier in config: + if (classifier.get('match_hdr_fields') and classifier['match_hdr_fields'].get('transport') and + classifier['match_hdr_fields']['transport'].get('tcp_flags')): + classifier['match_hdr_fields']['transport']['tcp_flags'].sort() + + def post_process_generated_config(self, config): + pop_list = [] + + for classifier in config: + match_hdr_fields = classifier.get('match_hdr_fields') + if match_hdr_fields: + transport = match_hdr_fields.get('transport') + if transport: + if 'tcp_flags' in transport and not transport['tcp_flags']: + transport.pop('tcp_flags') + if not transport: + match_hdr_fields.pop('transport') + if not match_hdr_fields: + classifier.pop('match_hdr_fields') + if 'class_name' in classifier and len(classifier) == 1: + pop_list.insert(0, config.index(classifier)) + for idx in pop_list: + config.pop(idx) diff --git a/plugins/module_utils/network/sonic/facts/facts.py b/plugins/module_utils/network/sonic/facts/facts.py index 73e12acf8..0e77ade6e 100644 --- a/plugins/module_utils/network/sonic/facts/facts.py +++ b/plugins/module_utils/network/sonic/facts/facts.py @@ -79,6 +79,7 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.mgmt_servers.mgmt_servers import Mgmt_serversFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ospf_area.ospf_area import Ospf_areaFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ssh.ssh import SshFacts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.fbs_classifiers.fbs_classifiers import Fbs_classifiersFacts FACT_LEGACY_SUBSETS = {} @@ -144,6 +145,7 @@ mgmt_servers=Mgmt_serversFacts, ospf_area=Ospf_areaFacts, ssh=SshFacts, + fbs_classifiers=Fbs_classifiersFacts ) diff --git a/plugins/module_utils/network/sonic/facts/fbs_classifiers/__init__.py b/plugins/module_utils/network/sonic/facts/fbs_classifiers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/facts/fbs_classifiers/fbs_classifiers.py b/plugins/module_utils/network/sonic/facts/fbs_classifiers/fbs_classifiers.py new file mode 100644 index 000000000..2bfc6b8ea --- /dev/null +++ b/plugins/module_utils/network/sonic/facts/fbs_classifiers/fbs_classifiers.py @@ -0,0 +1,224 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic fbs_classifiers fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + remove_empties_from_list +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.fbs_classifiers.fbs_classifiers import Fbs_classifiersArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) + + +enum_dict = { + 'openconfig-acl:ACL_IPV4': 'ip', + 'openconfig-acl:ACL_IPV6': 'ipv6', + 'openconfig-acl:ACL_L2': 'mac', + 'openconfig-fbs-ext:MATCH_ACL': 'acl', + 'openconfig-fbs-ext:MATCH_FIELDS': 'fields', + 'openconfig-packet-match-types:ETHERTYPE_ARP': 'arp', + 'openconfig-packet-match-types:ETHERTYPE_IPV4': 'ipv4', + 'openconfig-packet-match-types:ETHERTYPE_IPV6': 'ipv6', + 'openconfig-packet-match-types:ETHERTYPE_LLDP': 'lldp', + 'openconfig-packet-match-types:ETHERTYPE_MPLS': 'mpls', + 'openconfig-packet-match-types:ETHERTYPE_ROCE': 'roce', + 'openconfig-packet-match-types:ETHERTYPE_VLAN': 'vlan', + 'openconfig-packet-match-types:IP_AUTH': 'auth', + 'openconfig-packet-match-types:IP_GRE': 'gre', + 'openconfig-packet-match-types:IP_ICMP': 'icmp', + 'openconfig-packet-match-types:IP_IGMP': 'igmp', + 'openconfig-packet-match-types:IP_L2TP': 'l2tp', + 'openconfig-packet-match-types:IP_PIM': 'pim', + 'openconfig-packet-match-types:IP_RSVP': 'rsvp', + 'openconfig-packet-match-types:IP_TCP': 'tcp', + 'openconfig-packet-match-types:IP_UDP': 'udp', + 'openconfig-packet-match-types:TCP_ACK': 'ack', + 'openconfig-packet-match-types:TCP_FIN': 'fin', + 'openconfig-packet-match-types:TCP_PSH': 'psh', + 'openconfig-packet-match-types:TCP_RST': 'rst', + 'openconfig-packet-match-types:TCP_SYN': 'syn', + 'openconfig-packet-match-types:TCP_URG': 'urg', + 58: 'icmpv6' +} + + +class Fbs_classifiersFacts(object): + """ The sonic fbs_classifiers fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Fbs_classifiersArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for fbs_classifiers + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + objs = [] + + if not data: + cfg = self.get_config(self._module) + data = self.get_parsed_fbs_classifiers(cfg) + objs = data + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['fbs_classifiers'] = remove_empties_from_list(params['config']) + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def get_config(self, module): + cfg = None + get_path = 'data/openconfig-fbs-ext:fbs/classifiers' + request = {'path': get_path, 'method': 'get'} + + try: + response = edit_config(module, to_request(module, request)) + if 'openconfig-fbs-ext:classifiers' in response[0][1]: + cfg = response[0][1].get('openconfig-fbs-ext:classifiers') + except ConnectionError as exc: + module.fail_json(msg=str(exc), code=exc.code) + + return cfg + + def get_parsed_fbs_classifiers(self, cfg): + """This method parses the OC FBS classifiers data and returns the parsed data in argspec format""" + config_list = [] + + if cfg and cfg.get('classifier'): + for classifier in cfg['classifier']: + classifier_dict = {} + match_hdr_fields_dict = {} + + if classifier.get('class-name'): + classifier_dict['class_name'] = classifier['class-name'] + if classifier.get('config'): + config = classifier['config'] + if config.get('description'): + classifier_dict['class_description'] = config['description'] + if config.get('match-type'): + classifier_dict['match_type'] = enum_dict[config['match-type']] + + # Parse match-acl container + if classifier.get('match-acl') and classifier['match-acl'].get('config'): + config = classifier['match-acl']['config'] + match_acl_dict = {} + if config.get('acl-name'): + match_acl_dict['acl_name'] = config['acl-name'] + if config.get('acl-type'): + match_acl_dict['acl_type'] = enum_dict[config['acl-type']] + if match_acl_dict: + classifier_dict['match_acl'] = match_acl_dict + + # Parse ip container + if (classifier.get('match-hdr-fields') and classifier['match-hdr-fields'].get('ip') and + classifier['match-hdr-fields']['ip'].get('config')): + config = classifier['match-hdr-fields']['ip']['config'] + ip_dict = {} + + if config.get('dscp') is not None: + ip_dict['dscp'] = config['dscp'] + if config.get('protocol'): + ip_dict['protocol'] = enum_dict[config['protocol']] + if ip_dict: + match_hdr_fields_dict['ip'] = ip_dict + + # Parse ipv4 and ipv6 containers + ip_types = ['ipv4', 'ipv6'] + for ip in ip_types: + if (classifier.get('match-hdr-fields') and classifier['match-hdr-fields'].get(ip) and + classifier['match-hdr-fields'][ip].get('config')): + config = classifier['match-hdr-fields'][ip]['config'] + ip_dict = {} + + if config.get('destination-address'): + ip_dict['destination_address'] = config['destination-address'] + if config.get('source-address'): + ip_dict['source_address'] = config['source-address'] + if ip_dict: + match_hdr_fields_dict[ip] = ip_dict + + # Parse l2 container + if (classifier.get('match-hdr-fields') and classifier['match-hdr-fields'].get('l2') and + classifier['match-hdr-fields']['l2'].get('config')): + config = classifier['match-hdr-fields']['l2']['config'] + l2_dict = {} + + if config.get('dei') is not None: + l2_dict['dei'] = config['dei'] + if config.get('destination-mac'): + l2_dict['destination_mac'] = config['destination-mac'] + if config.get('destination-mac-mask'): + l2_dict['destination_mac_mask'] = config['destination-mac-mask'] + if config.get('ethertype'): + l2_dict['ethertype'] = enum_dict[config['ethertype']] + if config.get('source-mac'): + l2_dict['source_mac'] = config['source-mac'] + if config.get('source-mac-mask'): + l2_dict['source_mac_mask'] = config['source-mac-mask'] + if config.get('pcp') is not None: + l2_dict['pcp'] = config['pcp'] + if config.get('vlanid'): + l2_dict['vlanid'] = config['vlanid'] + if l2_dict: + match_hdr_fields_dict['l2'] = l2_dict + + # Parse transport container + if (classifier.get('match-hdr-fields') and classifier['match-hdr-fields'].get('transport') and + classifier['match-hdr-fields']['transport'].get('config')): + config = classifier['match-hdr-fields']['transport']['config'] + transport_dict = {} + + if config.get('destination-port'): + transport_dict['destination_port'] = config['destination-port'] + if config.get('icmp-code'): + transport_dict['icmp_code'] = config['icmp-code'] + if config.get('icmp-type'): + transport_dict['icmp_type'] = config['icmp-type'] + if config.get('source-port'): + transport_dict['source_port'] = config['source-port'] + if config.get('tcp-flags'): + converted_flags = [] + for flag in config['tcp-flags']: + converted_flags.append(enum_dict[flag]) + transport_dict['tcp_flags'] = converted_flags + if transport_dict: + match_hdr_fields_dict['transport'] = transport_dict + + if match_hdr_fields_dict: + classifier_dict['match_hdr_fields'] = match_hdr_fields_dict + if classifier_dict: + config_list.append(classifier_dict) + + return config_list diff --git a/plugins/modules/sonic_facts.py b/plugins/modules/sonic_facts.py index bd5b282c0..614a41ca5 100644 --- a/plugins/modules/sonic_facts.py +++ b/plugins/modules/sonic_facts.py @@ -114,6 +114,7 @@ - mgmt_servers - ospf_area - ssh + - fbs_classifiers """ EXAMPLES = """ diff --git a/plugins/modules/sonic_fbs_classifiers.py b/plugins/modules/sonic_fbs_classifiers.py new file mode 100644 index 000000000..be3e97196 --- /dev/null +++ b/plugins/modules/sonic_fbs_classifiers.py @@ -0,0 +1,484 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for sonic_fbs_classifiers +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: sonic_fbs_classifiers +version_added: 3.1.0 +notes: + - Tested against Enterprise SONiC Distribution by Dell Technologies. + - Supports C(check_mode). +short_description: Manage flow based services (FBS) classifiers configuration on SONiC +description: + - This module provides configuration management of FBS classifiers for devices running SONiC +author: "Shade Talabi (@stalabi1)" +options: + config: + description: + - FBS classifiers configuration + - I(match_acl) and I(match_hdr_fields) are mutually exclusive. + type: list + elements: dict + suboptions: + class_name: + description: + - Name of classifier + type: str + required: true + class_description: + description: + - Description of classifier + type: str + match_type: + description: + - Classifier match type + type: str + choices: ['acl', 'fields'] + match_acl: + description: + - Match ACL configuration + type: dict + suboptions: + acl_name: + description: + - Name of ACL to be used as match criteria + type: str + required: true + acl_type: + description: + - Type of ACL to be used as match criteria + type: str + choices: ['ip', 'ipv6', 'mac'] + required: true + match_hdr_fields: + description: + - Match header fields configuration + - I(ipv4) and I(ipv6) are mutually exclusive. + type: dict + suboptions: + ip: + description: + - IP field configuration + type: dict + suboptions: + dscp: + description: + - Value of diffserv code point, range 0-63 + type: int + protocol: + description: + - IP protocol + type: str + choices: ['auth', 'gre', 'icmp', 'icmpv6', 'igmp', 'l2tp', 'pim', 'rsvp', 'tcp', 'udp'] + ipv4: + description: + - IPv4 field configuration + type: dict + suboptions: + source_address: + description: + - Source IPv4 address prefix + type: str + destination_address: + description: + - Destination IPv4 address prefix + type: str + ipv6: + description: + - IPv6 field configuration + type: dict + suboptions: + source_address: + description: + - Source IPv6 address prefix + type: str + destination_address: + description: + - Destination IPv6 address prefix + type: str + l2: + description: + - Ethernet field configuration + type: dict + suboptions: + source_mac: + description: + - Source MAC address + type: str + source_mac_mask: + description: + - Source MAC address mask + type: str + destination_mac: + description: + - Destination MAC address + type: str + destination_mac_mask: + description: + - Destination MAC address mask + type: str + dei: + description: + - Drop eligible indicator, range 0-1 + type: int + ethertype: + description: + - Ethertype field to match in ethernet packets + type: str + choices: ['arp', 'ipv4', 'ipv6', 'lldp', 'mpls', 'roce', 'vlan'] + pcp: + description: + - Priority code point, range 0-7 + type: int + vlanid: + description: + - VLAN ID, range 1-4094 + type: int + transport: + description: + - Transport field configuration + type: dict + suboptions: + source_port: + description: + - Source port or range + type: str + destination_port: + description: + - Destination port or range + type: str + icmp_code: + description: + - ICMP or ICMPv6 code, range 0-255 + type: int + icmp_type: + description: + - ICMP or ICMPv6 type, range 0-255 + type: int + tcp_flags: + description: + - List of TCP flags to match + type: list + elements: str + choices: ['ack', 'fin', 'psh', 'rst', 'syn', 'urg'] + state: + description: + - The state of the configuration after module completion + type: str + choices: ['merged', 'deleted', 'replaced', 'overridden'] + default: merged +""" +EXAMPLES = """ +# Using Merged +# +# Before State: +# ------------- +# +# sonic# show class-map +# (No 'class-map' configuration present) + +- name: Merge FBS classifiers configuration + dellemc.enterprise_sonic.sonic_fbs_classifiers: + config: + - class_name: class1 + class_description: xyz + match_type: fields + match_hdr_fields: + ip: + dscp: 0 + protocol: tcp + ipv4: + source_address: 1.1.1.1/1 + destination_address: 2.2.2.2/2 + l2: + source_mac: 1a:2b:3c:4d:5e:6f + source_mac_mask: 6a:5b:4c:3d:2e:1f + destination_mac: 2a:4b:6c:8d:10:20 + destination_mac_mask: 20:10:8d:6c:4b:2a + dei: 0 + ethertype: ipv4 + pcp: 0 + vlanid: 1 + transport: + source_port: 1..3 + destination_port: 4..6 + tcp_flags: + - ack + - fin + - psh + state: merged + +# After State: +# ------------ +# +# sonic# show class-map +# Class-map class1 match-type fields +# Description: xyz +# Match: +# ethertype ip +# src-mac 1a:2b:3c:4d:5e:6f/6a:5b:4c:3d:2e:1f +# dst-mac 2a:4b:6c:8d:10:20/20:10:8d:6c:4b:2a +# vlan 1 +# pcp be +# dei 0 +# ip protocol tcp +# src-ip 1.1.1.1/1 +# dst-ip 2.2.2.2/2 +# dscp default +# src-port 1-3 +# dst-port 4-6 +# tcp-flags fin psh ack +# Referenced in flows: + + +# Using Replaced +# +# Before State: +# ------------- +# +# sonic# show class-map +# Class-map class1 match-type fields +# Description: xyz +# Match: +# ethertype ip +# src-mac 1a:2b:3c:4d:5e:6f/6a:5b:4c:3d:2e:1f +# dst-mac 2a:4b:6c:8d:10:20/20:10:8d:6c:4b:2a +# vlan 1 +# pcp be +# dei 0 +# ip protocol tcp +# src-ip 1.1.1.1/1 +# dst-ip 2.2.2.2/2 +# dscp default +# src-port 1-3 +# dst-port 4-6 +# tcp-flags fin psh ack +# Referenced in flows: + +- name: Replace FBS classifiers configuration + dellemc.enterprise_sonic.sonic_fbs_classifiers: + config: + - class_name: class1 + match_hdr_fields: + l2: + source_mac: 9a:8b:7c:6d:5e:4f + source_mac_mask: 2a:4b:1c:9b:1e:0f + destination_mac: 1a:6c:3c:4f:40:22 + destination_mac_mask: 26:44:8c:9d:4b:6f + ethertype: vlan + pcp: 6 + vlanid: 2 + state: replaced + +# After State: +# ------------ +# +# sonic# show class-map +# Class-map class1 match-type fields +# Description: xyz +# Match: +# ethertype 0x8100 +# src-mac 9a:8b:7c:6d:5e:4f/2a:4b:1c:9b:1e:0f +# dst-mac 1a:6c:3c:4f:40:22/26:44:8c:9d:4b:6f +# vlan 2 +# pcp ic +# Referenced in flows: + + +# Using Overridden +# +# Before State: +# ------------- +# +# sonic# show class-map +# Class-map class1 match-type fields +# Description: xyz +# Match: +# ethertype 0x8100 +# src-mac 9a:8b:7c:6d:5e:4f/2a:4b:1c:9b:1e:0f +# dst-mac 1a:6c:3c:4f:40:22/26:44:8c:9d:4b:6f +# vlan 2 +# pcp ic +# Referenced in flows: + +- name: Override FBS classifiers configuration + dellemc.enterprise_sonic.sonic_fbs_classifiers: + config: + - class_name: class2 + class_description: abc + match_type: acl + match_acl: + acl_name: acl1 + acl_type: ip + +# After State: +# ------------ +# +# sonic# show class-map +# Class-map class2 match-type acl +# Description: abc +# Match: +# ip access-group acl1 +# Referenced in flows: + + +# Using Deleted +# +# Before State: +# ------------- +# +# sonic# show class-map +# Class-map class1 match-type fields +# Description: xyz +# Match: +# ethertype 0x8100 +# src-mac 9a:8b:7c:6d:5e:4f/2a:4b:1c:9b:1e:0f +# dst-mac 1a:6c:3c:4f:40:22/26:44:8c:9d:4b:6f +# vlan 2 +# pcp ic +# Referenced in flows: +# +# Class-map class2 match-type acl +# Description: abc +# Match: +# ip access-group acl1 +# Referenced in flows: + +- name: Delete FBS classifiers configuration + dellemc.enterprise_sonic.sonic_fbs_classifiers: + config: + - class_name: class1 + class_description: xyz + match_hdr_fields: + l2: + source_mac: 9a:8b:7c:6d:5e:4f + source_mac_mask: 2a:4b:1c:9b:1e:0f + destination_mac: 1a:6c:3c:4f:40:22 + destination_mac_mask: 26:44:8c:9d:4b:6f + ethertype: vlan + pcp: 6 + vlanid: 2 + - class_name: class2 + state: deleted + +# After State: +# ------------ +# +# sonic# show class-map +# Class-map class1 match-type fields +# Description: +# Match: +# Referenced in flows: + + +# Using Deleted +# +# Before State: +# ------------- +# +# sonic# show class-map +# Class-map class1 match-type fields +# Description: xyz +# Match: +# ethertype 0x8100 +# src-mac 9a:8b:7c:6d:5e:4f/2a:4b:1c:9b:1e:0f +# dst-mac 1a:6c:3c:4f:40:22/26:44:8c:9d:4b:6f +# vlan 2 +# pcp ic +# Referenced in flows: +# +# Class-map class2 match-type acl +# Description: abc +# Match: +# ip access-group acl1 +# Referenced in flows: + +- name: Delete all FBS classifiers configuration + dellemc.enterprise_sonic.sonic_fbs_classifiers: + config: + state: deleted + +# After State: +# ------------ +# +# sonic# show class-map +# (No 'class-map' configuration present) +""" +RETURN = """ +before: + description: The configuration prior to the module invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +after: + description: The resulting configuration module invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +after(generated): + description: The generated configuration from module invocation. + returned: when C(check_mode) + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.fbs_classifiers.fbs_classifiers import Fbs_classifiersArgs +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.fbs_classifiers.fbs_classifiers import Fbs_classifiers + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Fbs_classifiersArgs.argument_spec, + supports_check_mode=True) + + result = Fbs_classifiers(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/tests/regression/roles/sonic_fbs_classifiers/defaults/main.yml b/tests/regression/roles/sonic_fbs_classifiers/defaults/main.yml new file mode 100644 index 000000000..5b9906a60 --- /dev/null +++ b/tests/regression/roles/sonic_fbs_classifiers/defaults/main.yml @@ -0,0 +1,229 @@ +--- +ansible_connection: httpapi +module_name: fbs_classifiers + +tests: + - name: test_case_01 + description: Initial FBS classifiers configuration + state: merged + input: + - class_name: class1 + class_description: abc + match_type: acl + match_acl: + acl_name: acl1 + acl_type: ip + - class_name: class2 + class_description: xyz + match_type: fields + match_hdr_fields: + ip: + dscp: 0 + protocol: tcp + ipv4: + source_address: 1.1.1.1/1 + destination_address: 2.2.2.2/2 + l2: + source_mac: 1a:2b:3c:4d:5e:6f + source_mac_mask: 6a:5b:4c:3d:2e:1f + destination_mac: 2a:4b:6c:8d:10:20 + destination_mac_mask: 20:10:8d:6c:4b:2a + dei: 0 + ethertype: ipv4 + pcp: 0 + vlanid: 1 + transport: + source_port: 1..3 + destination_port: 4..6 + tcp_flags: + - ack + - fin + - psh + - class_name: class3 + class_description: efg + match_type: fields + match_hdr_fields: + ip: + protocol: icmp + ipv6: + source_address: 1::1/1 + destination_address: 2::2/2 + transport: + icmp_code: 15 + icmp_type: 30 + + - name: test_case_02 + description: Update FBS classifiers configuration + state: merged + input: + - class_name: class1 + class_description: abc123 + match_acl: + acl_name: acl2 + acl_type: mac + - class_name: class2 + class_description: xyz789 + match_hdr_fields: + ip: + dscp: 10 + ipv4: + source_address: 3.3.3.3/3 + destination_address: 4.4.4.4/4 + l2: + source_mac: 2a:3b:4c:5d:6e:7f + source_mac_mask: 5a:4b:3c:2d:1e:0f + destination_mac: 7a:6b:5c:4d:30:20 + destination_mac_mask: 25:40:8b:7c:3b:2f + dei: 1 + pcp: 6 + vlanid: 2 + transport: + source_port: 7..9 + destination_port: 10..12 + tcp_flags: + - urg + - class_name: class3 + class_description: efg456 + match_hdr_fields: + ip: + protocol: icmpv6 + ipv6: + source_address: 9::9/9 + destination_address: 8::8/8 + transport: + icmp_code: 10 + icmp_type: 20 + + - name: test_case_03 + description: Replace FBS classifiers configuration + state: replaced + input: + - class_name: class1 + match_acl: + acl_name: acl1 + acl_type: ipv6 + - class_name: class2 + match_hdr_fields: + l2: + source_mac: 9a:8b:7c:6d:5e:4f + source_mac_mask: 2a:4b:1c:9b:1e:0f + destination_mac: 1a:6c:3c:4f:40:22 + destination_mac_mask: 26:44:8c:9d:4b:6f + ethertype: vlan + pcp: 6 + vlanid: 2 + - class_name: class3 + class_description: qwerty + match_type: acl + - class_name: class5 + match_type: acl + match_acl: + acl_name: acl2 + acl_type: ip + + - name: test_case_04 + description: Override FBS classifiers configuration + state: overridden + input: + - class_name: class1 + class_description: abc + match_type: acl + match_acl: + acl_name: acl1 + acl_type: ip + - class_name: class2 + class_description: xyz + match_type: fields + match_hdr_fields: + ip: + dscp: 0 + protocol: tcp + ipv4: + source_address: 1.1.1.1/1 + destination_address: 2.2.2.2/2 + l2: + source_mac: 1a:2b:3c:4d:5e:6f + source_mac_mask: 6a:5b:4c:3d:2e:1f + destination_mac: 2a:4b:6c:8d:10:20 + destination_mac_mask: 20:10:8d:6c:4b:2a + dei: 0 + ethertype: ipv4 + pcp: 0 + vlanid: 1 + transport: + source_port: 3 + destination_port: 6 + tcp_flags: + - ack + - fin + - psh + - class_name: class3 + class_description: efg + match_type: fields + match_hdr_fields: + ip: + protocol: icmp + ipv6: + source_address: 1::1/1 + destination_address: 2::2/2 + transport: + icmp_code: 15 + icmp_type: 30 + - class_name: class4 + class_description: 'acl class 4' + match_type: acl + match_acl: + acl_name: acl2 + acl_type: mac + + - name: test_case_05 + description: Delete FBS classifiers configuration + state: deleted + input: + - class_name: class1 + class_description: abc + match_acl: + acl_name: acl1 + acl_type: ip + - class_name: class2 + class_description: xyz + match_hdr_fields: + ip: + dscp: 0 + protocol: tcp + ipv4: + source_address: 1.1.1.1/1 + destination_address: 2.2.2.2/2 + l2: + source_mac: 1a:2b:3c:4d:5e:6f + source_mac_mask: 6a:5b:4c:3d:2e:1f + destination_mac: 2a:4b:6c:8d:10:20 + destination_mac_mask: 20:10:8d:6c:4b:2a + dei: 0 + ethertype: ipv4 + pcp: 0 + vlanid: 1 + transport: + source_port: 3 + destination_port: 6 + tcp_flags: + - ack + - fin + - psh + - class_name: class3 + class_description: efg + match_hdr_fields: + ip: + protocol: icmp + ipv6: + source_address: 1::1/1 + destination_address: 2::2/2 + transport: + icmp_code: 15 + icmp_type: 30 + - class_name: class4 + + - name: test_case_06 + description: Delete all FBS classifiers configuration + state: deleted + input: [] diff --git a/tests/regression/roles/sonic_fbs_classifiers/meta/main.yaml b/tests/regression/roles/sonic_fbs_classifiers/meta/main.yaml new file mode 100644 index 000000000..0b356217e --- /dev/null +++ b/tests/regression/roles/sonic_fbs_classifiers/meta/main.yaml @@ -0,0 +1,5 @@ +--- +collections: + - dellemc.enterprise_sonic +dependencies: + - { role: common } diff --git a/tests/regression/roles/sonic_fbs_classifiers/tasks/cleanup.yaml b/tests/regression/roles/sonic_fbs_classifiers/tasks/cleanup.yaml new file mode 100644 index 000000000..224c1a489 --- /dev/null +++ b/tests/regression/roles/sonic_fbs_classifiers/tasks/cleanup.yaml @@ -0,0 +1,11 @@ +- name: Delete VLANs + sonic_vlans: + config: [] + state: deleted + ignore_errors: true + +- name: Delete ACLs + sonic_l2_acls: + config: [] + state: deleted + ignore_errors: true diff --git a/tests/regression/roles/sonic_fbs_classifiers/tasks/main.yml b/tests/regression/roles/sonic_fbs_classifiers/tasks/main.yml new file mode 100644 index 000000000..af9dbe6a7 --- /dev/null +++ b/tests/regression/roles/sonic_fbs_classifiers/tasks/main.yml @@ -0,0 +1,14 @@ +- debug: msg="Executing regression testing for sonic_fbs_classifiers..." + +- set_fact: + base_cfg_path: "{{ playbook_dir + '/roles/' + role_name + '/' + 'templates/' }}" + +- name: Test preparation + include_tasks: preparation.yaml + +- name: "Test {{ module_name }} started ..." + include_tasks: tasks_template.yaml + loop: "{{ tests }}" + +- name: Test cleanup + include_tasks: cleanup.yaml diff --git a/tests/regression/roles/sonic_fbs_classifiers/tasks/preparation.yaml b/tests/regression/roles/sonic_fbs_classifiers/tasks/preparation.yaml new file mode 100644 index 000000000..78e494573 --- /dev/null +++ b/tests/regression/roles/sonic_fbs_classifiers/tasks/preparation.yaml @@ -0,0 +1,18 @@ +- name: Delete FBS classifiers configuration + sonic_fbs_classifiers: + config: [] + state: deleted + +- name: Create VLANs + sonic_vlans: + config: + - vlan_id: 1 + - vlan_id: 2 + ignore_errors: true + +- name: Create ACLs + sonic_l2_acls: + config: + - name: acl1 + - name: acl2 + ignore_errors: true diff --git a/tests/regression/roles/sonic_fbs_classifiers/tasks/tasks_template.yaml b/tests/regression/roles/sonic_fbs_classifiers/tasks/tasks_template.yaml new file mode 100644 index 000000000..7c9eefdf7 --- /dev/null +++ b/tests/regression/roles/sonic_fbs_classifiers/tasks/tasks_template.yaml @@ -0,0 +1,21 @@ +- name: "{{ item.name }} , {{ item.description }}" + sonic_fbs_classifiers: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: action_task_output + ignore_errors: yes + +- import_role: + name: common + tasks_from: action.facts.report.yaml + +- name: "{{ item.name }} , {{ item.description }} Idempotent" + sonic_fbs_classifiers: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: idempotent_task_output + ignore_errors: yes + +- import_role: + name: common + tasks_from: idempotent.facts.report.yaml diff --git a/tests/regression/test.yaml b/tests/regression/test.yaml index 5ea3065b3..ad198e60d 100644 --- a/tests/regression/test.yaml +++ b/tests/regression/test.yaml @@ -72,4 +72,5 @@ - sonic_poe - sonic_mgmt_servers - sonic_ssh + - sonic_fbs_classifiers - test_reports diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_fbs_classifiers.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_fbs_classifiers.yaml new file mode 100644 index 000000000..ad127e316 --- /dev/null +++ b/tests/unit/modules/network/sonic/fixtures/sonic_fbs_classifiers.yaml @@ -0,0 +1,636 @@ +merged_01: + module_args: + config: + - class_name: class1 + class_description: abc + match_type: acl + match_acl: + acl_name: acl1 + acl_type: ip + - class_name: class2 + class_description: xyz + match_type: fields + match_hdr_fields: + ip: + dscp: 0 + protocol: tcp + ipv4: + source_address: 1.1.1.1/1 + destination_address: 2.2.2.2/2 + l2: + source_mac: 1a:2b:3c:4d:5e:6f + source_mac_mask: 6a:5b:4c:3d:2e:1f + destination_mac: 2a:4b:6c:8d:10:20 + destination_mac_mask: 20:10:8d:6c:4b:2a + dei: 0 + ethertype: ipv4 + pcp: 0 + vlanid: 1 + transport: + source_port: 1..3 + destination_port: 4..6 + tcp_flags: + - ack + - fin + - psh + - class_name: class3 + class_description: efg + match_type: fields + match_hdr_fields: + ip: + protocol: icmp + ipv6: + source_address: 1::1/1 + destination_address: 2::2/2 + transport: + icmp_code: 15 + icmp_type: 30 + existing_fbs_classifiers_config: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + response: + code: 200 + expected_config_requests: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + method: 'patch' + data: + openconfig-fbs-ext:classifiers: + classifier: + - class-name: class1 + config: + name: class1 + description: abc + match-type: MATCH_ACL + match-acl: + config: + acl-name: acl1 + acl-type: ACL_IPV4 + - class-name: class2 + config: + name: class2 + description: xyz + match-type: MATCH_FIELDS + match-hdr-fields: + ip: + config: + dscp: 0 + protocol: IP_TCP + l2: + config: + dei: 0 + destination-mac: 2a:4b:6c:8d:10:20 + destination-mac-mask: 20:10:8d:6c:4b:2a + ethertype: ETHERTYPE_IPV4 + pcp: 0 + source-mac: 1a:2b:3c:4d:5e:6f + source-mac-mask: 6a:5b:4c:3d:2e:1f + vlanid: 1 + transport: + config: + destination-port: 4..6 + source-port: 1..3 + tcp-flags: + - TCP_ACK + - TCP_FIN + - TCP_PSH + ipv4: + config: + destination-address: 2.2.2.2/2 + source-address: 1.1.1.1/1 + - class-name: class3 + config: + name: class3 + description: efg + match-type: MATCH_FIELDS + match-hdr-fields: + ip: + config: + protocol: IP_ICMP + transport: + config: + icmp-code: 15 + icmp-type: 30 + ipv6: + config: + destination-address: 2::2/2 + source-address: 1::1/1 + +replaced_01: + module_args: + config: + - class_name: class1 + match_acl: + acl_name: acl2 + acl_type: ipv6 + - class_name: class2 + match_hdr_fields: + l2: + source_mac: 9a:8b:7c:6d:5e:4f + source_mac_mask: 2a:4b:1c:9b:1e:0f + destination_mac: 1a:6c:3c:4f:40:22 + destination_mac_mask: 26:44:8c:9d:4b:6f + ethertype: vlan + pcp: 6 + vlanid: 2 + - class_name: class3 + class_description: qwerty + match_type: acl + state: replaced + existing_fbs_classifiers_config: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + response: + code: 200 + value: + openconfig-fbs-ext:classifiers: + classifier: + - class-name: class1 + config: + name: class1 + description: abc + match-type: openconfig-fbs-ext:MATCH_ACL + match-acl: + config: + acl-name: acl1 + acl-type: openconfig-acl:ACL_IPV4 + - class-name: class2 + config: + name: class2 + description: xyz + match-type: openconfig-fbs-ext:MATCH_FIELDS + match-hdr-fields: + ip: + config: + dscp: 0 + protocol: openconfig-packet-match-types:IP_TCP + l2: + config: + dei: 0 + destination-mac: 2a:4b:6c:8d:10:20 + destination-mac-mask: 20:10:8d:6c:4b:2a + ethertype: openconfig-packet-match-types:ETHERTYPE_IPV4 + pcp: 0 + source-mac: 1a:2b:3c:4d:5e:6f + source-mac-mask: 6a:5b:4c:3d:2e:1f + vlanid: 1 + transport: + config: + destination-port: 4..6 + source-port: 1..3 + tcp-flags: + - openconfig-packet-match-types:TCP_ACK + - openconfig-packet-match-types:TCP_FIN + - openconfig-packet-match-types:TCP_PSH + ipv4: + config: + destination-address: 2.2.2.2/2 + source-address: 1.1.1.1/1 + - class-name: class3 + config: + name: class3 + description: efg + match-type: openconfig-fbs-ext:MATCH_FIELDS + match-hdr-fields: + ip: + config: + protocol: openconfig-packet-match-types:IP_ICMP + transport: + config: + icmp-code: 15 + icmp-type: 30 + ipv6: + config: + destination-address: 2::2/2 + source-address: 1::1/1 + expected_config_requests: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class1/match-acl' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class3' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + method: 'patch' + data: + openconfig-fbs-ext:classifiers: + classifier: + - class-name: class1 + config: + name: class1 + match-acl: + config: + acl-name: acl2 + acl-type: ACL_IPV6 + - class-name: class2 + config: + name: class2 + match-hdr-fields: + l2: + config: + destination-mac: 1a:6c:3c:4f:40:22 + destination-mac-mask: 26:44:8c:9d:4b:6f + ethertype: ETHERTYPE_VLAN + pcp: 6 + source-mac: 9a:8b:7c:6d:5e:4f + source-mac-mask: 2a:4b:1c:9b:1e:0f + vlanid: 2 + - class-name: class3 + config: + name: class3 + description: qwerty + match-type: MATCH_ACL + +overridden_01: + module_args: + config: + - class_name: class1 + class_description: abc + match_type: acl + match_acl: + acl_name: acl1 + acl_type: ip + - class_name: class2 + class_description: xyz + match_type: fields + match_hdr_fields: + ip: + dscp: 0 + protocol: tcp + ipv4: + source_address: 1.1.1.1/1 + destination_address: 2.2.2.2/2 + l2: + source_mac: 1a:2b:3c:4d:5e:6f + source_mac_mask: 6a:5b:4c:3d:2e:1f + destination_mac: 2a:4b:6c:8d:10:20 + destination_mac_mask: 20:10:8d:6c:4b:2a + dei: 0 + ethertype: ipv4 + pcp: 0 + vlanid: 1 + transport: + source_port: 3 + destination_port: 6 + tcp_flags: + - ack + - fin + - psh + - class_name: class3 + class_description: efg + match_type: fields + match_hdr_fields: + ip: + protocol: icmp + ipv6: + source_address: 1::1/1 + destination_address: 2::2/2 + transport: + icmp_code: 15 + icmp_type: 30 + - class_name: class4 + class_description: 'acl class 4' + match_type: acl + match_acl: + acl_name: acl2 + acl_type: mac + state: overridden + existing_fbs_classifiers_config: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + response: + code: 200 + value: + openconfig-fbs-ext:classifiers: + classifier: + - class-name: class1 + config: + name: class1 + match-acl: + config: + acl-name: acl2 + acl-type: openconfig-acl:ACL_IPV6 + - class-name: class2 + config: + name: class2 + match-hdr-fields: + l2: + config: + destination-mac: 1a:6c:3c:4f:40:22 + destination-mac-mask: 26:44:8c:9d:4b:6f + ethertype: openconfig-packet-match-types:ETHERTYPE_VLAN + pcp: 6 + source-mac: 9a:8b:7c:6d:5e:4f + source-mac-mask: 2a:4b:1c:9b:1e:0f + vlanid: 2 + - class-name: class3 + config: + name: class3 + description: qwerty + match-type: openconfig-fbs-ext:MATCH_ACL + expected_config_requests: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + method: 'patch' + data: + openconfig-fbs-ext:classifiers: + classifier: + - class-name: class1 + config: + name: class1 + description: abc + match-type: MATCH_ACL + match-acl: + config: + acl-name: acl1 + acl-type: ACL_IPV4 + - class-name: class2 + config: + name: class2 + description: xyz + match-type: MATCH_FIELDS + match-hdr-fields: + ip: + config: + dscp: 0 + protocol: IP_TCP + l2: + config: + dei: 0 + destination-mac: 2a:4b:6c:8d:10:20 + destination-mac-mask: 20:10:8d:6c:4b:2a + ethertype: ETHERTYPE_IPV4 + pcp: 0 + source-mac: 1a:2b:3c:4d:5e:6f + source-mac-mask: 6a:5b:4c:3d:2e:1f + vlanid: 1 + transport: + config: + destination-port: 6 + source-port: 3 + tcp-flags: + - TCP_ACK + - TCP_FIN + - TCP_PSH + ipv4: + config: + destination-address: 2.2.2.2/2 + source-address: 1.1.1.1/1 + - class-name: class3 + config: + name: class3 + description: efg + match-type: MATCH_FIELDS + match-hdr-fields: + ip: + config: + protocol: IP_ICMP + transport: + config: + icmp-code: 15 + icmp-type: 30 + ipv6: + config: + destination-address: 2::2/2 + source-address: 1::1/1 + - class-name: class4 + config: + name: class4 + description: 'acl class 4' + match-type: MATCH_ACL + match-acl: + config: + acl-name: acl2 + acl-type: ACL_L2 + +deleted_01: + module_args: + config: + - class_name: class1 + class_description: abc + match_acl: + acl_name: acl1 + acl_type: ip + - class_name: class2 + class_description: xyz + match_hdr_fields: + ip: + dscp: 0 + protocol: tcp + ipv4: + source_address: 1.1.1.1/1 + destination_address: 2.2.2.2/2 + l2: + source_mac: 1a:2b:3c:4d:5e:6f + source_mac_mask: 6a:5b:4c:3d:2e:1f + destination_mac: 2a:4b:6c:8d:10:20 + destination_mac_mask: 20:10:8d:6c:4b:2a + dei: 0 + ethertype: ipv4 + pcp: 0 + vlanid: 1 + transport: + source_port: 3 + destination_port: 6 + tcp_flags: + - ack + - fin + - psh + - class_name: class3 + class_description: efg + match_hdr_fields: + ip: + protocol: icmp + ipv6: + source_address: 1::1/1 + destination_address: 2::2/2 + transport: + icmp_code: 15 + icmp_type: 30 + - class_name: class4 + state: deleted + existing_fbs_classifiers_config: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + response: + code: 200 + value: + openconfig-fbs-ext:classifiers: + classifier: + - class-name: class1 + config: + name: class1 + description: abc + match-type: openconfig-fbs-ext:MATCH_ACL + match-acl: + config: + acl-name: acl1 + acl-type: openconfig-acl:ACL_IPV4 + - class-name: class2 + config: + name: class2 + description: xyz + match-type: openconfig-fbs-ext:MATCH_FIELDS + match-hdr-fields: + ip: + config: + dscp: 0 + protocol: openconfig-packet-match-types:IP_TCP + l2: + config: + dei: 0 + destination-mac: 2a:4b:6c:8d:10:20 + destination-mac-mask: 20:10:8d:6c:4b:2a + ethertype: openconfig-packet-match-types:ETHERTYPE_IPV4 + pcp: 0 + source-mac: 1a:2b:3c:4d:5e:6f + source-mac-mask: 6a:5b:4c:3d:2e:1f + vlanid: 1 + transport: + config: + destination-port: 6 + source-port: 3 + tcp-flags: + - openconfig-packet-match-types:TCP_ACK + - openconfig-packet-match-types:TCP_FIN + - openconfig-packet-match-types:TCP_PSH + ipv4: + config: + destination-address: 2.2.2.2/2 + source-address: 1.1.1.1/1 + - class-name: class3 + config: + name: class3 + description: efg + match-type: openconfig-fbs-ext:MATCH_FIELDS + match-hdr-fields: + ip: + config: + protocol: openconfig-packet-match-types:IP_ICMP + transport: + config: + icmp-code: 15 + icmp-type: 30 + ipv6: + config: + destination-address: 2::2/2 + source-address: 1::1/1 + - class-name: class4 + config: + name: class4 + description: 'acl class 4' + match-type: openconfig-fbs-ext:MATCH_ACL + match-acl: + config: + acl-name: acl2 + acl-type: openconfig-acl:ACL_L2 + expected_config_requests: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class1/config/description' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class1/match-acl' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/config/description' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/ip/config/dscp' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/ip/config/protocol' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/ipv4/config/source-address' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/ipv4/config/destination-address' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/l2/config/source-mac' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/l2/config/source-mac-mask' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/l2/config/destination-mac' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/l2/config/destination-mac-mask' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/l2/config/dei' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/l2/config/ethertype' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/l2/config/pcp' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/l2/config/vlanid' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/transport/config/source-port' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/transport/config/destination-port' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/transport/config/tcp-flags=TCP_ACK' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/transport/config/tcp-flags=TCP_FIN' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class2/match-hdr-fields/transport/config/tcp-flags=TCP_PSH' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class3/config/description' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class3/match-hdr-fields/ip/config/protocol' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class3/match-hdr-fields/ipv6/config/source-address' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class3/match-hdr-fields/ipv6/config/destination-address' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class3/match-hdr-fields/transport/config/icmp-code' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class3/match-hdr-fields/transport/config/icmp-type' + method: 'delete' + data: + - path: 'data/openconfig-fbs-ext:fbs/classifiers/classifier=class4' + method: 'delete' + data: + +deleted_02: + module_args: + config: [] + state: deleted + existing_fbs_classifiers_config: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + response: + code: 200 + value: + openconfig-fbs-ext:classifiers: + classifier: + - class-name: class1 + config: + name: class1 + match-type: openconfig-fbs-ext:MATCH_ACL + - class-name: class2 + config: + name: class2 + match-type: openconfig-fbs-ext:MATCH_FIELDS + - class-name: class3 + config: + name: class3 + match-type: openconfig-fbs-ext:MATCH_FIELDS + expected_config_requests: + - path: 'data/openconfig-fbs-ext:fbs/classifiers' + method: 'delete' + data: diff --git a/tests/unit/modules/network/sonic/test_sonic_fbs_classifiers.py b/tests/unit/modules/network/sonic/test_sonic_fbs_classifiers.py new file mode 100644 index 000000000..3c695fa26 --- /dev/null +++ b/tests/unit/modules/network/sonic/test_sonic_fbs_classifiers.py @@ -0,0 +1,87 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.dellemc.enterprise_sonic.tests.unit.compat.mock import ( + patch, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.modules import ( + sonic_fbs_classifiers, +) +from ansible_collections.dellemc.enterprise_sonic.tests.unit.modules.utils import ( + set_module_args, +) +from .sonic_module import TestSonicModule + + +class TestSonicFbsClassifiersModule(TestSonicModule): + module = sonic_fbs_classifiers + + @classmethod + def setUpClass(cls): + cls.mock_facts_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.fbs_classifiers.fbs_classifiers.edit_config" + ) + cls.mock_config_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.fbs_classifiers.fbs_classifiers.edit_config" + ) + cls.mock_utils_edit_config = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.edit_config" + ) + cls.mock_get_interface_naming_mode = patch( + "ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.get_device_interface_naming_mode" + ) + cls.fixture_data = cls.load_fixtures('sonic_fbs_classifiers.yaml') + + def setUp(self): + super(TestSonicFbsClassifiersModule, self).setUp() + self.facts_edit_config = self.mock_facts_edit_config.start() + self.config_edit_config = self.mock_config_edit_config.start() + self.facts_edit_config.side_effect = self.facts_side_effect + self.config_edit_config.side_effect = self.config_side_effect + self.get_interface_naming_mode = self.mock_get_interface_naming_mode.start() + self.get_interface_naming_mode.return_value = 'native' + self.utils_edit_config = self.mock_utils_edit_config.start() + self.utils_edit_config.side_effect = self.facts_side_effect + + def tearDown(self): + super(TestSonicFbsClassifiersModule, self).tearDown() + self.mock_facts_edit_config.stop() + self.mock_config_edit_config.stop() + self.mock_get_interface_naming_mode.stop() + self.mock_utils_edit_config.stop() + + def test_sonic_fbs_classifiers_merged_01(self): + set_module_args(self.fixture_data['merged_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_01']['existing_fbs_classifiers_config']) + self.initialize_config_requests(self.fixture_data['merged_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_fbs_classifiers_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['existing_fbs_classifiers_config']) + self.initialize_config_requests(self.fixture_data['replaced_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_fbs_classifiers_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['existing_fbs_classifiers_config']) + self.initialize_config_requests(self.fixture_data['overridden_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_fbs_classifiers_deleted_01(self): + set_module_args(self.fixture_data['deleted_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_01']['existing_fbs_classifiers_config']) + self.initialize_config_requests(self.fixture_data['deleted_01']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_fbs_classifiers_deleted_02(self): + set_module_args(self.fixture_data['deleted_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_02']['existing_fbs_classifiers_config']) + self.initialize_config_requests(self.fixture_data['deleted_02']['expected_config_requests']) + result = self.execute_module(changed=True) + self.validate_config_requests()