-
Notifications
You must be signed in to change notification settings - Fork 664
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[hash]: Implement GH frontend (#2580)
HLD sonic-net/SONiC#1101 - What I did Implemented CLI for Generic Hash feature - How I did it Integrated Generic Hash interface into config and show CLI root - How to verify it Run Generic Hash CLI UTs Signed-off-by: Nazarii Hnydyn <[email protected]>
- Loading branch information
Showing
16 changed files
with
1,055 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,273 @@ | ||
""" | ||
This CLI plugin was auto-generated by using 'sonic-cli-gen' utility | ||
""" | ||
|
||
import click | ||
import utilities_common.cli as clicommon | ||
|
||
from sonic_py_common import logger | ||
from utilities_common.switch_hash import ( | ||
CFG_SWITCH_HASH, | ||
STATE_SWITCH_CAPABILITY, | ||
SW_CAP_HASH_FIELD_LIST_KEY, | ||
SW_CAP_ECMP_HASH_KEY, | ||
SW_CAP_LAG_HASH_KEY, | ||
SW_HASH_KEY, | ||
SW_CAP_KEY, | ||
HASH_FIELD_LIST, | ||
SYSLOG_IDENTIFIER, | ||
get_param, | ||
get_param_hint, | ||
get_dupes, | ||
to_str, | ||
) | ||
|
||
|
||
log = logger.Logger(SYSLOG_IDENTIFIER) | ||
log.set_min_log_priority_info() | ||
|
||
# | ||
# Hash validators ----------------------------------------------------------------------------------------------------- | ||
# | ||
|
||
def hash_field_validator(ctx, param, value): | ||
""" | ||
Check if hash field list argument is valid | ||
Args: | ||
ctx: click context | ||
param: click parameter context | ||
value: value of parameter | ||
Returns: | ||
str: validated parameter | ||
""" | ||
|
||
for hash_field in value: | ||
click.Choice(HASH_FIELD_LIST).convert(hash_field, param, ctx) | ||
|
||
return list(value) | ||
|
||
|
||
def ecmp_hash_validator(ctx, db, ecmp_hash): | ||
""" | ||
Check if ECMP hash argument is valid | ||
Args: | ||
ctx: click context | ||
db: State DB connector object | ||
ecmp_hash: ECMP hash field list | ||
""" | ||
|
||
dup_list = get_dupes(ecmp_hash) | ||
if dup_list: | ||
raise click.UsageError("Invalid value for {}: {} has a duplicate hash field(s) {}".format( | ||
get_param_hint(ctx, "ecmp_hash"), to_str(ecmp_hash), to_str(dup_list)), ctx | ||
) | ||
|
||
entry = db.get_all(db.STATE_DB, "{}|{}".format(STATE_SWITCH_CAPABILITY, SW_CAP_KEY)) | ||
|
||
entry.setdefault(SW_CAP_HASH_FIELD_LIST_KEY, 'N/A') | ||
entry.setdefault(SW_CAP_ECMP_HASH_KEY, 'false') | ||
|
||
if entry[SW_CAP_ECMP_HASH_KEY] == 'false': | ||
raise click.UsageError("Failed to configure {}: operation is not supported".format( | ||
get_param_hint(ctx, "ecmp_hash")), ctx | ||
) | ||
|
||
if not entry[SW_CAP_HASH_FIELD_LIST_KEY]: | ||
raise click.UsageError("Failed to configure {}: no hash field capabilities".format( | ||
get_param_hint(ctx, "ecmp_hash")), ctx | ||
) | ||
|
||
if entry[SW_CAP_HASH_FIELD_LIST_KEY] == 'N/A': | ||
return | ||
|
||
cap_list = entry[SW_CAP_HASH_FIELD_LIST_KEY].split(',') | ||
|
||
for hash_field in ecmp_hash: | ||
click.Choice(cap_list).convert(hash_field, get_param(ctx, "ecmp_hash"), ctx) | ||
|
||
|
||
def lag_hash_validator(ctx, db, lag_hash): | ||
""" | ||
Check if LAG hash argument is valid | ||
Args: | ||
ctx: click context | ||
db: State DB connector object | ||
lag_hash: LAG hash field list | ||
""" | ||
|
||
dup_list = get_dupes(lag_hash) | ||
if dup_list: | ||
raise click.UsageError("Invalid value for {}: {} has a duplicate hash field(s) {}".format( | ||
get_param_hint(ctx, "lag_hash"), to_str(lag_hash), to_str(dup_list)), ctx | ||
) | ||
|
||
entry = db.get_all(db.STATE_DB, "{}|{}".format(STATE_SWITCH_CAPABILITY, SW_CAP_KEY)) | ||
|
||
entry.setdefault(SW_CAP_HASH_FIELD_LIST_KEY, 'N/A') | ||
entry.setdefault(SW_CAP_LAG_HASH_KEY, 'false') | ||
|
||
if entry[SW_CAP_LAG_HASH_KEY] == 'false': | ||
raise click.UsageError("Failed to configure {}: operation is not supported".format( | ||
get_param_hint(ctx, "lag_hash")), ctx | ||
) | ||
|
||
if not entry[SW_CAP_HASH_FIELD_LIST_KEY]: | ||
raise click.UsageError("Failed to configure {}: no hash field capabilities".format( | ||
get_param_hint(ctx, "lag_hash")), ctx | ||
) | ||
|
||
if entry[SW_CAP_HASH_FIELD_LIST_KEY] == 'N/A': | ||
return | ||
|
||
cap_list = entry[SW_CAP_HASH_FIELD_LIST_KEY].split(',') | ||
|
||
for hash_field in lag_hash: | ||
click.Choice(cap_list).convert(hash_field, get_param(ctx, "lag_hash"), ctx) | ||
|
||
# | ||
# Hash DB interface --------------------------------------------------------------------------------------------------- | ||
# | ||
|
||
def update_entry_validated(db, table, key, data, create_if_not_exists=False): | ||
""" Update entry in table and validate configuration. | ||
If attribute value in data is None, the attribute is deleted. | ||
Args: | ||
db (swsscommon.ConfigDBConnector): Config DB connector object. | ||
table (str): Table name to add new entry to. | ||
key (Union[str, Tuple]): Key name in the table. | ||
data (Dict): Entry data. | ||
create_if_not_exists (bool): | ||
In case entry does not exists already a new entry | ||
is not created if this flag is set to False and | ||
creates a new entry if flag is set to True. | ||
Raises: | ||
Exception: when cfg does not satisfy YANG schema. | ||
""" | ||
|
||
cfg = db.get_config() | ||
cfg.setdefault(table, {}) | ||
|
||
if not data: | ||
raise click.ClickException(f"No field/values to update {key}") | ||
|
||
if create_if_not_exists: | ||
cfg[table].setdefault(key, {}) | ||
|
||
if key not in cfg[table]: | ||
raise click.ClickException(f"{key} does not exist") | ||
|
||
entry_changed = False | ||
for attr, value in data.items(): | ||
if value == cfg[table][key].get(attr): | ||
continue | ||
entry_changed = True | ||
if value is None: | ||
cfg[table][key].pop(attr, None) | ||
else: | ||
cfg[table][key][attr] = value | ||
|
||
if not entry_changed: | ||
return | ||
|
||
db.set_entry(table, key, cfg[table][key]) | ||
|
||
# | ||
# Hash CLI ------------------------------------------------------------------------------------------------------------ | ||
# | ||
|
||
@click.group( | ||
name="switch-hash", | ||
cls=clicommon.AliasedGroup | ||
) | ||
def SWITCH_HASH(): | ||
""" Configure switch hash feature """ | ||
|
||
pass | ||
|
||
|
||
@SWITCH_HASH.group( | ||
name="global", | ||
cls=clicommon.AliasedGroup | ||
) | ||
def SWITCH_HASH_GLOBAL(): | ||
""" Configure switch hash global """ | ||
|
||
pass | ||
|
||
|
||
@SWITCH_HASH_GLOBAL.command( | ||
name="ecmp-hash" | ||
) | ||
@click.argument( | ||
"ecmp-hash", | ||
nargs=-1, | ||
required=True, | ||
callback=hash_field_validator, | ||
) | ||
@clicommon.pass_db | ||
@click.pass_context | ||
def SWITCH_HASH_GLOBAL_ecmp_hash(ctx, db, ecmp_hash): | ||
""" Hash fields for hashing packets going through ECMP """ | ||
|
||
ecmp_hash_validator(ctx, db.db, ecmp_hash) | ||
|
||
table = CFG_SWITCH_HASH | ||
key = SW_HASH_KEY | ||
data = { | ||
"ecmp_hash": ecmp_hash, | ||
} | ||
|
||
try: | ||
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) | ||
log.log_notice("Configured switch global ECMP hash: {}".format(to_str(ecmp_hash))) | ||
except Exception as e: | ||
log.log_error("Failed to configure switch global ECMP hash: {}".format(str(e))) | ||
ctx.fail(str(e)) | ||
|
||
|
||
@SWITCH_HASH_GLOBAL.command( | ||
name="lag-hash" | ||
) | ||
@click.argument( | ||
"lag-hash", | ||
nargs=-1, | ||
required=True, | ||
callback=hash_field_validator, | ||
) | ||
@clicommon.pass_db | ||
@click.pass_context | ||
def SWITCH_HASH_GLOBAL_lag_hash(ctx, db, lag_hash): | ||
""" Hash fields for hashing packets going through LAG """ | ||
|
||
lag_hash_validator(ctx, db.db, lag_hash) | ||
|
||
table = CFG_SWITCH_HASH | ||
key = SW_HASH_KEY | ||
data = { | ||
"lag_hash": lag_hash, | ||
} | ||
|
||
try: | ||
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True) | ||
log.log_notice("Configured switch global LAG hash: {}".format(to_str(lag_hash))) | ||
except Exception as err: | ||
log.log_error("Failed to configure switch global LAG hash: {}".format(str(err))) | ||
ctx.fail(str(err)) | ||
|
||
|
||
def register(cli): | ||
""" Register new CLI nodes in root CLI. | ||
Args: | ||
cli: Root CLI node. | ||
Raises: | ||
Exception: when root CLI already has a command | ||
we are trying to register. | ||
""" | ||
cli_node = SWITCH_HASH | ||
if cli_node.name in cli.commands: | ||
raise Exception(f"{cli_node.name} already exists in CLI") | ||
cli.add_command(SWITCH_HASH) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.