Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[202405][wcmp]: Add W-ECMP CLI #3407

Open
wants to merge 1 commit into
base: 202405
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions config/bgp_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import click
import utilities_common.cli as clicommon

from sonic_py_common import logger
from utilities_common.bgp import (
CFG_BGP_DEVICE_GLOBAL,
BGP_DEVICE_GLOBAL_KEY,
SYSLOG_IDENTIFIER,
to_str,
)


log = logger.Logger(SYSLOG_IDENTIFIER)
log.set_min_log_priority_info()


#
# BGP 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])


#
# BGP handlers --------------------------------------------------------------------------------------------------------
#


def tsa_handler(ctx, db, state):
""" Handle config updates for Traffic-Shift-Away (TSA) feature """

table = CFG_BGP_DEVICE_GLOBAL
key = BGP_DEVICE_GLOBAL_KEY
data = {
"tsa_enabled": state,
}

try:
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
log.log_notice("Configured TSA state: {}".format(to_str(state)))
except Exception as e:
log.log_error("Failed to configure TSA state: {}".format(str(e)))
ctx.fail(str(e))


def wcmp_handler(ctx, db, state):
""" Handle config updates for Weighted-Cost Multi-Path (W-ECMP) feature """

table = CFG_BGP_DEVICE_GLOBAL
key = BGP_DEVICE_GLOBAL_KEY
data = {
"wcmp_enabled": state,
}

try:
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
log.log_notice("Configured W-ECMP state: {}".format(to_str(state)))
except Exception as e:
log.log_error("Failed to configure W-ECMP state: {}".format(str(e)))
ctx.fail(str(e))


#
# BGP device-global ---------------------------------------------------------------------------------------------------
#


@click.group(
name="device-global",
cls=clicommon.AliasedGroup
)
def DEVICE_GLOBAL():
""" Configure BGP device global state """

pass


#
# BGP device-global tsa -----------------------------------------------------------------------------------------------
#


@DEVICE_GLOBAL.group(
name="tsa",
cls=clicommon.AliasedGroup
)
def DEVICE_GLOBAL_TSA():
""" Configure Traffic-Shift-Away (TSA) feature """

pass


@DEVICE_GLOBAL_TSA.command(
name="enabled"
)
@clicommon.pass_db
@click.pass_context
def DEVICE_GLOBAL_TSA_ENABLED(ctx, db):
""" Enable Traffic-Shift-Away (TSA) feature """

tsa_handler(ctx, db, "true")


@DEVICE_GLOBAL_TSA.command(
name="disabled"
)
@clicommon.pass_db
@click.pass_context
def DEVICE_GLOBAL_TSA_DISABLED(ctx, db):
""" Disable Traffic-Shift-Away (TSA) feature """

tsa_handler(ctx, db, "false")


#
# BGP device-global w-ecmp --------------------------------------------------------------------------------------------
#


@DEVICE_GLOBAL.group(
name="w-ecmp",
cls=clicommon.AliasedGroup
)
def DEVICE_GLOBAL_WCMP():
""" Configure Weighted-Cost Multi-Path (W-ECMP) feature """

pass


@DEVICE_GLOBAL_WCMP.command(
name="enabled"
)
@clicommon.pass_db
@click.pass_context
def DEVICE_GLOBAL_WCMP_ENABLED(ctx, db):
""" Enable Weighted-Cost Multi-Path (W-ECMP) feature """

wcmp_handler(ctx, db, "true")


@DEVICE_GLOBAL_WCMP.command(
name="disabled"
)
@clicommon.pass_db
@click.pass_context
def DEVICE_GLOBAL_WCMP_DISABLED(ctx, db):
""" Disable Weighted-Cost Multi-Path (W-ECMP) feature """

wcmp_handler(ctx, db, "false")
5 changes: 5 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from . import syslog
from . import switchport
from . import dns
from . import bgp_cli


# mock masic APIs for unit test
Expand Down Expand Up @@ -4047,6 +4048,10 @@ def bgp():
"""BGP-related configuration tasks"""
pass


