Skip to content

Commit c390229

Browse files
committed
[trim]: Add Packet Trimming CLI
Signed-off-by: Nazarii Hnydyn <[email protected]> <!-- Please make sure you've read and understood our contributing guidelines: https://github.com/Azure/SONiC/blob/gh-pages/CONTRIBUTING.md ** Make sure all your commits include a signature generated with `git commit -s` ** If this is a bug fix, make sure your description includes "closes #xxxx", "fixes #xxxx" or "resolves #xxxx" so that GitHub automatically closes the related issue when the PR is merged. If you are adding/modifying/removing any command or utility script, please also make sure to add/modify/remove any unit tests from the tests directory as appropriate. If you are modifying or removing an existing 'show', 'config' or 'sonic-clear' subcommand, or you are adding a new subcommand, please make sure you also update the Command Line Reference Guide (doc/Command-Reference.md) to reflect your changes. Please provide the following information: --> **DEPENDS:** 1. sonic-net/sonic-swss-common#1001 **HLD:** sonic-net/SONiC#1898 #### What I did * Implemented CLI for Packet Trimming feature #### How I did it * Integrated Packet Trimming interface into `config` and `show` CLI root #### How to verify it 1. Run Packet Trimming CLI UTs #### Previous command output (if the output of a command-line utility has changed) * N/A #### New command output (if the output of a command-line utility has changed) * N/A #### A picture of a cute animal (not mandatory but encouraged) ``` .---. .----------- / \ __ / ------ / / \( )/ ----- ////// ' \/ ` --- //// / // : : --- // / / /` '-- // //..\\ ====UU====UU==== '//||\\` ''`` ```
1 parent ba955a9 commit c390229

33 files changed

+3580
-235
lines changed

