diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 19be9bef7e..2ecd16a805 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -250,6 +250,9 @@ * [Historical Memory Statistics for Last 3 Hours](#historical-memory-statistics-for-last-3-hours) * [Historical Memory Statistics for Specific Metric (Used Memory)](#historical-memory-statistics-for-specific-metric-used-memory) * [View Memory Statistics Configuration](#view-memory-statistics-configuration) +* [CoPP Commands](#copp-commands) + * [Overview](#overview) + * [CoPP show commands](#copp-show-commands) ## Document History | Version | Modification Date | Details | @@ -10862,6 +10865,7 @@ This sub-section explains the show commands for displaying the running configura 6) acl 7) ports 8) syslog +9) copp **show runningconfiguration all** @@ -10988,6 +10992,20 @@ This command displays the running configuration of the snmp module. admin@sonic:~$ show runningconfiguration ports Ethernet0 ``` + **show runningconfiguration copp** + + This command displays the running configuration of copp + +- Usage: + ``` + show runningconfiguration copp + ``` + +- Example: + ``` + admin@sonic:~$ show runningconfiguration copp + ``` + Go Back To [Beginning of the document](#) or [Beginning of this section](#Startup--Running-Configuration) @@ -14810,3 +14828,106 @@ Enabled: false Sampling Interval: 5 Retention Period: 15 ``` +--- +# CoPP Commands + +## Overview +This sub-section explains the list of commands available for CoPP (Control Plane Policing) feature. + +--- + +## CoPP Show Commands + +These commands are used to display the current CoPP configuration and their status. + +### Usage +```bash +show copp configuration [--trapid ] [--group ] +``` + +**Example**: + +```bash +show copp configuration +show copp configuration detailed --group queue1_group3 +show copp configuration detailed --trapid neighbor_miss +``` + +### Show CoPP Configuration + +Command to display the current CoPP configurations and hardware status of the traps. + +```bash +admin@sonic:~$ show copp configuration +``` + +**Sample Output**: + +```bash +admin@sonic:~$ show copp configuration +TrapId Trap Group Action CBS CIR Meter Type Mode HW Status +--------------- ------------- -------- ----- ----- ------------ ------ ------------- +arp_req queue4_group2 copy 600 600 packets sr_tcm installed +arp_resp queue4_group2 copy 600 600 packets sr_tcm installed +bgp queue4_group1 trap 6000 6000 packets sr_tcm not-installed +bgpv6 queue4_group1 trap 6000 6000 packets sr_tcm not-installed +dest_nat_miss queue1_group2 trap 600 600 packets sr_tcm installed +dhcp queue4_group3 trap 100 100 packets sr_tcm installed +dhcpv6 queue4_group3 trap 100 100 packets sr_tcm installed +eapol queue4_group1 trap 6000 6000 packets sr_tcm installed +ip2me queue1_group1 trap 6000 6000 packets sr_tcm installed +lacp queue4_group1 trap 6000 6000 packets sr_tcm installed +lldp queue4_group3 trap 100 100 packets sr_tcm installed +neigh_discovery queue4_group2 copy 600 600 packets sr_tcm installed +neighbor_miss queue1_group3 trap 200 200 packets sr_tcm installed +sample_packet queue2_group1 trap 1000 1000 packets sr_tcm not-installed +src_nat_miss queue1_group2 trap 600 600 packets sr_tcm installed +udld queue4_group3 trap 100 100 packets sr_tcm installed +``` + +### Show CoPP Configuration Detailed + +Command to display the detailed CoPP configuration of a specific trap ID. + +```bash +admin@sonic:~$ show copp configuration detailed --trapid neighbor_miss +``` + +**Sample Output**: + +```bash +Trap Group.................. queue1_group3 +queue....................... 1 +Trap Priority............... 1 +Trap Action................. trap +Meter Type.................. packets +Mode........................ sr_tcm +CBS......................... 200 +CIR......................... 200 +Green Action................ forward +Yellow Action............... forward +Red Action.................. drop +Oper Status................. installed +``` + +Command to display the detailed CoPP configuration of a specific CoPP group. + +```bash +admin@sonic:~$ show copp configuration detailed --group queue1_group3 +``` + +**Sample Output**: + +```bash +Trap Id(s).................. neighbor_miss +queue....................... 1 +Trap Priority............... 1 +Trap Action................. trap +Meter Type.................. packets +Mode........................ sr_tcm +CBS......................... 200 +CIR......................... 200 +Yellow Action............... forward +Green Action................ forward +Red Action.................. drop +``` diff --git a/dump/plugins/copp.py b/dump/plugins/copp.py index 3b63b15706..168662442d 100644 --- a/dump/plugins/copp.py +++ b/dump/plugins/copp.py @@ -43,7 +43,8 @@ "bfd": "SAI_HOSTIF_TRAP_TYPE_BFD", "bfdv6": "SAI_HOSTIF_TRAP_TYPE_BFDV6", "src_nat_miss": "SAI_HOSTIF_TRAP_TYPE_SNAT_MISS", - "dest_nat_miss": "SAI_HOSTIF_TRAP_TYPE_DNAT_MISS" + "dest_nat_miss": "SAI_HOSTIF_TRAP_TYPE_DNAT_MISS", + "neighbor_miss": "SAI_HOSTIF_TRAP_TYPE_NEIGHBOR_MISS" } CFG_COPP_TRAP_TABLE_NAME = "COPP_TRAP" diff --git a/show/copp.py b/show/copp.py new file mode 100644 index 0000000000..933d20edd6 --- /dev/null +++ b/show/copp.py @@ -0,0 +1,266 @@ +import click +import json +import utilities_common.cli as clicommon +from swsscommon.swsscommon import SonicV2Connector +from natsort import natsorted +from tabulate import tabulate + +COPP_INIT_CFG_JSON_FILE = "/etc/sonic/copp_cfg.json" + +############################################################################## +# CoPP show commands +# show copp configuration +# show copp configuration detailed --trapid +# show copp configuration detailed --group +# ############################################################################ + + +def get_copp_trap_hw_status(trap_id, state_db): + """Get CoPP Trap operational status""" + + state_db_data = state_db.get_all(state_db.STATE_DB, f"COPP_TRAP_TABLE|{trap_id}") + hw_status = state_db_data.get("hw_status", "not-installed") \ + if state_db_data else "not-installed" + + return hw_status + + +def print_single_copp_entry(entry, trap_id=None, group=None): + """Print single copp entry""" + + if not trap_id: + click.echo("Trap Id(s).................. {}".format(",".join(entry.get("trap_ids", {})))) + else: + click.echo("Trap Group.................. {}".format(group)) + + click.echo("Trap Action................. {}".format(entry.get("trap_action", ""))) + click.echo("Trap Priority............... {}".format(entry.get("trap_priority", ""))) + click.echo("Queue....................... {}".format(entry.get("queue", ""))) + click.echo("CBS......................... {}".format(entry.get("cbs", ""))) + click.echo("CIR......................... {}".format(entry.get("cir", ""))) + click.echo("Meter Type.................. {}".format(entry.get("meter_type", ""))) + mode = entry.get("mode", "") + click.echo("Mode........................ {}".format(mode)) + + if mode in ['sr_tcm', 'tr_tcm']: + click.echo("Yellow Action............... {}".format(entry.get("yellow_action", "forward"))) + + click.echo("Green Action................ {}".format(entry.get("green_action", "forward"))) + click.echo("Red Action.................. {}".format(entry.get("red_action", ""))) + + if trap_id: + state_db = SonicV2Connector() + state_db.connect(state_db.STATE_DB) + hw_status = get_copp_trap_hw_status(trap_id, state_db) + click.echo("HW Status................... {}".format(hw_status)) + + +def merge_copp_entries(config_db, json_data, db_keys, table_name, input_key=None, is_group=False): + """ + Merge CoPP entries (groups or traps) from CONFIG_DB and copp_cfg.json. + + Args: + config_db: CONFIG_DB connector. + json_data: JSON data from copp_cfg.json. + db_keys: List of keys from CONFIG_DB. + table_name: Key in the JSON data (e.g., "COPP_GROUP" or "COPP_TRAP"). + input_key: Specific key to filter (e.g., input_group or input_trap). + is_group: Boolean indicating whether the entry is a group (True) or a trap (False). + + Returns: + A dictionary of merged entries. + """ + + merged_entries = {} + json_entries = json_data.get(table_name, {}) + + # Merge entries from copp_cfg.json + for json_entry, json_entry_data in json_entries.items(): + + # Strip leading and trailing spaces from the entry key and its nested fields + json_entry = json_entry.strip() + json_entry_data = {k.strip(): v.strip() for k, v in json_entry_data.items()} + + # For groups, filter by input_key (input_group) + if is_group and input_key and json_entry != input_key: + continue + + # For traps, skip if input_key (input_trap) is not in trap_ids + if not is_group and input_key: + trap_ids = json_entry_data.get("trap_ids", "").split(",") + if not any(trap_id.strip() == input_key for trap_id in trap_ids): + continue + + # Ignore entries with "NULL" keys + if "NULL" in json_entry_data: + continue + + merged_entries[json_entry] = json_entry_data + + if json_entry in db_keys: + db_entry = config_db.get_all(config_db.CONFIG_DB, f'{table_name}|{json_entry}') + if "NULL" in db_entry: + del merged_entries[json_entry] + continue + for json_field in json_entry_data: + if json_field in db_entry: + merged_entries[json_entry][json_field] = db_entry[json_field] + + # Add keys from db_entry that are not in json_entry_data + merged_entries[json_entry].update({db_field: db_entry[db_field] + for db_field in db_entry if db_field not in merged_entries[json_entry]}) + + # Include entries from CONFIG_DB that are not in copp_cfg.json + for db_entry_key in db_keys: + # For groups, filter by input_key (input_group) + if is_group and input_key and db_entry_key != input_key: + continue + + # For traps, skip if input_key (input_trap) is not in trap_ids + if not is_group and input_key: + db_entry = config_db.get_all(config_db.CONFIG_DB, f'{table_name}|{db_entry_key}') + if input_key not in db_entry.get("trap_ids", "").split(","): + continue + + if db_entry_key not in merged_entries: + db_entry = config_db.get_all(config_db.CONFIG_DB, f'{table_name}|{db_entry_key}') + if "NULL" not in db_entry: + merged_entries[db_entry_key] = db_entry + + return merged_entries + + +def merge_copp_config(config_db, input_trap=None, input_group=None): + """Merge copp configuration""" + + copp_group_table = f"COPP_GROUP|{input_group}" if input_group else "COPP_GROUP|*" + copp_trap_table = "COPP_TRAP|*" + + cfg_db_grp_keys = config_db.keys(config_db.CONFIG_DB, copp_group_table) or [] + cfg_db_trap_keys = config_db.keys(config_db.CONFIG_DB, copp_trap_table) or [] + + # Remove 'COPP_GROUP|' and 'COPP_TRAP|' from the keys + cfg_db_grp_keys = [key.replace('COPP_GROUP|', '') for key in cfg_db_grp_keys] + cfg_db_trap_keys = [key.replace('COPP_TRAP|', '') for key in cfg_db_trap_keys] + + json_data = {} + try: + # Read the JSON data from the file + with open(COPP_INIT_CFG_JSON_FILE, 'r') as file: + json_data = json.load(file) + except FileNotFoundError: + click.echo(f"WARNING: CoPP CFG file: '{COPP_INIT_CFG_JSON_FILE}' not found.") + + # Merge CoPP groups and traps + merged_group = merge_copp_entries(config_db, + json_data, + cfg_db_grp_keys, + "COPP_GROUP", + input_group, + is_group=True) + merged_traps = merge_copp_entries(config_db, + json_data, + cfg_db_trap_keys, + "COPP_TRAP", + input_trap, + is_group=False) + + # Merge trap_ids from merged_traps to merged_group + for trap_key, trap_data in merged_traps.items(): + trap_ids_list = trap_data.get("trap_ids", "").split(",") + trap_ids_list = [trap_id.strip() for trap_id in trap_ids_list if trap_id.strip()] + trap_group = trap_data.get("trap_group", "") + + # Make sure the trap_group exists in merged_group + if trap_group not in merged_group: + continue + + # Add trap_ids to merged_group[trap_group] + merged_group[trap_group].setdefault("trap_ids", []).extend(trap_ids_list) + + return merged_group, merged_traps + + +@click.group(cls=clicommon.AliasedGroup) +@clicommon.pass_db +def copp(_db): + """Show copp configuration""" + pass + + +@copp.group(invoke_without_command=True) +@click.pass_context +@clicommon.pass_db +def configuration(_db, ctx): + """Show copp configuration""" + + if ctx.invoked_subcommand is not None: + return + + header = ["TrapId", "Trap Group", "Action", "CBS", "CIR", "Meter Type", "Mode", "HW Status"] + + config_db = _db.cfgdb + + merged_group, merged_traps = merge_copp_config(config_db) + if not merged_group or not merged_traps: + return + + state_db = SonicV2Connector() + state_db.connect(state_db.STATE_DB) + + # Extract all trap_ids and trap_group from merged_traps + rows = [] + for trap, trap_data in merged_traps.items(): + trap_ids = trap_data.get("trap_ids", "").split(",") + trap_group = trap_data.get("trap_group", "") + action = merged_group[trap_group].get("trap_action", "") + cbs = merged_group[trap_group].get("cbs", "") + cir = merged_group[trap_group].get("cir", "") + meter_type = merged_group[trap_group].get("meter_type", "") + mode = merged_group[trap_group].get("mode", "") + + for trap_id in trap_ids: + trap_id = trap_id.strip() + hw_status = get_copp_trap_hw_status(trap_id, state_db) + rows.append([trap_id, trap_group, action, cbs, cir, meter_type, mode, hw_status]) + + body = natsorted(rows) + click.echo(tabulate(body, header)) + + +@configuration.command() +@click.option('-t', '--trapid', help="Trap id text") +@click.option('-g', '--group', help="Trap group text") +@clicommon.pass_db +def detailed(_db, trapid, group): + """Show copp configuration detailed""" + + # Validation to ensure at least one argument is provided + if not trapid and not group: + click.echo("Either trapid or group must be provided.") + return + + if trapid and group: + click.echo("Either trapid or group must be provided, but not both.") + return + + config_db = _db.cfgdb + merged_groups, merged_traps = merge_copp_config(config_db, trapid, group) + if not merged_groups or not merged_traps: + return + + if group: + copp_group = merged_groups.get(group, {}) + else: + for _, trap_data in merged_traps.items(): + trap_ids_list = trap_data.get("trap_ids", "").split(",") + if trapid in trap_ids_list: + group = trap_data.get("trap_group") + break + + copp_group = merged_groups.get(group, {}) + + if len(copp_group) == 0: + return + + print_single_copp_entry(copp_group, trapid, group) diff --git a/show/main.py b/show/main.py index 6efbee421d..b66fbd48b6 100755 --- a/show/main.py +++ b/show/main.py @@ -69,6 +69,7 @@ from . import bgp_cli from . import stp from . import srv6 +from . import copp # Global Variables PLATFORM_JSON = 'platform.json' @@ -322,6 +323,7 @@ def cli(ctx): cli.add_command(dns.dns) cli.add_command(stp.spanning_tree) cli.add_command(srv6.srv6) +cli.add_command(copp.copp) # syslog module cli.add_command(syslog.syslog) @@ -2057,6 +2059,17 @@ def spanning_tree(verbose): cmd = ['sudo', 'sonic-cfggen', '-d', '--var-json', key] run_command(cmd, display_cmd=verbose) + +# 'copp' subcommand ("show runningconfiguration copp") +@runningconfiguration.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def copp(verbose): + """Show copp running configuration""" + copp_list = ["COPP_GROUP", "COPP_TRAP"] + for key in copp_list: + cmd = ['sudo', 'sonic-cfggen', '-d', '--var-json', key] + run_command(cmd, display_cmd=verbose) + # # 'startupconfiguration' group ("show startupconfiguration ...") # diff --git a/tests/copp_input/mock_config/mock_copp_cfg.json b/tests/copp_input/mock_config/mock_copp_cfg.json new file mode 100644 index 0000000000..5499f9b019 --- /dev/null +++ b/tests/copp_input/mock_config/mock_copp_cfg.json @@ -0,0 +1,136 @@ +{ + "COPP_GROUP": { + "default": { + "queue": "0", + "meter_type":"packets", + "mode":"sr_tcm", + "cir":"600", + "cbs":"600", + "red_action":"drop" + }, + "queue4_group1": { + "trap_action":"trap", + "trap_priority":"4", + "queue": "4", + "meter_type":"packets", + "mode":"sr_tcm", + "cir":"6000", + "cbs":"6000", + "red_action":"drop" + }, + "queue4_group2": { + "trap_action":"copy", + "trap_priority":"4", + "queue": "4", + "meter_type":"packets", + "mode":"sr_tcm", + "cir":"600", + "cbs":"600", + "red_action":"drop" + }, + "queue4_group3": { + "trap_action":"trap", + "trap_priority":"4", + "queue": "4", + "meter_type":"packets", + "mode":"sr_tcm", + "cir":"100", + "cbs":"100", + "red_action":"drop" + }, + "queue1_group1": { + "trap_action":"trap", + "trap_priority":"1", + "queue": "1", + "meter_type":"packets", + "mode":"sr_tcm", + "cir":"6000", + "cbs":"6000", + "red_action":"drop" + }, + "queue1_group2": { + "trap_action":"trap", + "trap_priority":"1", + "queue": "1", + "meter_type":" packets", + "mode":"sr_tcm", + "cir":"600", + "cbs":"600", + "red_action":"drop" + }, + "queue1_group3": { + "trap_action":"trap", + "trap_priority":"1", + "queue": "1", + "meter_type":" packets", + "mode":"sr_tcm", + "cir":"200", + "cbs":"200", + "red_action":"drop" + }, + "queue2_group1": { + "cbs": "1000", + "cir": "1000", + "genetlink_mcgrp_name": "packets", + "genetlink_name": "psample", + "meter_type": "packets", + "mode": "sr_tcm", + "queue": "2", + "red_action": "drop", + "trap_action": "trap", + "trap_priority": "1" + + } + }, + "COPP_TRAP": { + "bgp": { + "trap_ids": "bgp, bgpv6", + "trap_group": "queue4_group1" + }, + "lacp": { + "trap_ids": "lacp", + "trap_group": "queue4_group1", + "always_enabled": "true" + }, + "arp": { + "trap_ids": "arp_req,arp_resp,neigh_discovery", + "trap_group": "queue4_group2", + "always_enabled": "true" + }, + "lldp": { + "trap_ids": "lldp", + "trap_group": "queue4_group3" + }, + "dhcp_relay": { + "trap_ids": "dhcp,dhcpv6", + "trap_group": "queue4_group3" + }, + "udld": { + "trap_ids": "udld", + "trap_group": "queue4_group3", + "always_enabled": "true" + }, + "ip2me": { + "trap_ids": "ip2me", + "trap_group": "queue1_group1", + "always_enabled": "true" + }, + "macsec": { + "trap_ids": "eapol", + "trap_group": "queue4_group1" + }, + "nat": { + "trap_ids": "src_nat_miss,dest_nat_miss", + "trap_group": "queue1_group2" + }, + "sflow": { + "trap_group": "queue2_group1", + "trap_ids": "sample_packet" + }, + "neighbor_miss": { + "trap_ids": "neighbor_miss", + "trap_group": "queue1_group3", + "always_enabled": "true" + } + } +} diff --git a/tests/copp_test.py b/tests/copp_test.py new file mode 100644 index 0000000000..c79584ded8 --- /dev/null +++ b/tests/copp_test.py @@ -0,0 +1,202 @@ +import pytest +import os +import logging +import show.main as show +from utilities_common.db import Db +from .mock_tables import dbconnector +from unittest.mock import mock_open, patch + +from click.testing import CliRunner + +test_path = os.path.dirname(os.path.abspath(__file__)) +input_path = os.path.join(test_path, "copp_input") +mock_config_path = os.path.join(input_path, "mock_config") + +mock_db_path = os.path.join(test_path, "mock_tables") + +logger = logging.getLogger(__name__) + +# show copp configuration +show_copp_expected_output = """\ +TrapId Trap Group Action CBS CIR Meter Type Mode HW Status +--------------- ------------- -------- ----- ----- ------------ ------ ------------- +arp_req queue4_group2 copy 600 600 packets sr_tcm not-installed +arp_resp queue4_group2 copy 600 600 packets sr_tcm not-installed +bgp queue4_group1 trap 2000 2000 packets sr_tcm installed +bgpv6 queue4_group1 trap 2000 2000 packets sr_tcm not-installed +dest_nat_miss queue1_group2 trap 600 600 packets sr_tcm not-installed +dhcp queue4_group3 trap 100 100 packets sr_tcm not-installed +dhcpv6 queue4_group3 trap 100 100 packets sr_tcm not-installed +eapol queue4_group1 trap 2000 2000 packets sr_tcm not-installed +ip2me queue1_group1 trap 6000 6000 packets sr_tcm not-installed +lacp queue4_group1 trap 2000 2000 packets sr_tcm not-installed +lldp queue4_group3 trap 100 100 packets sr_tcm not-installed +neigh_discovery queue4_group2 copy 600 600 packets sr_tcm not-installed +sample_packet queue2_group1 trap 1000 1000 packets sr_tcm not-installed +src_nat_miss queue1_group2 trap 600 600 packets sr_tcm not-installed +udld queue4_group3 trap 100 100 packets sr_tcm not-installed +""" + +# show copp configuration detailed --group queue4_group1 +show_copp_detailed_queue4_group1_expected_output = """\ +Trap Id(s).................. bgp,bgpv6,lacp,eapol +Trap Action................. trap +Trap Priority............... 4 +Queue....................... 4 +CBS......................... 2000 +CIR......................... 2000 +Meter Type.................. packets +Mode........................ sr_tcm +Yellow Action............... forward +Green Action................ forward +Red Action.................. drop +""" + +# show copp configuration detailed --group queue1_group3 +show_copp_detailed_queue1_group3_expected_output = "" + +# show copp configuration detailed --trapid bgp +show_copp_detailed_bgp_trap_expected_output = """\ +Trap Group.................. queue4_group1 +Trap Action................. trap +Trap Priority............... 4 +Queue....................... 4 +CBS......................... 2000 +CIR......................... 2000 +Meter Type.................. packets +Mode........................ sr_tcm +Yellow Action............... forward +Green Action................ forward +Red Action.................. drop +HW Status................... installed +""" + +# show copp configuration detailed --trapid neighbor_miss +show_copp_detailed_neighbor_miss_trap_expected_output = "" + +# show copp configuration detailed --group queue1_group3 --trapid neighbor_miss +show_copp_detailed_invalid_both_options_expected_output = """\ +Either trapid or group must be provided, but not both. +""" + +# show copp configuration detailed +show_copp_detailed_invalid_no_option_output = """\ +Either trapid or group must be provided. +""" + + +class TestCoPP: + @classmethod + def setup_class(cls): + logger.info("Setup class: {}".format(cls.__name__)) + os.environ['UTILITIES_UNIT_TESTING'] = "1" + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db') + dbconnector.dedicated_dbs['STATE_DB'] = os.path.join(mock_db_path, 'state_db') + + # Path to the mock copp_cfg.json file in the test directory + cls.mock_copp_cfg_path = os.path.join(mock_config_path, 'mock_copp_cfg.json') + + real_open = open + + # Read the content of the mock copp_cfg.json file + with open(cls.mock_copp_cfg_path, 'r') as f: + cls.mock_file_content = f.read() + + # logger.debug("Mock CoPP configuration file content: \n{}".format(cls.mock_file_content)) + + # Custom mock function to selectively mock file opens + def custom_open(file, *args, **kwargs): + if os.path.basename(file) == 'copp_cfg.json': + return mock_open(read_data=cls.mock_file_content)(file, *args, **kwargs) + else: + return real_open(file, *args, **kwargs) + + # Mock the open function to simulate the file content + cls.patcher = patch('builtins.open', custom_open) + cls.patcher.start() + + @classmethod + def teardown_class(cls): + logger.info("Teardown class: {}".format(cls.__name__)) + os.environ['UTILITIES_UNIT_TESTING'] = "0" + dbconnector.dedicated_dbs.clear() + cls.patcher.stop() + + # ---------- SHOW CoPP ---------- # + + def test_show_copp_configuration(self): + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands["copp"].commands["configuration"], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + + assert result.exit_code == 0 + assert result.output == show_copp_expected_output + + @pytest.mark.parametrize("trap_name", ["neighbor_miss", "bgp"]) + def test_show_copp_configuration_detailed_trap_id(self, trap_name): + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands["copp"].commands["configuration"].commands["detailed"], + ["--trapid", trap_name], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + + assert result.exit_code == 0 + if trap_name == "bgp": + assert result.output == show_copp_detailed_bgp_trap_expected_output + elif trap_name == "neighbor_miss": + assert result.output == show_copp_detailed_neighbor_miss_trap_expected_output + + @pytest.mark.parametrize("group_name", ["queue4_group1", "queue1_group3"]) + def test_show_copp_configuration_detailed_group(self, group_name): + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands["copp"].commands["configuration"].commands["detailed"], + ["--group", group_name], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + + assert result.exit_code == 0 + if group_name == "queue4_group1": + assert result.output == show_copp_detailed_queue4_group1_expected_output + elif group_name == "queue1_group3": + assert result.output == show_copp_detailed_queue1_group3_expected_output + + def test_show_copp_configuration_detailed_invalid(self): + db = Db() + runner = CliRunner() + + result = runner.invoke( + show.cli.commands["copp"].commands["configuration"].commands["detailed"], + ["--group", "queue1_group3", "--trapid", "neighbor_miss"], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + + assert result.exit_code == 0 + assert result.output == show_copp_detailed_invalid_both_options_expected_output + + result = runner.invoke( + show.cli.commands["copp"].commands["configuration"].commands["detailed"], obj=db + ) + + logger.debug("\n" + result.output) + logger.debug(result.exit_code) + + assert result.exit_code == 0 + assert result.output == show_copp_detailed_invalid_no_option_output diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index d715f28444..9716ed434d 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -2878,5 +2878,16 @@ "hello_time": "2", "max_age": "20", "priority": "32768" + }, + "COPP_GROUP|queue4_group1": { + "cir":"2000", + "cbs":"2000" + }, + "COPP_GROUP|queue1_group3": { + "NULL":"NULL" + }, + "COPP_TRAP|neighbor_miss": { + "NULL":"NULL" } + } diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index 093fa5ff00..6aa46e6516 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -1777,5 +1777,12 @@ "mac_remote_fault_time": "", "mac_local_fault_count": "0", "mac_local_fault_time": "" + }, + "COPP_TRAP_TABLE|bgp": { + "state": "ok", + "hw_status": "installed" + }, + "COPP_TRAP_TABLE|neighbor_miss": { + "state": "ok" } }