Skip to content

Commit d836673

Browse files
committed
Added CoPP show configuration command
What I did: CoPP show commands: * show copp configuration * show copp configuration detailed --trapid <trap_id> * show copp configuration detailed --group <group> Added UT for the CLI commands. HLD: sonic-net/SONiC#1943 Signed-off-by: Ravi Minnikanti <[email protected]>
1 parent c5df741 commit d836673

File tree

7 files changed

+633
-1
lines changed

7 files changed

+633
-1
lines changed

dump/plugins/copp.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
"bfd": "SAI_HOSTIF_TRAP_TYPE_BFD",
4444
"bfdv6": "SAI_HOSTIF_TRAP_TYPE_BFDV6",
4545
"src_nat_miss": "SAI_HOSTIF_TRAP_TYPE_SNAT_MISS",
46-
"dest_nat_miss": "SAI_HOSTIF_TRAP_TYPE_DNAT_MISS"
46+
"dest_nat_miss": "SAI_HOSTIF_TRAP_TYPE_DNAT_MISS",
47+
"neighbor_miss": "SAI_HOSTIF_TRAP_TYPE_NEIGHBOR_MISS"
4748
}
4849

4950
CFG_COPP_TRAP_TABLE_NAME = "COPP_TRAP"

show/copp.py