config/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7456,6 +7456,7 @@ def ecn(profile, rmax, rmin, ymax, ymin, gmax, gmin, rdrop, ydrop, gdrop, verbos
74567456
@click.option('-p', metavar='<profile_name>', type=str, required=True, help="Profile name")
74577457
@click.option('-a', metavar='<alpha>', type=click.IntRange(-8,8), help="Set alpha for profile type dynamic")
74587458
@click.option('-s', metavar='<staticth>', type=click.IntRange(min=0), help="Set staticth for profile type static")
7459+
@click.option('-t', type=click.Choice(["on", "off"]), help="Set packet trimming eligibility")
74597460
@click.option('--verbose', '-vv', is_flag=True, help="Enable verbose output")
74607461
@click.option('--namespace',
74617462
'-n',
@@ -7465,12 +7466,14 @@ def ecn(profile, rmax, rmin, ymax, ymin, gmax, gmin, rdrop, ydrop, gdrop, verbos
74657466
show_default=True,
74667467
help='Namespace name or all',
74677468
callback=multi_asic_util.multi_asic_namespace_validation_callback)
7468-
def mmu(p, a, s, namespace, verbose):
7469+
def mmu(p, a, s, t, namespace, verbose):
74697470
"""mmuconfig configuration tasks"""
74707471
log.log_info("'mmuconfig -p {}' executing...".format(p))
74717472
command = ['mmuconfig', '-p', str(p)]
74727473
if a is not None: command += ['-a', str(a)]
74737474
if s is not None: command += ['-s', str(s)]
7475+
if t is not None:
7476+
command += ['-t', str(t)]
74747477
if namespace is not None:
74757478
command += ['-n', str(namespace)]
74767479
if verbose:

config/plugins/sonic-trimming.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
"""
2+
This CLI plugin was auto-generated by using 'sonic-cli-gen' utility
3+
"""
4+
5+
import click
6+
import utilities_common.cli as clicommon
7+
8+
from sonic_py_common import logger
9+
from utilities_common.switch_trimming import (
10+
CFG_SWITCH_TRIMMING,
11+
STATE_SWITCH_CAPABILITY,
12+
STATE_CAP_TRIMMING_CAPABLE_KEY,
13+
STATE_CAP_QUEUE_MODE_KEY,
14+
STATE_CAP_QUEUE_MODE_DYNAMIC,
15+
STATE_CAP_QUEUE_MODE_STATIC,
16+
CFG_TRIM_QUEUE_INDEX_DYNAMIC,
17+
CFG_TRIM_KEY,
18+
STATE_CAP_KEY,
19+
UINT32_MAX,
20+
UINT8_MAX,
21+
SYSLOG_IDENTIFIER,
22+
get_db,
23+
to_str,
24+
)
25+
26+
27+
log = logger.Logger(SYSLOG_IDENTIFIER)
28+
log.set_min_log_priority_info()
29+
30+
31+
#
32+
# Validators ----------------------------------------------------------------------------------------------------------
33+
#
34+
35+
36+
class SizeTypeValidator(click.ParamType):
37+
""" Size option validator """
38+
name = "integer"
39+
40+
def convert(self, value, param, ctx):
41+
click.IntRange(0, UINT32_MAX).convert(value, param, ctx)
42+
return value
43+
44+
45+
class DscpTypeValidator(click.ParamType):
46+
""" Dscp option validator """
47+
name = "integer"
48+
49+
def convert(self, value, param, ctx):
50+
click.IntRange(0, UINT8_MAX).convert(value, param, ctx)
51+
return value
52+
53+
54+
class QueueTypeValidator(click.ParamType):
55+
""" Queue index option validator """
56+
name = "text"
57+
58+
def get_metavar(self, param):
59+
db = get_db(click.get_current_context())
60+
61+
entry = db.get_all(db.STATE_DB, "{}|{}".format(STATE_SWITCH_CAPABILITY, STATE_CAP_KEY))
62+
entry.setdefault(STATE_CAP_QUEUE_MODE_KEY, "N/A")
63+
64+
cap_list = entry[STATE_CAP_QUEUE_MODE_KEY].split(',')
65+
66+
if cap_list.count(STATE_CAP_QUEUE_MODE_DYNAMIC) == len(cap_list):
67+
return "dynamic"
68+
elif cap_list.count(STATE_CAP_QUEUE_MODE_STATIC) == len(cap_list):
69+
return "INTEGER"
70+
71+
return "[INTEGER|dynamic]"
72+
73+
def convert(self, value, param, ctx):
74+
db = get_db(ctx)
75+
76+
entry = db.get_all(db.STATE_DB, "{}|{}".format(STATE_SWITCH_CAPABILITY, STATE_CAP_KEY))
77+
78+
entry.setdefault(STATE_CAP_TRIMMING_CAPABLE_KEY, "false")
79+
entry.setdefault(STATE_CAP_QUEUE_MODE_KEY, "N/A")
80+
81+
if entry[STATE_CAP_TRIMMING_CAPABLE_KEY] == "false":
82+
raise click.UsageError("Failed to configure {}: operation is not supported".format(
83+
param.get_error_hint(ctx)), ctx
84+
)
85+
86+
if not entry[STATE_CAP_QUEUE_MODE_KEY]:
87+
raise click.UsageError("Failed to configure {}: no queue resolution mode capabilities".format(
88+
param.get_error_hint(ctx)), ctx
89+
)
90+
91+
verify_cap = True
92+
93+
if entry[STATE_CAP_QUEUE_MODE_KEY] == "N/A":
94+
verify_cap = False
95+
96+
cap_list = entry[STATE_CAP_QUEUE_MODE_KEY].split(',')
97+
98+
if value == CFG_TRIM_QUEUE_INDEX_DYNAMIC:
99+
if verify_cap and (STATE_CAP_QUEUE_MODE_DYNAMIC not in cap_list):
100+
self.fail("dynamic queue resolution mode is not supported", param, ctx)
101+
else:
102+
if verify_cap and (STATE_CAP_QUEUE_MODE_STATIC not in cap_list):
103+
self.fail("static queue resolution mode is not supported", param, ctx)
104+
105+
click.IntRange(0, UINT8_MAX).convert(value, param, ctx)
106+
107+
return value
108+
109+
110+
#
111+
# DB interface --------------------------------------------------------------------------------------------------------
112+
#
113+
114+
115+
def update_entry_validated(db, table, key, data, create_if_not_exists=False):
116+
""" Update entry in table and validate configuration.
117+
If attribute value in data is None, the attribute is deleted.
118+
119+
Args:
120+
db (swsscommon.ConfigDBConnector): Config DB connector object.
121+
table (str): Table name to add new entry to.
122+
key (Union[str, Tuple]): Key name in the table.
123+
data (Dict): Entry data.
124+
create_if_not_exists (bool):
125+
In case entry does not exists already a new entry
126+
is not created if this flag is set to False and
127+
creates a new entry if flag is set to True.
128+
Raises:
129+
Exception: when cfg does not satisfy YANG schema.
130+
"""
131+
132+
cfg = db.get_config()
133+
cfg.setdefault(table, {})
134+
135+
if not data:
136+
raise click.ClickException(f"No field/values to update {key}")
137+
138+
if create_if_not_exists:
139+
cfg[table].setdefault(key, {})
140+
141+
if key not in cfg[table]:
142+
raise click.ClickException(f"{key} does not exist")
143+
144+
entry_changed = False
145+
for attr, value in data.items():
146+
if value == cfg[table][key].get(attr):
147+
continue
148+
if value is not None:
149+
cfg[table][key][attr] = value
150+
entry_changed = True
151+
152+
if not entry_changed:
153+
return
154+
155+
db.set_entry(table, key, cfg[table][key])
156+
157+
158+
#
159+
# CLI -----------------------------------------------------------------------------------------------------------------
160+
#
161+
162+
163+
@click.group(
164+
name="switch-trimming",
165+
cls=clicommon.AliasedGroup
166+
)
167+
def SWITCH_TRIMMING():
168+
""" Configure switch trimming feature """
169+
170+
pass
171+
172+
173+
@SWITCH_TRIMMING.command(
174+
name="global"
175+
)
176+
@click.option(
177+
"-s", "--size", "size",
178+
help="Configures size (in bytes) to trim eligible packet",
179+
type=SizeTypeValidator(),
180+
)
181+
@click.option(
182+
"-d", "--dscp", "dscp",
183+
help="Configures DSCP value assigned to a packet after trimming",
184+
type=DscpTypeValidator(),
185+
)
186+
@click.option(
187+
"-q", "--queue", "queue",
188+
help="Configures queue index to use for transmission of a packet after trimming",
189+
type=QueueTypeValidator(),
190+
)
191+
@clicommon.pass_db
192+
@click.pass_context
193+
def SWITCH_TRIMMING_GLOBAL(ctx, db, size, dscp, queue):
194+
""" Configure switch trimming global """
195+
196+
if not (size or dscp or queue):
197+
raise click.UsageError("Failed to configure switch trimming global: no options are provided", ctx)
198+
199+
table = CFG_SWITCH_TRIMMING
200+
key = CFG_TRIM_KEY
201+
202+
data = {
203+
"size": size,
204+
"dscp_value": dscp,
205+
"queue_index": queue,
206+
}
207+
208+
try:
209+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
210+
log.log_notice("Configured switch trimming global: {}".format(to_str(data)))
211+
except Exception as e:
212+
log.log_error("Failed to configure switch trimming global: {}".format(str(e)))
213+
ctx.fail(str(e))
214+
215+
216+
def register(cli):
217+
""" Register new CLI nodes in root CLI.
218+
219+
Args:
220+
cli: Root CLI node.
221+
Raises:
222+
Exception: when root CLI already has a command
223+
we are trying to register.
224+
"""
225+
cli_node = SWITCH_TRIMMING
226+
if cli_node.name in cli.commands:
227+
raise Exception(f"{cli_node.name} already exists in CLI")
228+
cli.add_command(SWITCH_TRIMMING)

0 commit comments

Comments
 (0)