Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions test/sai_test/config/lag_configer.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ def create_lag(self, lag_port_idxs):
Lag: lag object
"""
lag: Lag = Lag(None, [], [])
lag_index = len(self.test_obj.dut.lag_list)
try:
from config import simulate_sonic
except ImportError:
simulate_sonic = None
if simulate_sonic is not None:
simulate_sonic.ensure_portchannel(lag_index)
lag_oid = sai_thrift_create_lag(self.client)
lag.oid = lag_oid
self.create_lag_member(lag, lag_port_idxs)
Expand Down
17 changes: 17 additions & 0 deletions test/sai_test/config/route_configer.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,25 @@ def create_router_interface(self, net_interface: route_item, virtual_router=None
if not self.test_obj.dut.rif_list:
self.test_obj.dut.rif_list = []
self.test_obj.dut.rif_list.append(rif)
self._simulate_sonic_assign_rif_ips(net_interface)
return net_interface.rif_list[-1]

def _simulate_sonic_assign_rif_ips(self, net_interface):
"""When SIMULATE_SONIC=1, assign the connected IPs IntfMgr would set on a
LAG/SVI router interface (no-op otherwise)."""
try:
from config import simulate_sonic
except ImportError:
return
if isinstance(net_interface, Lag):
try:
lag_index = self.test_obj.dut.lag_list.index(net_interface)
except ValueError:
return
simulate_sonic.assign_lag_rif_ips(lag_index)
elif isinstance(net_interface, Vlan):
simulate_sonic.assign_svi_rif_ips(net_interface.vlan_id)

def create_nexthop_by_rif(self, rif, nexthop_device: Device):
"""
Create nexthop by bridge port index for a port.
Expand Down
157 changes: 157 additions & 0 deletions test/sai_test/config/simulate_sonic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Copyright (c) 2021 Microsoft Open Technologies, Inc.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the year to 2026

#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
# LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS
# FOR A PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
#
# See the Apache Version 2.0 License for specific language governing
# permissions and limitations under the License.
#
# Microsoft would like to thank the following companies for their review and
# assistance with these files: Intel Corporation, Mellanox Technologies Ltd,
# Dell Products, L.P., Facebook, Inc., Marvell International Ltd.
#
#

# Optional SONiC control-plane simulation for standalone sai_test benches.
#
# In a real SONiC device teamd creates PortChannel netdevs and IntfMgr assigns the
# interface IPs that the SAI backend mirrors onto LAG/SVI router interfaces. A
# standalone sai_test bench (no teamd / IntfMgr) has neither, so LAG bring-up and
# routed-to-LAG/SVI forwarding can fail. When SIMULATE_SONIC=1 the config helpers
# call into this module to emulate that setup from the test's setUp path.
#
# This is opt-in and default-preserving: with SIMULATE_SONIC unset (the default) all
# entry points are no-ops, so real ASICs and other harnesses are unaffected. All
# interface names and address patterns are environment-overridable so the defaults
# can be retargeted without code changes. This module is backend-neutral: it uses
# standard Linux "ip" for PortChannel netdevs and LAG router-interface IPs, and for
# a VLAN (SVI) router interface -- which has no host-interface netdev to mirror from
# -- it runs the command templates the harness provides via SVI_RIF_SET_IP_CMD (and
# optional SVI_RIF_PROBE_CMD), so no backend-specific tooling is hardcoded here.

import os
import shlex
import subprocess
import time


def enabled():
return os.environ.get("SIMULATE_SONIC", "0") == "1"