+262
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
import click
2+
import json
3+
import utilities_common.cli as clicommon
4+
from swsscommon.swsscommon import SonicV2Connector
5+
from natsort import natsorted
6+
from tabulate import tabulate
7+
8+
COPP_INIT_CFG_JSON_FILE = "/etc/sonic/copp_cfg.json"
9+
10+
##############################################################################
11+
# CoPP show commands
12+
# show copp configuration
13+
# show copp configuration detailed --trapid <trap_id>
14+
# show copp configuration detailed --group <group>
15+
# ############################################################################
16+
17+
def get_copp_trap_hw_status(trap_id, state_db):
18+
"""Get CoPP Trap operational status"""
19+
20+
state_db_data = state_db.get_all(state_db.STATE_DB, f"COPP_TRAP_TABLE|{trap_id}")
21+
hw_status = state_db_data.get("hw_status", "not-installed") \
22+
if state_db_data else "not-installed"
23+
24+
return hw_status
25+
26+
def print_single_copp_entry(entry, trap_id=None, group=None):
27+
"""Print single copp entry"""
28+
29+
if not trap_id:
30+
click.echo("Trap Id(s).................. {}".format(",".join(entry.get("trap_ids", {}))))
31+
else:
32+
click.echo("Trap Group.................. {}".format(group))
33+
34+
click.echo("Trap Action................. {}".format(entry.get("trap_action", "")))
35+
click.echo("Trap Priority............... {}".format(entry.get("trap_priority", "")))
36+
click.echo("Queue....................... {}".format(entry.get("queue", "")))
37+
click.echo("CBS......................... {}".format(entry.get("cbs", "")))
38+
click.echo("CIR......................... {}".format(entry.get("cir", "")))
39+
click.echo("Meter Type.................. {}".format(entry.get("meter_type", "")))
40+
mode = entry.get("mode", "")
41+
click.echo("Mode........................ {}".format(mode))
42+
43+
if mode in ['sr_tcm', 'tr_tcm']:
44+
click.echo("Yellow Action............... {}".format(entry.get("yellow_action", "forward")))
45+
46+
click.echo("Green Action................ {}".format(entry.get("green_action", "forward")))
47+
click.echo("Red Action.................. {}".format(entry.get("red_action", "")))
48+
49+
if trap_id:
50+
state_db = SonicV2Connector()
51+
state_db.connect(state_db.STATE_DB)
52+
hw_status = get_copp_trap_hw_status(trap_id, state_db)
53+
click.echo("HW Status................... {}".format(hw_status))
54+
55+
def merge_copp_entries(config_db, json_data, db_keys, table_name, input_key=None, is_group=False):
56+
"""
57+
Merge CoPP entries (groups or traps) from CONFIG_DB and copp_cfg.json.
58+
59+
Args:
60+
config_db: CONFIG_DB connector.
61+
json_data: JSON data from copp_cfg.json.
62+
db_keys: List of keys from CONFIG_DB.
63+
table_name: Key in the JSON data (e.g., "COPP_GROUP" or "COPP_TRAP").
64+
input_key: Specific key to filter (e.g., input_group or input_trap).
65+
is_group: Boolean indicating whether the entry is a group (True) or a trap (False).
66+
67+
Returns:
68+
A dictionary of merged entries.
69+
"""
70+
71+
merged_entries = {}
72+
json_entries = json_data.get(table_name, {})
73+
74+
# Merge entries from copp_cfg.json
75+
for json_entry, json_entry_data in json_entries.items():
76+
77+
# Strip leading and trailing spaces from the entry key and its nested fields
78+
json_entry = json_entry.strip()
79+
json_entry_data = {k.strip(): v.strip() for k, v in json_entry_data.items()}
80+
81+
# For groups, filter by input_key (input_group)
82+
if is_group and input_key and json_entry != input_key:
83+
continue
84+
85+
# For traps, skip if input_key (input_trap) is not in trap_ids
86+
if not is_group and input_key:
87+
trap_ids = json_entry_data.get("trap_ids", "").split(",")
88+
if not any(trap_id.strip() == input_key for trap_id in trap_ids):
89+
continue
90+
91+
# Ignore entries with "NULL" keys
92+
if "NULL" in json_entry_data:
93+
continue
94+
95+
merged_entries[json_entry] = json_entry_data
96+
97+
if json_entry in db_keys:
98+
db_entry = config_db.get_all(config_db.CONFIG_DB, f'{table_name}|{json_entry}')
99+
if "NULL" in db_entry:
100+
del merged_entries[json_entry]
101+
continue
102+
for json_field in json_entry_data:
103+
if json_field in db_entry:
104+
merged_entries[json_entry][json_field] = db_entry[json_field]
105+
106+
# Add keys from db_entry that are not in json_entry_data
107+
merged_entries[json_entry].update({db_field: db_entry[db_field]
108+
for db_field in db_entry if db_field not in merged_entries[json_entry]})
109+
110+
# Include entries from CONFIG_DB that are not in copp_cfg.json
111+
for db_entry_key in db_keys:
112+
# For groups, filter by input_key (input_group)
113+
if is_group and input_key and db_entry_key != input_key:
114+
continue
115+
116+
# For traps, skip if input_key (input_trap) is not in trap_ids
117+
if not is_group and input_key:
118+
db_entry = config_db.get_all(config_db.CONFIG_DB, f'{table_name}|{db_entry_key}')
119+
if input_key not in db_entry.get("trap_ids", "").split(","):
120+
continue
121+
122+
if db_entry_key not in merged_entries:
123+
db_entry = config_db.get_all(config_db.CONFIG_DB, f'{table_name}|{db_entry_key}')
124+
if "NULL" not in db_entry:
125+
merged_entries[db_entry_key] = db_entry
126+
127+
return merged_entries
128+
129+
def merge_copp_config(config_db, input_trap=None, input_group=None):
130+
"""Merge copp configuration"""
131+
132+
copp_group_table = f"COPP_GROUP|{input_group}" if input_group else "COPP_GROUP|*"
133+
copp_trap_table = "COPP_TRAP|*"
134+
135+
cfg_db_grp_keys = config_db.keys(config_db.CONFIG_DB, copp_group_table) or []
136+
cfg_db_trap_keys = config_db.keys(config_db.CONFIG_DB, copp_trap_table) or []
137+
138+
# Remove 'COPP_GROUP|' and 'COPP_TRAP|' from the keys
139+
cfg_db_grp_keys = [key.replace('COPP_GROUP|', '') for key in cfg_db_grp_keys]
140+
cfg_db_trap_keys = [key.replace('COPP_TRAP|', '') for key in cfg_db_trap_keys]
141+
142+
json_data = {}
143+
try:
144+
# Read the JSON data from the file
145+
with open(COPP_INIT_CFG_JSON_FILE, 'r') as file:
146+
json_data = json.load(file)
147+
except FileNotFoundError:
148+
click.echo(f"WARNING: CoPP CFG file: '{COPP_INIT_CFG_JSON_FILE}' not found.")
149+
150+
# Merge CoPP groups and traps
151+
merged_group = merge_copp_entries(config_db,
152+
json_data,
153+
cfg_db_grp_keys,
154+
"COPP_GROUP",
155+
input_group,
156+
is_group=True)
157+
merged_traps = merge_copp_entries(config_db,
158+
json_data,
159+
cfg_db_trap_keys,
160+
"COPP_TRAP",
161+
input_trap,
162+
is_group=False)
163+
164+
# Merge trap_ids from merged_traps to merged_group
165+
for trap_key, trap_data in merged_traps.items():
166+
trap_ids_list = trap_data.get("trap_ids", "").split(",")
167+
trap_ids_list = [trap_id.strip() for trap_id in trap_ids_list if trap_id.strip()]
168+
trap_group = trap_data.get("trap_group", "")
169+
170+
# Make sure the trap_group exists in merged_group
171+
if trap_group not in merged_group:
172+
continue
173+
174+
# Add trap_ids to merged_group[trap_group]
175+
merged_group[trap_group].setdefault("trap_ids", []).extend(trap_ids_list)
176+
177+
return merged_group, merged_traps
178+
179+
@click.group(cls=clicommon.AliasedGroup)
180+
@clicommon.pass_db
181+
def copp(_db):
182+
"""Show copp configuration"""
183+
pass
184+
185+
@copp.group(invoke_without_command=True)
186+
@click.pass_context
187+
@clicommon.pass_db
188+
def configuration(_db, ctx):
189+
"""Show copp configuration"""
190+
191+
if ctx.invoked_subcommand is not None:
192+
return
193+
194+
header = ["TrapId", "Trap Group", "Action", "CBS", "CIR", "Meter Type", "Mode", "HW Status"]
195+
196+
config_db = _db.cfgdb
197+
198+
merged_group, merged_traps = merge_copp_config(config_db)
199+
if not merged_group or not merged_traps:
200+
return
201+
202+
state_db = SonicV2Connector()
203+
state_db.connect(state_db.STATE_DB)
204+
205+
# Extract all trap_ids and trap_group from merged_traps
206+
rows = []
207+
for trap, trap_data in merged_traps.items():
208+
trap_ids = trap_data.get("trap_ids", "").split(",")
209+
trap_group = trap_data.get("trap_group", "")
210+
action = merged_group[trap_group].get("trap_action", "")
211+
cbs = merged_group[trap_group].get("cbs", "")
212+
cir = merged_group[trap_group].get("cir", "")
213+
meter_type = merged_group[trap_group].get("meter_type", "")
214+
mode = merged_group[trap_group].get("mode", "")
215+
216+
for trap_id in trap_ids:
217+
trap_id = trap_id.strip()
218+
hw_status = get_copp_trap_hw_status(trap_id, state_db)
219+
rows.append([trap_id, trap_group, action, cbs, cir, meter_type, mode, hw_status])
220+
221+
body = natsorted(rows)
222+
click.echo(tabulate(body, header))
223+
224+
@configuration.command()
225+
@click.option('-t', '--trapid', help="Trap id text")
226+
@click.option('-g', '--group', help="Trap group text")
227+
@clicommon.pass_db
228+
def detailed(_db, trapid, group):
229+
"""Show copp configuration detailed"""
230+
231+
# Validation to ensure at least one argument is provided
232+
if not trapid and not group:
233+
click.echo("Either trapid or group must be provided.")
234+
return
235+
236+
if trapid and group:
237+
click.echo("Either trapid or group must be provided, but not both.")
238+
return
239+
240+
config_db = _db.cfgdb
241+
merged_groups, merged_traps = merge_copp_config(config_db, trapid, group)
242+
if not merged_groups or not merged_traps:
243+
return
244+
245+
if group:
246+
copp_group = merged_groups.get(group, {})
247+
else:
248+
trap_found = False
249+
for _, trap_data in merged_traps.items():
250+
trap_ids_list = trap_data.get("trap_ids", "").split(",")
251+
if trapid in trap_ids_list:
252+
trap_found = True
253+
group = trap_data.get("trap_group")
254+
break
255+
256+
copp_group = merged_groups.get(group, {})
257+
258+
if len(copp_group) == 0:
259+
return
260+
261+
print_single_copp_entry(copp_group, trapid, group)
262+

