Skip to content

Commit 6829ded

Browse files
authored
Add W-ECMP CLI (sonic-net#3253)
* [wcmp]: Add WCMP CLI. Signed-off-by: Nazarii Hnydyn <[email protected]>
1 parent 4133ef5 commit 6829ded

File tree

13 files changed

+605
-0
lines changed

13 files changed

+605
-0
lines changed

config/bgp_cli.py

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import click
2+
import utilities_common.cli as clicommon
3+
4+
from sonic_py_common import logger
5+
from utilities_common.bgp import (
6+
CFG_BGP_DEVICE_GLOBAL,
7+
BGP_DEVICE_GLOBAL_KEY,
8+
SYSLOG_IDENTIFIER,
9+
to_str,
10+
)
11+
12+
13+
log = logger.Logger(SYSLOG_IDENTIFIER)
14+
log.set_min_log_priority_info()
15+
16+
17+
#
18+
# BGP DB interface ----------------------------------------------------------------------------------------------------
19+
#
20+
21+
22+
def update_entry_validated(db, table, key, data, create_if_not_exists=False):
23+
""" Update entry in table and validate configuration.
24+
If attribute value in data is None, the attribute is deleted.
25+
26+
Args:
27+
db (swsscommon.ConfigDBConnector): Config DB connector object.
28+
table (str): Table name to add new entry to.
29+
key (Union[str, Tuple]): Key name in the table.
30+
data (Dict): Entry data.
31+
create_if_not_exists (bool):
32+
In case entry does not exists already a new entry
33+
is not created if this flag is set to False and
34+
creates a new entry if flag is set to True.
35+
Raises:
36+
Exception: when cfg does not satisfy YANG schema.
37+
"""
38+
39+
cfg = db.get_config()
40+
cfg.setdefault(table, {})
41+
42+
if not data:
43+
raise click.ClickException(f"No field/values to update {key}")
44+
45+
if create_if_not_exists:
46+
cfg[table].setdefault(key, {})
47+
48+
if key not in cfg[table]:
49+
raise click.ClickException(f"{key} does not exist")
50+
51+
entry_changed = False
52+
for attr, value in data.items():
53+
if value == cfg[table][key].get(attr):
54+
continue
55+
entry_changed = True
56+
if value is None:
57+
cfg[table][key].pop(attr, None)
58+
else:
59+
cfg[table][key][attr] = value
60+
61+
if not entry_changed:
62+
return
63+
64+
db.set_entry(table, key, cfg[table][key])
65+
66+
67+
#
68+
# BGP handlers --------------------------------------------------------------------------------------------------------
69+
#
70+
71+
72+
def tsa_handler(ctx, db, state):
73+
""" Handle config updates for Traffic-Shift-Away (TSA) feature """
74+
75+
table = CFG_BGP_DEVICE_GLOBAL
76+
key = BGP_DEVICE_GLOBAL_KEY
77+
data = {
78+
"tsa_enabled": state,
79+
}
80+
81+
try:
82+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
83+
log.log_notice("Configured TSA state: {}".format(to_str(state)))
84+
except Exception as e:
85+
log.log_error("Failed to configure TSA state: {}".format(str(e)))
86+
ctx.fail(str(e))
87+
88+
89+
def wcmp_handler(ctx, db, state):
90+
""" Handle config updates for Weighted-Cost Multi-Path (W-ECMP) feature """
91+
92+
table = CFG_BGP_DEVICE_GLOBAL
93+
key = BGP_DEVICE_GLOBAL_KEY
94+
data = {
95+
"wcmp_enabled": state,
96+
}
97+
98+
try:
99+
update_entry_validated(db.cfgdb, table, key, data, create_if_not_exists=True)
100+
log.log_notice("Configured W-ECMP state: {}".format(to_str(state)))
101+
except Exception as e:
102+
log.log_error("Failed to configure W-ECMP state: {}".format(str(e)))
103+
ctx.fail(str(e))
104+
105+
106+
#
107+
# BGP device-global ---------------------------------------------------------------------------------------------------
108+
#
109+
110+
111+
@click.group(
112+
name="device-global",
113+
cls=clicommon.AliasedGroup
114+
)
115+
def DEVICE_GLOBAL():
116+
""" Configure BGP device global state """
117+
118+
pass
119+
120+
121+
#
122+
# BGP device-global tsa -----------------------------------------------------------------------------------------------
123+
#
124+
125+
126+
@DEVICE_GLOBAL.group(
127+
name="tsa",
128+
cls=clicommon.AliasedGroup
129+
)
130+
def DEVICE_GLOBAL_TSA():
131+
""" Configure Traffic-Shift-Away (TSA) feature """
132+
133+
pass
134+
135+
136+
@DEVICE_GLOBAL_TSA.command(
137+
name="enabled"
138+
)
139+
@clicommon.pass_db
140+
@click.pass_context
141+
def DEVICE_GLOBAL_TSA_ENABLED(ctx, db):
142+
""" Enable Traffic-Shift-Away (TSA) feature """
143+
144+
tsa_handler(ctx, db, "true")
145+
146+
147+
@DEVICE_GLOBAL_TSA.command(
148+
name="disabled"
149+
)
150+
@clicommon.pass_db
151+
@click.pass_context
152+
def DEVICE_GLOBAL_TSA_DISABLED(ctx, db):
153+
""" Disable Traffic-Shift-Away (TSA) feature """
154+
155+
tsa_handler(ctx, db, "false")
156+
157+
158+
#
159+
# BGP device-global w-ecmp --------------------------------------------------------------------------------------------
160+
#
161+
162+
163+
@DEVICE_GLOBAL.group(
164+
name="w-ecmp",
165+
cls=clicommon.AliasedGroup
166+
)
167+
def DEVICE_GLOBAL_WCMP():
168+
""" Configure Weighted-Cost Multi-Path (W-ECMP) feature """
169+
170+
pass
171+
172+
173+
@DEVICE_GLOBAL_WCMP.command(
174+
name="enabled"
175+
)
176+
@clicommon.pass_db
177+
@click.pass_context
178+
def DEVICE_GLOBAL_WCMP_ENABLED(ctx, db):
179+
""" Enable Weighted-Cost Multi-Path (W-ECMP) feature """
180+
181+
wcmp_handler(ctx, db, "true")
182+
183+
184+
@DEVICE_GLOBAL_WCMP.command(
185+
name="disabled"
186+
)
187+
@clicommon.pass_db
188+
@click.pass_context
189+
def DEVICE_GLOBAL_WCMP_DISABLED(ctx, db):
190+
""" Disable Weighted-Cost Multi-Path (W-ECMP) feature """
191+
192+
wcmp_handler(ctx, db, "false")

config/main.py

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from . import syslog
6262
from . import switchport
6363
from . import dns
64+
from . import bgp_cli
6465

6566

6667
# mock masic APIs for unit test
@@ -4047,6 +4048,10 @@ def bgp():
40474048
"""BGP-related configuration tasks"""
40484049
pass
40494050

4051+
4052+
# BGP module extensions
4053+
config.commands['bgp'].add_command(bgp_cli.DEVICE_GLOBAL)
4054+
40504055
#
40514056
# 'shutdown' subgroup ('config bgp shutdown ...')
40524057
#

doc/Command-Reference.md

+40
Original file line numberDiff line numberDiff line change
@@ -2630,6 +2630,26 @@ When enabled, BGP will not advertise routes which aren't yet offloaded.
26302630
Disabled
26312631
```
26322632
2633+
**show bgp device-global**
2634+
2635+
This command displays BGP device global configuration.
2636+
2637+
- Usage:
2638+
```bash
2639+
show bgp device-global
2640+
```
2641+
2642+
- Options:
2643+
- _-j,--json_: display in JSON format
2644+
2645+
- Example:
2646+
```bash
2647+
admin@sonic:~$ show bgp device-global
2648+
TSA W-ECMP
2649+
------- -------
2650+
enabled enabled
2651+
```
2652+
26332653
Go Back To [Beginning of the document](#) or [Beginning of this section](#bgp)
26342654

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

2763+
**config bgp device-global tsa/w-ecmp**
2764+
2765+
This command is used to manage BGP device global configuration.
2766+
2767+
Feature list:
2768+
1. TSA - Traffic-Shift-Away
2769+
2. W-ECMP - Weighted-Cost Multi-Path
2770+
2771+
- Usage:
2772+
```bash
2773+
config bgp device-global tsa <enabled|disabled>
2774+
config bgp device-global w-ecmp <enabled|disabled>
2775+
```
2776+
2777+
- Examples:
2778+
```bash
2779+
admin@sonic:~$ config bgp device-global tsa enabled
2780+
admin@sonic:~$ config bgp device-global w-ecmp enabled
2781+
```
2782+
27432783
Go Back To [Beginning of the document](#) or [Beginning of this section](#bgp)
27442784

27452785
## Console

show/bgp_cli.py

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import click
2+
import tabulate
3+
import json
4+
import utilities_common.cli as clicommon
5+
6+
from utilities_common.bgp import (
7+
CFG_BGP_DEVICE_GLOBAL,
8+
BGP_DEVICE_GLOBAL_KEY,
9+
to_str,
10+
)
11+
12+
13+
#
14+
# BGP helpers ---------------------------------------------------------------------------------------------------------
15+
#
16+
17+
18+
def format_attr_value(entry, attr):
19+
""" Helper that formats attribute to be presented in the table output.
20+
21+
Args:
22+
entry (Dict[str, str]): CONFIG DB entry configuration.
23+
attr (Dict): Attribute metadata.
24+
25+
Returns:
26+
str: formatted attribute value.
27+
"""
28+
29+
if attr["is-leaf-list"]:
30+
value = entry.get(attr["name"], [])
31+
return "\n".join(value) if value else "N/A"
32+
return entry.get(attr["name"], "N/A")
33+
34+
35+
#
36+
# BGP CLI -------------------------------------------------------------------------------------------------------------
37+
#
38+
39+
40+
@click.group(
41+
name="bgp",
42+
cls=clicommon.AliasedGroup
43+
)
44+
def BGP():
45+
""" Show BGP configuration """
46+
47+
pass
48+
49+
50+
#
51+
# BGP device-global ---------------------------------------------------------------------------------------------------
52+
#
53+
54+
55+
@BGP.command(
56+
name="device-global"
57+
)
58+
@click.option(
59+
"-j", "--json", "json_format",
60+
help="Display in JSON format",
61+
is_flag=True,
62+
default=False
63+
)
64+
@clicommon.pass_db
65+
@click.pass_context
66+
def DEVICE_GLOBAL(ctx, db, json_format):
67+
""" Show BGP device global state """
68+
69+
header = [
70+
"TSA",
71+
"W-ECMP",
72+
]
73+
body = []
74+
75+
table = db.cfgdb.get_table(CFG_BGP_DEVICE_GLOBAL)
76+
entry = table.get(BGP_DEVICE_GLOBAL_KEY, {})
77+
78+
if not entry:
79+
click.echo("No configuration is present in CONFIG DB")
80+
ctx.exit(0)
81+
82+
if json_format:
83+
json_dict = {
84+
"tsa": to_str(
85+
format_attr_value(
86+
entry,
87+
{
88+
'name': 'tsa_enabled',
89+
'is-leaf-list': False
90+
}
91+
)
92+
),
93+
"w-ecmp": to_str(
94+
format_attr_value(
95+
entry,
96+
{
97+
'name': 'wcmp_enabled',
98+
'is-leaf-list': False
99+
}
100+
)
101+
)
102+
}
103+
click.echo(json.dumps(json_dict, indent=4))
104+
ctx.exit(0)
105+
106+
row = [
107+
to_str(
108+
format_attr_value(
109+
entry,
110+
{
111+
'name': 'tsa_enabled',
112+
'is-leaf-list': False
113+
}
114+
)
115+
),
116+
to_str(
117+
format_attr_value(
118+
entry,
119+
{
120+
'name': 'wcmp_enabled',
121+
'is-leaf-list': False
122+
}
123+
)
124+
)
125+
]
126+
body.append(row)
127+
128+
click.echo(tabulate.tabulate(body, header))

0 commit comments

Comments
 (0)