Skip to content

Commit

Permalink
[hash]: Implement GH frontend (#2580)
Browse files Browse the repository at this point in the history
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 <nazariig@nvidia.com>
  • Loading branch information
nazariig authored Jul 10, 2023
1 parent 61bad06 commit ff380e0
Show file tree
Hide file tree
Showing 16 changed files with 1,055 additions and 0 deletions.
273 changes: 273 additions & 0 deletions config/plugins/sonic-hash.py
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)
98 changes: 98 additions & 0 deletions doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
* [Flow Counters config commands](#flow-counters-config-commands)
* [Gearbox](#gearbox)
* [Gearbox show commands](#gearbox-show-commands)
* [Hash](#hash)
* [Hash show commands](#hash-show-commands)
* [Hash config commands](#hash-config-commands)
* [Interfaces](#interfaces)
* [Interface Show Commands](#interface-show-commands)
* [Interface Config Commands](#interface-config-commands)
Expand Down Expand Up @@ -4087,6 +4090,101 @@ This command is used to change device hostname without traffic being impacted.
Please note loaded setting will be lost after system reboot. To preserve setting, run `config save`.
```
## Hash
This section explains the various show and configuration commands available for user.
### Hash Show Commands
This subsection explains how to display switch hash configuration.
**show switch-hash global**
This command displays switch hash global configuration.
- Usage:
```bash
show switch-hash global
```

- Example:
```bash
admin@sonic:~$ show switch-hash global
ECMP HASH LAG HASH
----------------- -----------------
DST_MAC DST_MAC
SRC_MAC SRC_MAC
ETHERTYPE ETHERTYPE
IP_PROTOCOL IP_PROTOCOL
DST_IP DST_IP
SRC_IP SRC_IP
L4_DST_PORT L4_DST_PORT
L4_SRC_PORT L4_SRC_PORT
INNER_DST_MAC INNER_DST_MAC
INNER_SRC_MAC INNER_SRC_MAC
INNER_ETHERTYPE INNER_ETHERTYPE
INNER_IP_PROTOCOL INNER_IP_PROTOCOL
INNER_DST_IP INNER_DST_IP
INNER_SRC_IP INNER_SRC_IP
INNER_L4_DST_PORT INNER_L4_DST_PORT
INNER_L4_SRC_PORT INNER_L4_SRC_PORT
```

### Hash Config Commands

This subsection explains how to configure switch hash.

**config switch-hash global**

This command is used to manage switch hash global configuration.

- Usage:
```bash
config switch-hash global ecmp-hash <hash_field_list>
config switch-hash global lag-hash <hash_field_list>
```

- Parameters:
- _hash_field_list_: hash fields for hashing packets going through ECMP/LAG

- Examples:
```bash
admin@sonic:~$ config switch-hash global ecmp-hash \
'DST_MAC' \
'SRC_MAC' \
'ETHERTYPE' \
'IP_PROTOCOL' \
'DST_IP' \
'SRC_IP' \
'L4_DST_PORT' \
'L4_SRC_PORT' \
'INNER_DST_MAC' \
'INNER_SRC_MAC' \
'INNER_ETHERTYPE' \
'INNER_IP_PROTOCOL' \
'INNER_DST_IP' \
'INNER_SRC_IP' \
'INNER_L4_DST_PORT' \
'INNER_L4_SRC_PORT'
admin@sonic:~$ config switch-hash global lag-hash \
'DST_MAC' \
'SRC_MAC' \
'ETHERTYPE' \
'IP_PROTOCOL' \
'DST_IP' \
'SRC_IP' \
'L4_DST_PORT' \
'L4_SRC_PORT' \
'INNER_DST_MAC' \
'INNER_SRC_MAC' \
'INNER_ETHERTYPE' \
'INNER_IP_PROTOCOL' \
'INNER_DST_IP' \
'INNER_SRC_IP' \
'INNER_L4_DST_PORT' \
'INNER_L4_SRC_PORT'
```

## Interfaces

### Interface Show Commands
Expand Down
Loading

0 comments on commit ff380e0

Please sign in to comment.