show/main.py

+13
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
from . import bgp_cli
7070
from . import stp
7171
from . import srv6
72+
from . import copp
7273

7374
# Global Variables
7475
PLATFORM_JSON = 'platform.json'
@@ -322,6 +323,7 @@ def cli(ctx):
322323
cli.add_command(dns.dns)
323324
cli.add_command(stp.spanning_tree)
324325
cli.add_command(srv6.srv6)
326+
cli.add_command(copp.copp)
325327

326328
# syslog module
327329
cli.add_command(syslog.syslog)
@@ -2037,6 +2039,17 @@ def spanning_tree(verbose):
20372039
cmd = ['sudo', 'sonic-cfggen', '-d', '--var-json', key]
20382040
run_command(cmd, display_cmd=verbose)
20392041

2042+
2043+
# 'copp' subcommand ("show runningconfiguration copp")
2044+
@runningconfiguration.command()
2045+
@click.option('--verbose', is_flag=True, help="Enable verbose output")
2046+
def copp(verbose):
2047+
"""Show copp running configuration"""
2048+
copp_list = ["COPP_GROUP", "COPP_TRAP"]
2049+
for key in copp_list:
2050+
cmd = ['sudo', 'sonic-cfggen', '-d', '--var-json', key]
2051+
run_command(cmd, display_cmd=verbose)
2052+
20402053
#
20412054
# 'startupconfiguration' group ("show startupconfiguration ...")
20422055
#

0 commit comments

Comments
 (0)