Skip to content

Commit 17f60eb

Browse files
authored
[EVPN-MH] Add CLI commands for EVPN VXLAN Multihoming configuration (#380)
#### Why I did it This PR adds CLI commands to configure and monitor EVPN VXLAN Multihoming (EVPN-MH) feature in SONiC. These commands enable network operators to: - Configure global EVPN-MH parameters (startup delay, MAC/neighbor hold times) - Configure EVPN Ethernet Segments on LAG/PortChannel interfaces - Set Designated Forwarder (DF) preference for load distribution - Display EVPN ES, ES-EVI, and L2 nexthop information This complements the EVPN-MH infrastructure added in sonic-swss and sonic-swss-common. ##### Work item tracking - Microsoft ADO **(number only)**: #### How I did it **Global EVPN-MH Configuration** (evpn_mh.py): 1. **`config evpn-mh startup-delay <0-3600>`** - Configure EVPN-MH startup delay (default: 300s) - Determines how long to wait before declaring local Ethernet Segments as up after system boot - Prevents premature traffic forwarding during initialization - Valid range: 0-3600 seconds 2. **`config evpn-mh mac-holdtime <0-86400>`** - Configure MAC hold time (default: 1080s) - Duration to retain remote MAC entries after EVPN route withdrawal - Allows for graceful handling of temporary connectivity issues - Valid range: 0-86400 seconds 3. **`config evpn-mh neigh-holdtime <0-86400>`** - Configure neighbor hold time (default: 1080s) - Duration to retain neighbor entries after EVPN route withdrawal - Valid range: 0-86400 seconds **Interface-level EVPN-ES Configuration** (main.py): 4. **`config interface evpn-esi add <interface> <esi>`** - Add EVPN Ethernet Segment Identifier - Supports two ESI types: - **Type 0 (Manual)**: `00:XX:XX:XX:XX:XX:XX:XX:XX:XX` - Operator-configured 10-byte ESI - First byte must be 0x00 (Type 0) - Validates against reserved ESIs (all zeros, all FFs) - Prevents duplicate manual ESI across interfaces - **Type 3 (MAC-based)**: `auto-system-mac` - Auto-generated from system MAC - Uses port ID derived from interface name - Leverages `system_mac` from PORTCHANNEL table if configured - Integrates with FRR via vtysh commands - Stores configuration in CONFIG_DB `EVPN_ETHERNET_SEGMENT` table 5. **`config interface evpn-esi del <interface>`** - Remove EVPN Ethernet Segment configuration - Removes from CONFIG_DB - Cleans up FRR configuration (es-id, es-sys-mac, es-df-pref) 6. **`config interface evpn-df-pref <interface> <1-65535>`** - Set DF preference - Controls Designated Forwarder election priority (higher value = higher preference) - Default: 32767 - Valid range: 1-65535 - Updates both CONFIG_DB and FRR configuration **Show Commands** (evpn.py): 7. **`show evpn`** - Display general EVPN information 8. **`show evpn es [<esi>]`** - Display Ethernet Segment information - Optional ESI parameter to show specific ES details 9. **`show evpn es-evi [<vni>]`** - Display Ethernet Segment per EVI information - Optional VNI parameter to filter by specific VLAN/VNI 10. **`show evpn es-evi detail`** - Show detailed ES-EVI information 11. **`show evpn l2-nh`** - Display L2 nexthop groups for EVPN all-active ES All show commands leverage FRR's `show evpn` commands via `bgp_util.run_bgp_show_command()`. **Testing** (210+ test cases): - config_evpn_mh_test.py - Global EVPN-MH configuration tests - Startup delay validation (boundary values, valid/invalid inputs) - MAC holdtime validation - Neighbor holdtime validation - config_int_evpn_test.py - Interface EVPN-ES configuration tests - Manual ESI configuration (Type 0) - MAC-based ESI configuration (Type 3) - ESI validation (reserved ESI rejection, format validation, duplicate detection) - DF preference configuration - Configuration add/delete operations - Error handling and edge cases **Helper Functions**: - `is_reserved_esi()` - Validates against reserved ESI values - `parse_esi_input()` - Parses and validates ESI input (Type 0 vs Type 3) - `port_id_from_if_name()` - Extracts numeric port ID from interface name - `check_if_same_manual_esi_exists()` - Prevents duplicate manual ESI configuration - `run_vtysh_command()` - Wraps vtysh execution for FRR integration **Files Changed**: - evpn_mh.py - New file (109 lines) - main.py - Added 199 lines for interface EVPN commands - evpn.py - New file (67 lines) - main.py - Added 2 lines to register EVPN command group - config_evpn_mh_test.py - New file (104 lines) - config_int_evpn_test.py - New file (230 lines) **Total**: +711 lines across 6 files #### How to verify it **Global EVPN-MH Configuration**: ```bash # Configure global EVPN-MH parameters config evpn-mh startup-delay 600 config evpn-mh mac-holdtime 2000 config evpn-mh neigh-holdtime 2000 # Verify in CONFIG_DB redis-cli -n 4 HGETALL "EVPN_MH_GLOBAL|default" ``` **Interface EVPN-ES Configuration**: ```bash # Configure Type 0 (manual) ESI on PortChannel config interface evpn-esi add PortChannel1 00:01:02:03:04:05:06:07:08:09 # Configure Type 3 (MAC-based) ESI config interface evpn-esi add PortChannel2 auto-system-mac # Set DF preference config interface evpn-df-pref PortChannel1 50000 # Verify in CONFIG_DB redis-cli -n 4 HGETALL "EVPN_ETHERNET_SEGMENT|PortChannel1" # Verify in FRR vtysh -c "show running-config" | grep -A 5 "interface PortChannel1" # Delete ESI config interface evpn-esi del PortChannel1 ``` **Show Commands**: ```bash # Display EVPN information show evpn show evpn es show evpn es 00:01:02:03:04:05:06:07:08:09 show evpn es-evi show evpn es-evi 1000 show evpn es-evi detail show evpn l2-nh ``` **Run Unit Tests**: ```bash pytest tests/config_evpn_mh_test.py -v pytest tests/config_int_evpn_test.py -v ``` #### Which release branch to backport (provide reason below if selected) - [ ] 202305 - [ ] 202311 - [ ] 202405 - [ ] 202411 - [ ] 202505 - [ ] 202511 #### Tested branch (Please provide the tested image version) - [ ] <!-- image version 1 --> - [ ] <!-- image version 2 --> #### Description for the changelog Add CLI commands for EVPN VXLAN Multihoming: global EVPN-MH configuration, interface Ethernet Segment (ESI) configuration, DF preference settings, and EVPN show commands #### Link to config_db schema for YANG model changes Updates required for: - New table: `EVPN_MH_GLOBAL` - Global EVPN-MH configuration - `startup_delay`: 0-3600 (default: 300) - `mac_holdtime`: 0-86400 (default: 1080) - `neigh_holdtime`: 0-86400 (default: 1080) - New table: `EVPN_ETHERNET_SEGMENT` - Per-interface ES configuration - `esi`: 10-byte ESI string (e.g., "00:01:02:03:04:05:06:07:08:09") or "AUTO" - `type`: "TYPE_0_OPERATOR_CONFIGURED" or "TYPE_3_MAC_BASED" - `df_pref`: 1-65535 (default: 32767) YANG model updates should be submitted to sonic-yang-models repository. #### Depends on - sonic-swss-common PR: [Add L2 nexthop group table and raw netlink message handling support] - sonic-swss PR: [EVPN-MH infrastructure and orchestration agents] - FRR: EVPN MH support (available in FRR 8.x+) --- **Key Features**: ✅ Global EVPN-MH parameter configuration ✅ Type 0 (manual) and Type 3 (MAC-based) ESI support ✅ DF preference configuration for load balancing ✅ FRR integration via vtysh ✅ CONFIG_DB persistence ✅ Comprehensive validation and error handling ✅ 210+ unit test cases ✅ Show commands for monitoring EVPN state Signed-off-by: Sonic Build Admin <sonicbld@microsoft.com>
1 parent ce55b26 commit 17f60eb

19 files changed

Lines changed: 2383 additions & 245 deletions

config/evpn_mh.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import click
2+
import utilities_common.cli as clicommon
3+
4+
from jsonpatch import JsonPatchConflict
5+
6+
from .validated_config_db_connector import ValidatedConfigDBConnector
7+
8+
#
9+
# EVPN MH commands
10+
#
11+
EVPN_MH_TABLE = 'EVPN_MH_GLOBAL'
12+
13+
#
14+
# 'evpn-mh' group ('config evpn-mh ...')
15+
#
16+
17+
18+
@click.group(cls=clicommon.AbbreviationGroup, name='evpn-mh')
19+
@click.pass_context
20+
def evpn_mh(ctx):
21+
"""Set EVPN MH attributes"""
22+
pass
23+
24+
25+
#
26+
# 'startup-delay' subcommand
27+
#
28+
EVPN_MH_STARTUP_DELAY_MIN = 0
29+
EVPN_MH_STARTUP_DELAY_DEFAULT = 300
30+
EVPN_MH_STARTUP_DELAY_MAX = 3600
31+
32+
33+
def is_valid_startup_delay(startup_delay):
34+
try:
35+
if int(startup_delay) in range(EVPN_MH_STARTUP_DELAY_MIN, EVPN_MH_STARTUP_DELAY_MAX + 1):
36+
return True
37+
except (ValueError, TypeError):
38+
pass
39+
return False
40+
41+
42+
@evpn_mh.command('startup-delay')
43+
@click.argument('startup_delay', metavar='<startup_delay>', required=True)
44+
@clicommon.pass_db
45+
@click.pass_context
46+
def set_startup_delay(ctx, db, startup_delay=EVPN_MH_STARTUP_DELAY_DEFAULT):
47+
"""Set EVPN MH startup delay time in seconds"""
48+
config_db = ValidatedConfigDBConnector(db.cfgdb)
49+
if not is_valid_startup_delay(startup_delay):
50+
ctx.fail(f"EVPN MH Startup Delay {startup_delay} is not valid. "
51+
f"Valid values are {EVPN_MH_STARTUP_DELAY_MIN}-{EVPN_MH_STARTUP_DELAY_MAX}.")
52+
53+
try:
54+
# Get existing entry to preserve other fields
55+
entry = config_db.get_entry(EVPN_MH_TABLE, 'default') or {}
56+
entry['startup_delay'] = startup_delay
57+
config_db.set_entry(EVPN_MH_TABLE, 'default', entry)
58+
except (ValueError, JsonPatchConflict) as e:
59+
ctx.fail("Failed to save to ConfigDB. Error: {}".format(e))
60+
61+
62+
#
63+
# 'mac-holdtime' subcommand
64+
#
65+
EVPN_MH_MAC_HOLDTIME_MIN = 0
66+
EVPN_MH_MAC_HOLDTIME_DEFAULT = 1080
67+
EVPN_MH_MAC_HOLDTIME_MAX = 86400
68+
69+
70+
def is_valid_mac_holdtime(mac_holdtime):
71+
try:
72+
if int(mac_holdtime) in range(EVPN_MH_MAC_HOLDTIME_MIN, EVPN_MH_MAC_HOLDTIME_MAX + 1):
73+
return True
74+
except (ValueError, TypeError):
75+
pass
76+
return False
77+
78+
79+
@evpn_mh.command('mac-holdtime')
80+
@click.argument('mac_holdtime', metavar='<mac_holdtime>', required=True)
81+
@clicommon.pass_db
82+
@click.pass_context
83+
def set_mac_holdtime(ctx, db, mac_holdtime=EVPN_MH_MAC_HOLDTIME_DEFAULT):
84+
"""Set EVPN MH MAC holdtime in seconds"""
85+
config_db = ValidatedConfigDBConnector(db.cfgdb)
86+
if not is_valid_mac_holdtime(mac_holdtime):
87+
ctx.fail(f"EVPN MH MAC Holdtime {mac_holdtime} is not valid. "
88+
f"Valid values are {EVPN_MH_MAC_HOLDTIME_MIN}-{EVPN_MH_MAC_HOLDTIME_MAX}.")
89+
90+
try:
91+
# Get existing entry to preserve other fields
92+
entry = config_db.get_entry(EVPN_MH_TABLE, 'default') or {}
93+
entry['mac_holdtime'] = mac_holdtime
94+
config_db.set_entry(EVPN_MH_TABLE, 'default', entry)
95+
except (ValueError, JsonPatchConflict) as e:
96+
ctx.fail("Failed to save to ConfigDB. Error: {}".format(e))
97+
98+
99+
#
100+
# 'neigh_holdtime' subcommand
101+
#
102+
EVPN_MH_NEIGH_HOLDTIME_MIN = 0
103+
EVPN_MH_NEIGH_HOLDTIME_DEFAULT = 1080
104+
EVPN_MH_NEIGH_HOLDTIME_MAX = 86400
105+
106+
107+
def is_valid_neigh_holdtime(neigh_holdtime):
108+
try:
109+
if int(neigh_holdtime) in range(EVPN_MH_NEIGH_HOLDTIME_MIN, EVPN_MH_NEIGH_HOLDTIME_MAX + 1):
110+
return True
111+
except (ValueError, TypeError):
112+
pass
113+
return False
114+
115+
116+
@evpn_mh.command('neigh-holdtime')
117+
@click.argument('neigh_holdtime', metavar='<neigh_holdtime>', required=True)
118+
@clicommon.pass_db
119+
@click.pass_context
120+
def set_neigh_holdtime(ctx, db, neigh_holdtime=EVPN_MH_NEIGH_HOLDTIME_DEFAULT):
121+
"""Set EVPN MH neighbor holdtime in seconds"""
122+
config_db = ValidatedConfigDBConnector(db.cfgdb)
123+
if not is_valid_neigh_holdtime(neigh_holdtime):
124+
ctx.fail(f"EVPN MH Neigh Holdtime {neigh_holdtime} is not valid. "
125+
f"Valid values are {EVPN_MH_NEIGH_HOLDTIME_MIN}-{EVPN_MH_NEIGH_HOLDTIME_MAX}.")
126+
127+
try:
128+
# Get existing entry to preserve other fields
129+
entry = config_db.get_entry(EVPN_MH_TABLE, 'default') or {}
130+
entry['neigh_holdtime'] = neigh_holdtime
131+
config_db.set_entry(EVPN_MH_TABLE, 'default', entry)
132+
except (ValueError, JsonPatchConflict) as e:
133+
ctx.fail("Failed to save to ConfigDB. Error: {}".format(e))

0 commit comments

Comments
 (0)