Skip to content

Commit 29317e9

Browse files
committed
feat: add SIMULATE_SONIC option to emulate SONiC teamd/IntfMgr netdevs in sai_test
Signed-off-by: Nicholas Ching <nicholaslching@gmail.com>
1 parent c0203c0 commit 29317e9

3 files changed

Lines changed: 181 additions & 0 deletions

File tree

test/sai_test/config/lag_configer.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ def create_lag(self, lag_port_idxs):
7878
Lag: lag object
7979
"""
8080
lag: Lag = Lag(None, [], [])
81+
lag_index = len(self.test_obj.dut.lag_list)
82+
try:
83+
from config import simulate_sonic
84+
except ImportError:
85+
simulate_sonic = None
86+
if simulate_sonic is not None:
87+
simulate_sonic.ensure_portchannel(lag_index)
8188
lag_oid = sai_thrift_create_lag(self.client)
8289
lag.oid = lag_oid
8390
self.create_lag_member(lag, lag_port_idxs)

test/sai_test/config/route_configer.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,8 +549,25 @@ def create_router_interface(self, net_interface: route_item, virtual_router=None
549549
if not self.test_obj.dut.rif_list:
550550
self.test_obj.dut.rif_list = []
551551
self.test_obj.dut.rif_list.append(rif)
552+
self._simulate_sonic_assign_rif_ips(net_interface)
552553
return net_interface.rif_list[-1]
553554

555+
def _simulate_sonic_assign_rif_ips(self, net_interface):
556+
"""When SIMULATE_SONIC=1, assign the connected IPs IntfMgr would set on a
557+
LAG/SVI router interface (no-op otherwise)."""
558+
try:
559+
from config import simulate_sonic
560+
except ImportError:
561+
return
562+
if isinstance(net_interface, Lag):
563+
try:
564+
lag_index = self.test_obj.dut.lag_list.index(net_interface)
565+
except ValueError:
566+
return
567+
simulate_sonic.assign_lag_rif_ips(lag_index)
568+
elif isinstance(net_interface, Vlan):
569+
simulate_sonic.assign_svi_rif_ips(net_interface.vlan_id)
570+
554571
def create_nexthop_by_rif(self, rif, nexthop_device: Device):
555572
"""
556573
Create nexthop by bridge port index for a port.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Copyright (c) 2021 Microsoft Open Technologies, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
8+
# CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
9+
# LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS
10+
# FOR A PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
11+
#
12+
# See the Apache Version 2.0 License for specific language governing
13+
# permissions and limitations under the License.
14+
#
15+
# Microsoft would like to thank the following companies for their review and
16+
# assistance with these files: Intel Corporation, Mellanox Technologies Ltd,
17+
# Dell Products, L.P., Facebook, Inc., Marvell International Ltd.
18+
#
19+
#
20+
21+
# Optional SONiC control-plane simulation for standalone sai_test benches.
22+
#
23+
# In a real SONiC device teamd creates PortChannel netdevs and IntfMgr assigns the
24+
# interface IPs that the SAI backend mirrors onto LAG/SVI router interfaces. A
25+
# standalone sai_test bench (no teamd / IntfMgr) has neither, so LAG bring-up and
26+
# routed-to-LAG/SVI forwarding can fail. When SIMULATE_SONIC=1 the config helpers
27+
# call into this module to emulate that setup from the test's setUp path.
28+
#
29+
# This is opt-in and default-preserving: with SIMULATE_SONIC unset (the default) all
30+
# entry points are no-ops, so real ASICs and other harnesses are unaffected. All
31+
# interface names and address patterns are environment-overridable so the defaults
32+
# can be retargeted without code changes. This module is backend-neutral: it uses
33+
# standard Linux "ip" for PortChannel netdevs and LAG router-interface IPs, and for
34+
# a VLAN (SVI) router interface -- which has no host-interface netdev to mirror from
35+
# -- it runs the command templates the harness provides via SVI_RIF_SET_IP_CMD (and
36+
# optional SVI_RIF_PROBE_CMD), so no backend-specific tooling is hardcoded here.
37+
38+
import os
39+
import shlex
40+
import subprocess
41+
import time
42+
43+
44+
def enabled():
45+
return os.environ.get("SIMULATE_SONIC", "0") == "1"
46+
47+
48+
def _run(cmd, check=False):
49+
return subprocess.run(cmd, check=check, capture_output=True, text=True)
50+
51+
52+
def _mtu():
53+
return int(os.environ.get("MTU", "9100"))
54+
55+
56+
def _templated(cmd_tmpl, **kw):
57+
"""Split a command template and substitute {ifname}/{addr} placeholders."""
58+
return shlex.split(cmd_tmpl.format(**kw))
59+
60+
61+
def ensure_portchannel(lag_index):
62+
"""Create the PortChannel<lag_index> netdev teamd would provide for a LAG."""
63+
if not enabled():
64+
return
65+
66+
pc_if = "PortChannel{}".format(lag_index)
67+
mtu = _mtu()
68+
69+
if _run(["ip", "link", "show", pc_if]).returncode != 0:
70+
if _run(["ip", "link", "add", pc_if, "type", "bond"]).returncode != 0:
71+
_run(["ip", "link", "add", pc_if, "type", "dummy"], check=False)
72+
73+
_run(["ip", "link", "set", "dev", pc_if, "mtu", str(mtu)], check=False)
74+
_run(["ip", "link", "set", "dev", pc_if, "up"], check=False)
75+
76+
77+
def assign_lag_rif_ips(lag_index, retries=20, interval=0.3):
78+
"""Assign the connected IPs IntfMgr would put on a LAG router interface.
79+
80+
The IP is placed on the backend's LAG host-interface netdev (default "be<N>"),
81+
which the backend mirrors onto the LAG router interface.
82+
"""
83+
if not enabled() or os.environ.get("LAG_RIF_IPS", "1") != "1":
84+
return
85+
86+
be_prefix = os.environ.get("LAG_BE_TAP_PREFIX", "be")
87+
v4_pattern = os.environ.get("LAG_RIF_IPV4_PATTERN", "10.1.%d.1/24")
88+
v6_pattern = os.environ.get("LAG_RIF_IPV6_PATTERN", "fc00:1::%d:1/112")
89+
subnet_id = lag_index + 1
90+
be_if = "{}{}".format(be_prefix, lag_index)
91+
v4_addr = v4_pattern % subnet_id
92+
v6_addr = v6_pattern % subnet_id
93+
94+
for _ in range(retries):
95+
if _run(["ip", "link", "show", be_if]).returncode == 0:
96+
break
97+
time.sleep(interval)
98+
else:
99+
return
100+
101+
disable_v6 = "/proc/sys/net/ipv6/conf/{}/disable_ipv6".format(be_if)
102+
accept_dad = "/proc/sys/net/ipv6/conf/{}/accept_dad".format(be_if)
103+
try:
104+
with open(disable_v6, "w", encoding="ascii") as fh:
105+
fh.write("0")
106+
with open(accept_dad, "w", encoding="ascii") as fh:
107+
fh.write("0")
108+
except OSError:
109+
pass
110+
111+
_run(["ip", "addr", "add", v4_addr, "dev", be_if], check=False)
112+
_run(["ip", "-6", "addr", "add", v6_addr, "dev", be_if, "nodad"], check=False)
113+
114+
115+
def _svi_group_id(vlan_id):
116+
for entry in os.environ.get("SVI_RIF_VLANS", "10:1 20:2").split():
117+
vid, gid = entry.split(":", 1)
118+
if int(vid) == int(vlan_id):
119+
return int(gid)
120+
return int(vlan_id)
121+
122+
123+
def assign_svi_rif_ips(vlan_id, retries=20, interval=0.3):
124+
"""Assign the connected IPs IntfMgr would put on a VLAN (SVI) router interface.
125+
126+
A VLAN SVI has no host-interface netdev to mirror from, so the IP must be set on
127+
the backend interface directly. The backend-specific commands are injected by the
128+
harness as templates (with {ifname}/{addr} placeholders): SVI_RIF_SET_IP_CMD sets
129+
the address, and the optional SVI_RIF_PROBE_CMD waits until the interface exists.
130+
With SVI_RIF_SET_IP_CMD unset this is a no-op, keeping this module backend-neutral.
131+
"""
132+
if not enabled() or os.environ.get("SVI_RIF_IPS", "1") != "1":
133+
return
134+
135+
set_ip_cmd = os.environ.get("SVI_RIF_SET_IP_CMD")
136+
if not set_ip_cmd:
137+
return
138+
139+
probe_cmd = os.environ.get("SVI_RIF_PROBE_CMD")
140+
bvi_prefix = os.environ.get("SVI_BVI_PREFIX", "bvi")
141+
v4_pattern = os.environ.get("SVI_RIF_IPV4_PATTERN", "192.168.%d.1/24")
142+
v6_pattern = os.environ.get("SVI_RIF_IPV6_PATTERN", "fc02::%d:1/112")
143+
group_id = _svi_group_id(vlan_id)
144+
bvi_if = "{}{}".format(bvi_prefix, vlan_id)
145+
v4_addr = v4_pattern % group_id
146+
v6_addr = v6_pattern % group_id
147+
148+
if probe_cmd:
149+
for _ in range(retries):
150+
if _run(_templated(probe_cmd, ifname=bvi_if, addr="")).returncode == 0:
151+
break
152+
time.sleep(interval)
153+
else:
154+
return
155+
156+
for addr in (v4_addr, v6_addr):
157+
_run(_templated(set_ip_cmd, ifname=bvi_if, addr=addr), check=False)

0 commit comments

Comments
 (0)