def _run(cmd, check=False):
return subprocess.run(cmd, check=check, capture_output=True, text=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a warning or error log message when _run fails

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All done! Warnings/errors now log using logger module.



def _mtu():
return int(os.environ.get("MTU", "9100"))


def _templated(cmd_tmpl, **kw):
"""Split a command template and substitute {ifname}/{addr} placeholders."""
return shlex.split(cmd_tmpl.format(**kw))


def ensure_portchannel(lag_index):
"""Create the PortChannel<lag_index> netdev teamd would provide for a LAG."""
if not enabled():
return

pc_if = "PortChannel{}".format(lag_index)
mtu = _mtu()

if _run(["ip", "link", "show", pc_if]).returncode != 0:
if _run(["ip", "link", "add", pc_if, "type", "bond"]).returncode != 0:
_run(["ip", "link", "add", pc_if, "type", "dummy"], check=False)

_run(["ip", "link", "set", "dev", pc_if, "mtu", str(mtu)], check=False)
_run(["ip", "link", "set", "dev", pc_if, "up"], check=False)


def assign_lag_rif_ips(lag_index, retries=20, interval=0.3):
"""Assign the connected IPs IntfMgr would put on a LAG router interface.

The IP is placed on the backend's LAG host-interface netdev (default "be<N>"),
which the backend mirrors onto the LAG router interface.
"""
if not enabled() or os.environ.get("LAG_RIF_IPS", "1") != "1":
return

be_prefix = os.environ.get("LAG_BE_TAP_PREFIX", "be")
v4_pattern = os.environ.get("LAG_RIF_IPV4_PATTERN", "10.1.%d.1/24")
v6_pattern = os.environ.get("LAG_RIF_IPV6_PATTERN", "fc00:1::%d:1/112")
subnet_id = lag_index + 1
be_if = "{}{}".format(be_prefix, lag_index)
v4_addr = v4_pattern % subnet_id
v6_addr = v6_pattern % subnet_id

for _ in range(retries):
if _run(["ip", "link", "show", be_if]).returncode == 0:
break
time.sleep(interval)
else:
return

disable_v6 = "/proc/sys/net/ipv6/conf/{}/disable_ipv6".format(be_if)
accept_dad = "/proc/sys/net/ipv6/conf/{}/accept_dad".format(be_if)
try:
with open(disable_v6, "w", encoding="ascii") as fh:
fh.write("0")
with open(accept_dad, "w", encoding="ascii") as fh:
fh.write("0")
except OSError:
pass

_run(["ip", "addr", "add", v4_addr, "dev", be_if], check=False)
_run(["ip", "-6", "addr", "add", v6_addr, "dev", be_if, "nodad"], check=False)


def _svi_group_id(vlan_id):
for entry in os.environ.get("SVI_RIF_VLANS", "10:1 20:2").split():
vid, gid = entry.split(":", 1)
if int(vid) == int(vlan_id):
return int(gid)
return int(vlan_id)


def assign_svi_rif_ips(vlan_id, retries=20, interval=0.3):
"""Assign the connected IPs IntfMgr would put on a VLAN (SVI) router interface.

A VLAN SVI has no host-interface netdev to mirror from, so the IP must be set on
the backend interface directly. The backend-specific commands are injected by the
harness as templates (with {ifname}/{addr} placeholders): SVI_RIF_SET_IP_CMD sets
the address, and the optional SVI_RIF_PROBE_CMD waits until the interface exists.
With SVI_RIF_SET_IP_CMD unset this is a no-op, keeping this module backend-neutral.
"""
if not enabled() or os.environ.get("SVI_RIF_IPS", "1") != "1":
return

set_ip_cmd = os.environ.get("SVI_RIF_SET_IP_CMD")
if not set_ip_cmd:
return

probe_cmd = os.environ.get("SVI_RIF_PROBE_CMD")
bvi_prefix = os.environ.get("SVI_BVI_PREFIX", "bvi")
v4_pattern = os.environ.get("SVI_RIF_IPV4_PATTERN", "192.168.%d.1/24")
v6_pattern = os.environ.get("SVI_RIF_IPV6_PATTERN", "fc02::%d:1/112")
group_id = _svi_group_id(vlan_id)
bvi_if = "{}{}".format(bvi_prefix, vlan_id)
v4_addr = v4_pattern % group_id
v6_addr = v6_pattern % group_id

if probe_cmd:
for _ in range(retries):
if _run(_templated(probe_cmd, ifname=bvi_if, addr="")).returncode == 0:
break
time.sleep(interval)
else:
return

for addr in (v4_addr, v6_addr):
_run(_templated(set_ip_cmd, ifname=bvi_if, addr=addr), check=False)
Loading