# BGP module extensions
config.commands['bgp'].add_command(bgp_cli.DEVICE_GLOBAL)

#
# 'shutdown' subgroup ('config bgp shutdown ...')
#
Expand Down
40 changes: 40 additions & 0 deletions doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2630,6 +2630,26 @@ When enabled, BGP will not advertise routes which aren't yet offloaded.
Disabled
```

**show bgp device-global**

This command displays BGP device global configuration.

- Usage:
```bash
show bgp device-global
```

- Options:
- _-j,--json_: display in JSON format

- Example:
```bash
admin@sonic:~$ show bgp device-global
TSA W-ECMP
------- -------
enabled enabled
```

Go Back To [Beginning of the document](#) or [Beginning of this section](#bgp)

### BGP config commands
Expand Down Expand Up @@ -2740,6 +2760,26 @@ Once enabled, BGP will not advertise routes which aren't yet offloaded.
admin@sonic:~$ sudo config suppress-fib-pending disabled
```

**config bgp device-global tsa/w-ecmp**

This command is used to manage BGP device global configuration.

Feature list:
1. TSA - Traffic-Shift-Away
2. W-ECMP - Weighted-Cost Multi-Path

- Usage:
```bash
config bgp device-global tsa <enabled|disabled>
config bgp device-global w-ecmp <enabled|disabled>
```

- Examples:
```bash
admin@sonic:~$ config bgp device-global tsa enabled
admin@sonic:~$ config bgp device-global w-ecmp enabled
```

Go Back To [Beginning of the document](#) or [Beginning of this section](#bgp)

## Console
Expand Down
128 changes: 128 additions & 0 deletions show/bgp_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import click
import tabulate
import json
import utilities_common.cli as clicommon

from utilities_common.bgp import (
CFG_BGP_DEVICE_GLOBAL,
BGP_DEVICE_GLOBAL_KEY,
to_str,
)


#
# BGP helpers ---------------------------------------------------------------------------------------------------------
#


def format_attr_value(entry, attr):
""" Helper that formats attribute to be presented in the table output.

Args:
entry (Dict[str, str]): CONFIG DB entry configuration.
attr (Dict): Attribute metadata.

Returns:
str: formatted attribute value.
"""

if attr["is-leaf-list"]:
value = entry.get(attr["name"], [])
return "\n".join(value) if value else "N/A"
return entry.get(attr["name"], "N/A")


#
# BGP CLI -------------------------------------------------------------------------------------------------------------
#


@click.group(
name="bgp",
cls=clicommon.AliasedGroup
)
def BGP():
""" Show BGP configuration """

pass


#
# BGP device-global ---------------------------------------------------------------------------------------------------
#


@BGP.command(
name="device-global"
)
@click.option(
"-j", "--json", "json_format",
help="Display in JSON format",
is_flag=True,
default=False
)
@clicommon.pass_db
@click.pass_context
def DEVICE_GLOBAL(ctx, db, json_format):
""" Show BGP device global state """

header = [
"TSA",
"W-ECMP",
]
body = []

table = db.cfgdb.get_table(CFG_BGP_DEVICE_GLOBAL)
entry = table.get(BGP_DEVICE_GLOBAL_KEY, {})

if not entry:
click.echo("No configuration is present in CONFIG DB")
ctx.exit(0)

if json_format:
json_dict = {
"tsa": to_str(
format_attr_value(
entry,
{
'name': 'tsa_enabled',
'is-leaf-list': False
}
)
),
"w-ecmp": to_str(
format_attr_value(
entry,
{
'name': 'wcmp_enabled',
'is-leaf-list': False
}
)
)
}
click.echo(json.dumps(json_dict, indent=4))
ctx.exit(0)

row = [
to_str(
format_attr_value(
entry,
{
'name': 'tsa_enabled',
'is-leaf-list': False
}
)
),
to_str(
format_attr_value(
entry,
{
'name': 'wcmp_enabled',
'is-leaf-list': False
}
)
)
]
body.append(row)

click.echo(tabulate.tabulate(body, header))
Loading
Loading