From 40a8f4adfef43415951ce583d897f2d41f772c7c Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Tue, 2 Jun 2026 11:05:14 -0700 Subject: [PATCH 01/10] feat: modify saithriftv2 makefile; phase 1, task 1 Signed-off-by: Nicholas Ching --- test/saithriftv2/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/saithriftv2/Makefile b/test/saithriftv2/Makefile index 7cd624bc5..38e1897c1 100644 --- a/test/saithriftv2/Makefile +++ b/test/saithriftv2/Makefile @@ -39,6 +39,9 @@ endif ifeq ($(platform),vs) LIBS = -lthrift -lpthread -lsaivs -lsaimeta -lsaimetadata -lzmq +else ifeq ($(platform),vpp) +LIBS = -lthrift -lpthread -lsaivs -lsaimeta -lsaimetadata -lzmq \ + -lvlib -lvlibapi -lvppapiclient -lvlibmemoryclient -lvppinfra else LIBS = -lthrift -lpthread -lsai -lsaimetadata endif From 3d8eea236026177fffc2f7da4e2783acceb27d8f Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Wed, 10 Jun 2026 11:36:50 -0700 Subject: [PATCH 02/10] fix: index error on stringification Signed-off-by: Nicholas Ching --- test/saithriftv2/convert_header.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/saithriftv2/convert_header.py b/test/saithriftv2/convert_header.py index 2c5a958cf..9c08aac34 100644 --- a/test/saithriftv2/convert_header.py +++ b/test/saithriftv2/convert_header.py @@ -47,7 +47,7 @@ class sai_common_api(SAIEnum): import enum class SAIEnum(enum.IntEnum): def __str__(self): - return super().__str__().split(\".\")[1]\n""" + return self.name\n""" ENUM_PREFIX = "enum__sai_" SAI_NAME = "SAI" ENUM_TYPE = "= c_int" From e2eb1de81c4b0be66c24806acc3dd0f52aa214a3 Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Mon, 15 Jun 2026 06:56:09 -0700 Subject: [PATCH 03/10] fix: drop bridge_id on create_bridge_port Signed-off-by: Nicholas Ching --- test/sai_test/config/port_configer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/sai_test/config/port_configer.py b/test/sai_test/config/port_configer.py index a3126af46..e0126fcd1 100644 --- a/test/sai_test/config/port_configer.py +++ b/test/sai_test/config/port_configer.py @@ -120,7 +120,6 @@ def create_bridge_ports(self, bridge_id, port_list: List['Port']): for index, item in enumerate(port_list): port_bp = sai_thrift_create_bridge_port( self.client, - bridge_id=bridge_id, port_id=item.oid, type=SAI_BRIDGE_PORT_TYPE_PORT, admin_state=True) @@ -498,7 +497,7 @@ def turn_up_and_get_checked_ports(self, port_list: List['Port']): ''' # For brcm devices, need to init and setup the ports at once after start the switch. - retries = 10 + retries = 2 down_port_list = [] test_port_list:List[Port] = [] @@ -527,7 +526,7 @@ def turn_up_and_get_checked_ports(self, port_list: List['Port']): if port_attr['oper_status'] == SAI_PORT_OPER_STATUS_UP: port_up = True break - time.sleep(3) + time.sleep(1) self.log_port_state(port, index) print("port {} , local index {} id {} is not up, status: {}. Retry. Reset Admin State.".format( index, port.port_index, port.oid, port_attr['oper_status'])) From e9dacaa6e013317dca928a3532580f3832c770a5 Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Mon, 15 Jun 2026 06:56:31 -0700 Subject: [PATCH 04/10] fix: switch_id on route + neighbor entries Signed-off-by: Nicholas Ching --- test/sai_test/config/route_configer.py | 32 +++++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/test/sai_test/config/route_configer.py b/test/sai_test/config/route_configer.py index a7aa20946..54f742f0f 100644 --- a/test/sai_test/config/route_configer.py +++ b/test/sai_test/config/route_configer.py @@ -290,7 +290,8 @@ def create_default_v4_v6_route_entry(self): addr=sai_thrift_ip_addr_t( ip6=DEFAULT_IP_V6_PREFIX), mask=sai_thrift_ip_addr_t(ip6=DEFAULT_IP_V6_PREFIX)) - self.test_obj.dut.default_ipv6_route_entry = sai_thrift_route_entry_t(vr_id=self.test_obj.dut.default_vrf, + self.test_obj.dut.default_ipv6_route_entry = sai_thrift_route_entry_t(switch_id=self.test_obj.dut.switch_id, + vr_id=self.test_obj.dut.default_vrf, destination=v6_default) status = sai_thrift_create_route_entry( self.client, @@ -298,7 +299,8 @@ def create_default_v4_v6_route_entry(self): packet_action=SAI_PACKET_ACTION_DROP) self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) - self.test_obj.dut.default_ipv4_route_entry = sai_thrift_route_entry_t(vr_id=self.test_obj.dut.default_vrf, + self.test_obj.dut.default_ipv4_route_entry = sai_thrift_route_entry_t(switch_id=self.test_obj.dut.switch_id, + vr_id=self.test_obj.dut.default_vrf, destination=sai_ipprefix(DEFAULT_IP_V4_PREFIX)) status = sai_thrift_create_route_entry( self.client, @@ -323,22 +325,22 @@ def create_route_by_rif( vr_id = self.choice_virtual_route(virtual_router) if dest_device.ip_prefix: net_routev4 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/'+dest_device.ip_prefix)) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/'+dest_device.ip_prefix)) else: # destination cannot use sai_ipaddress net_routev4 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/32')) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/32')) status = sai_thrift_create_route_entry( self.client, net_routev4, next_hop_id=rif) self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) if dest_device.ip_prefix_v6: net_routev6 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/'+dest_device.ip_prefix_v6)) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/'+dest_device.ip_prefix_v6)) else: # destination cannot use sai_ipaddress net_routev6 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/128')) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/128')) status = sai_thrift_create_route_entry( self.client, net_routev6, next_hop_id=rif) self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) @@ -366,22 +368,22 @@ def create_route_by_nexthop( vr_id = self.choice_virtual_route(virtual_router) if dest_device.ip_prefix: net_routev4 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/'+dest_device.ip_prefix)) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/'+dest_device.ip_prefix)) else: # destination cannot use sai_ipaddress net_routev4 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/32')) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/32')) status = sai_thrift_create_route_entry( self.client, net_routev4, next_hop_id=nexthopv4.oid) self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) if dest_device.ip_prefix_v6: net_routev6 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/'+dest_device.ip_prefix_v6)) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/'+dest_device.ip_prefix_v6)) else: # destination cannot use sai_ipaddress net_routev6 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/128')) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/128')) status = sai_thrift_create_route_entry( self.client, net_routev6, next_hop_id=nexthopv6.oid) self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) @@ -406,22 +408,22 @@ def create_route_by_nexthop_group( vr_id = self.choice_virtual_route(virtual_router) if dest_device.ip_prefix: net_routev4 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/'+dest_device.ip_prefix)) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/'+dest_device.ip_prefix)) else: # destination cannot use sai_ipaddress net_routev4 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/32')) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv4+'/32')) status = sai_thrift_create_route_entry( self.client, net_routev4, next_hop_id=nexthop_groupv4.nhp_grp_id) self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) if dest_device.ip_prefix_v6: net_routev6 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/'+dest_device.ip_prefix_v6)) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/'+dest_device.ip_prefix_v6)) else: # destination cannot use sai_ipaddress net_routev6 = sai_thrift_route_entry_t( - vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/128')) + switch_id=self.test_obj.dut.switch_id, vr_id=vr_id, destination=sai_ipprefix(dest_device.ipv6+'/128')) status = sai_thrift_create_route_entry( self.client, net_routev6, next_hop_id=nexthop_groupv6.nhp_grp_id) self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) @@ -447,6 +449,7 @@ def create_neighbor_by_rif(self, nexthop_device: Device, rif, no_host=True): """ if nexthop_device.ipv4: nbr_entry_v4 = sai_thrift_neighbor_entry_t( + switch_id=self.test_obj.dut.switch_id, rif_id=rif, ip_address=sai_ipaddress(nexthop_device.ipv4)) status = sai_thrift_create_neighbor_entry( @@ -460,6 +463,7 @@ def create_neighbor_by_rif(self, nexthop_device: Device, rif, no_host=True): if nexthop_device.ipv6: nbr_entry_v6 = sai_thrift_neighbor_entry_t( + switch_id=self.test_obj.dut.switch_id, rif_id=rif, ip_address=sai_ipaddress(nexthop_device.ipv6)) status = sai_thrift_create_neighbor_entry( From dc7ca41df44f9ae65155abace3cb08dd9a71ce34 Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Mon, 15 Jun 2026 07:18:21 -0700 Subject: [PATCH 05/10] feat: enable SWSS debug logging in saiserver (link swsscommon) Signed-off-by: Nicholas Ching --- test/saithriftv2/Makefile | 4 ++-- test/saithriftv2/src/saiserver.cpp | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/saithriftv2/Makefile b/test/saithriftv2/Makefile index 38e1897c1..6cfeac2e7 100644 --- a/test/saithriftv2/Makefile +++ b/test/saithriftv2/Makefile @@ -38,10 +38,10 @@ CTYPESGEN = /usr/local/bin/ctypesgen.py endif ifeq ($(platform),vs) -LIBS = -lthrift -lpthread -lsaivs -lsaimeta -lsaimetadata -lzmq +LIBS = -lthrift -lpthread -lsaivs -lsaimeta -lsaimetadata -lzmq -lswsscommon else ifeq ($(platform),vpp) LIBS = -lthrift -lpthread -lsaivs -lsaimeta -lsaimetadata -lzmq \ - -lvlib -lvlibapi -lvppapiclient -lvlibmemoryclient -lvppinfra + -lvlib -lvlibapi -lvppapiclient -lvlibmemoryclient -lvppinfra -lswsscommon else LIBS = -lthrift -lpthread -lsai -lsaimetadata endif diff --git a/test/saithriftv2/src/saiserver.cpp b/test/saithriftv2/src/saiserver.cpp index 331274506..121f4495e 100644 --- a/test/saithriftv2/src/saiserver.cpp +++ b/test/saithriftv2/src/saiserver.cpp @@ -21,6 +21,7 @@ #include #include #include "sai_rpc.h" +#include #define UNREFERENCED_PARAMETER(P) (P) @@ -269,6 +270,9 @@ void handleInitScript(const std::string& initScript) int main(int argc, char* argv[]) { + swss::Logger::getInstance().setMinPrio(swss::Logger::SWSS_DEBUG); + swss::Logger::getInstance().swssOutputNotify("saiserver", "STDOUT"); + int rv = 0; auto options = handleCmdLine(argc, argv); From c5410f66a137d3dfe1104fd3f6972e50a477352d Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Mon, 15 Jun 2026 07:19:35 -0700 Subject: [PATCH 06/10] chore: install Python bindings to python3 dist-packages for bookworm Signed-off-by: Nicholas Ching --- debian/python-saithrift.install | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/python-saithrift.install b/debian/python-saithrift.install index b98491588..2823a1da2 100644 --- a/debian/python-saithrift.install +++ b/debian/python-saithrift.install @@ -1 +1,2 @@ -debian/usr/local/lib/python2.7/site-packages/* /usr/lib/python2.7/dist-packages/ +#compatiable with bookworm python 3.11 environment and build with python3 +debian/usr/local/local/lib/python3*/dist-packages/* /usr/lib/python3/dist-packages/ From b9b4fb0abb6e5d991adc987a1af3ac5b9b999464 Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Mon, 15 Jun 2026 07:20:36 -0700 Subject: [PATCH 07/10] update pointer to ptf submodule Signed-off-by: Nicholas Ching --- test/ptf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ptf b/test/ptf index 749436660..e7a74af04 160000 --- a/test/ptf +++ b/test/ptf @@ -1 +1 @@ -Subproject commit 7494366607e2e4c171439df3585eba3c9769fad8 +Subproject commit e7a74af04ba348487ff315008b2b238597369fea From 911a8ff47a264d4457fcc9ff10b53a7df66e4914 Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Fri, 19 Jun 2026 11:27:23 -0700 Subject: [PATCH 08/10] fix: -5 error, switch-id-NULL on neighbor/route create test Signed-off-by: Nicholas Ching --- .../sai_rpc_server_helper_functions.tt | 52 ++++++++++++++++++- test/saithrift/src/switch_sai.thrift | 5 +- test/saithrift/src/switch_sai_rpc_server.cpp | 8 ++- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/meta/templates/sai_rpc_server_helper_functions.tt b/meta/templates/sai_rpc_server_helper_functions.tt index 7ea24b063..da8dc2fc6 100644 --- a/meta/templates/sai_rpc_server_helper_functions.tt +++ b/meta/templates/sai_rpc_server_helper_functions.tt @@ -31,7 +31,8 @@ } [%- END -%] [%- FOREACH struct IN apis.$api.structs %] - + [%- NEXT IF struct.short_name == 'neighbor_entry' %] + [%- NEXT IF struct.short_name == 'route_entry' %] [%- PROCESS struct_helper_function_header %] { @@ -135,8 +136,57 @@ void sai_thrift_parse_[% struct.short_name %](const [% struct.thrift_name %] &th [%- ######################################################################## -%] [%- BLOCK special_helper_functions -%] +extern sai_object_id_t gSwitchId; +extern sai_object_id_t switch_id; + void sai_thrift_parse_buffer(const std::string &thrift_buffer, void *buffer) { /* not supported yet */ } + +void sai_thrift_parse_neighbor_entry(const sai_thrift_neighbor_entry_t &thrift_neighbor_entry, + sai_neighbor_entry_t *neighbor_entry) +{ + if (neighbor_entry == NULL) { + return; + } + + // OCP sai_test often omits switch_id on neighbor_entry; fall back to the + // RPC-global switch_id (set by sai_thrift_create_switch) then gSwitchId. + if (thrift_neighbor_entry.switch_id != 0) { + neighbor_entry->switch_id = (sai_object_id_t)thrift_neighbor_entry.switch_id; + } else if (switch_id != SAI_NULL_OBJECT_ID) { + neighbor_entry->switch_id = switch_id; + } else if (gSwitchId != SAI_NULL_OBJECT_ID) { + neighbor_entry->switch_id = gSwitchId; + } else { + neighbor_entry->switch_id = SAI_NULL_OBJECT_ID; + } + + neighbor_entry->rif_id = (sai_object_id_t)thrift_neighbor_entry.rif_id; + sai_thrift_ip_address_t_parse(thrift_neighbor_entry.ip_address, + &neighbor_entry->ip_address); +} + +void sai_thrift_parse_route_entry(const sai_thrift_route_entry_t &thrift_route_entry, + sai_route_entry_t *route_entry) +{ + if (route_entry == NULL) { + return; + } + + if (thrift_route_entry.switch_id != 0) { + route_entry->switch_id = (sai_object_id_t)thrift_route_entry.switch_id; + } else if (switch_id != SAI_NULL_OBJECT_ID) { + route_entry->switch_id = switch_id; + } else if (gSwitchId != SAI_NULL_OBJECT_ID) { + route_entry->switch_id = gSwitchId; + } else { + route_entry->switch_id = SAI_NULL_OBJECT_ID; + } + + route_entry->vr_id = (sai_object_id_t)thrift_route_entry.vr_id; + sai_thrift_ip_prefix_t_parse(thrift_route_entry.destination, + &route_entry->destination); +} [% END -%] diff --git a/test/saithrift/src/switch_sai.thrift b/test/saithrift/src/switch_sai.thrift index 7f465749d..e46af31bf 100644 --- a/test/saithrift/src/switch_sai.thrift +++ b/test/saithrift/src/switch_sai.thrift @@ -198,8 +198,9 @@ struct sai_thrift_route_entry_t { } struct sai_thrift_neighbor_entry_t { - 1: sai_thrift_object_id_t rif_id; - 2: sai_thrift_ip_address_t ip_address; + 1: sai_thrift_object_id_t switch_id; + 2: sai_thrift_object_id_t rif_id; + 3: sai_thrift_ip_address_t ip_address; } struct sai_thrift_attribute_list_t { diff --git a/test/saithrift/src/switch_sai_rpc_server.cpp b/test/saithrift/src/switch_sai_rpc_server.cpp index 3a487de8a..41346e40c 100644 --- a/test/saithrift/src/switch_sai_rpc_server.cpp +++ b/test/saithrift/src/switch_sai_rpc_server.cpp @@ -241,7 +241,13 @@ class switch_sai_rpcHandler : virtual public switch_sai_rpcIf { } void sai_thrift_parse_neighbor_entry(const sai_thrift_neighbor_entry_t &thrift_neighbor_entry, sai_neighbor_entry_t *neighbor_entry) { - neighbor_entry->switch_id = gSwitchId; + // Python saithrift sends switch_id as field 1 (optional). Always fall back to + // gSwitchId when the client omits it (OCP sai_test often only passes rif_id). + if (thrift_neighbor_entry.switch_id != 0) { + neighbor_entry->switch_id = (sai_object_id_t) thrift_neighbor_entry.switch_id; + } else { + neighbor_entry->switch_id = gSwitchId; + } neighbor_entry->rif_id = (sai_object_id_t) thrift_neighbor_entry.rif_id; sai_thrift_parse_ip_address(thrift_neighbor_entry.ip_address, &neighbor_entry->ip_address); } From 4af46a71723aef8012e4b86afb238ab01ee37a42 Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Fri, 19 Jun 2026 11:28:04 -0700 Subject: [PATCH 09/10] feat: env-gated parallel port bring-up config option through params, default option unchanged Signed-off-by: Nicholas Ching --- test/sai_test/config/port_configer.py | 80 +++++++++++++++++++-------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/test/sai_test/config/port_configer.py b/test/sai_test/config/port_configer.py index e0126fcd1..3f3843a2f 100644 --- a/test/sai_test/config/port_configer.py +++ b/test/sai_test/config/port_configer.py @@ -18,6 +18,7 @@ # # +import os from collections import OrderedDict from ptf import config from sai_utils import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] @@ -497,7 +498,22 @@ def turn_up_and_get_checked_ports(self, port_list: List['Port']): ''' # For brcm devices, need to init and setup the ports at once after start the switch. - retries = 2 + # + # Waiting strategy is configurable via environment, defaulting to the original + # behavior (retries=2, 1s poll interval). The original loop waited PER PORT + # serially (retries x interval seconds for each of N ports), which on a large + # port count where oper-status is slow to settle costs N * retries * interval + # seconds (e.g. 32 ports x 2 x 1s ~= 64s). The shared-wait path below issues + # admin-up to every port once, then polls ALL ports together for at most + # (retries x interval) seconds total, re-asserting admin-up on stragglers each + # round. Semantics are preserved: every port is set admin-up, oper-status is + # polled, and down_port_list / the returned list are identical; only the total + # wall-clock wait changes. Real-HW/ASIC consumers that do not set these env + # vars keep the exact original timing. + retries = int(os.environ.get('SAI_PORT_UP_RETRIES', '2')) + poll_interval = float(os.environ.get('SAI_PORT_UP_POLL_INTERVAL', '1')) + # Opt-in: poll all ports together in one bounded loop instead of per-port. + shared_wait = os.environ.get('SAI_PORT_UP_SHARED_WAIT', '0') == '1' down_port_list = [] test_port_list:List[Port] = [] @@ -513,29 +529,49 @@ def turn_up_and_get_checked_ports(self, port_list: List['Port']): port_oid=port.oid, admin_state=True) - for index, port in enumerate(test_port_list): - port_attr = sai_thrift_get_port_attribute( - self.client, port.oid, oper_status=True) - print("Turn up port {}".format(index)) - port_up = True - if port_attr['oper_status'] != SAI_PORT_OPER_STATUS_UP: - port_up = False - for num_of_tries in range(retries): + if shared_wait: + # Single bounded wait shared across all ports. + pending = list(enumerate(test_port_list)) + for num_of_tries in range(retries + 1): + still_down = [] + for index, port in pending: port_attr = sai_thrift_get_port_attribute( self.client, port.oid, oper_status=True) - if port_attr['oper_status'] == SAI_PORT_OPER_STATUS_UP: - port_up = True - break - time.sleep(1) - self.log_port_state(port, index) - print("port {} , local index {} id {} is not up, status: {}. Retry. Reset Admin State.".format( - index, port.port_index, port.oid, port_attr['oper_status'])) - sai_thrift_set_port_attribute( - self.client, - port_oid=port.oid, - admin_state=True) - if not port_up: - down_port_list.append(index) + if port_attr['oper_status'] != SAI_PORT_OPER_STATUS_UP: + still_down.append((index, port)) + pending = still_down + if not pending: + break + if num_of_tries < retries: + time.sleep(poll_interval) + for index, port in pending: + sai_thrift_set_port_attribute( + self.client, port_oid=port.oid, admin_state=True) + down_port_list = [index for index, _ in pending] + else: + for index, port in enumerate(test_port_list): + port_attr = sai_thrift_get_port_attribute( + self.client, port.oid, oper_status=True) + print("Turn up port {}".format(index)) + port_up = True + if port_attr['oper_status'] != SAI_PORT_OPER_STATUS_UP: + port_up = False + for num_of_tries in range(retries): + port_attr = sai_thrift_get_port_attribute( + self.client, port.oid, oper_status=True) + if port_attr['oper_status'] == SAI_PORT_OPER_STATUS_UP: + port_up = True + break + time.sleep(poll_interval) + self.log_port_state(port, index) + print("port {} , local index {} id {} is not up, status: {}. Retry. Reset Admin State.".format( + index, port.port_index, port.oid, port_attr['oper_status'])) + sai_thrift_set_port_attribute( + self.client, + port_oid=port.oid, + admin_state=True) + if not port_up: + down_port_list.append(index) if down_port_list: print("Ports {} are down after retries.".format(down_port_list)) return test_port_list From d305b2e0647dc37aa5132a9272c4f6cc16a8f55e Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Fri, 19 Jun 2026 11:28:22 -0700 Subject: [PATCH 10/10] fix: v4/v6 NHG member_port_indexs aliasing Signed-off-by: Nicholas Ching --- test/sai_test/config/route_configer.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/sai_test/config/route_configer.py b/test/sai_test/config/route_configer.py index 54f742f0f..8ae4687e3 100644 --- a/test/sai_test/config/route_configer.py +++ b/test/sai_test/config/route_configer.py @@ -626,9 +626,13 @@ def create_nexthop_group_by_nexthops(self, nexthopv4_list: List[Nexthop], nextho nhp_grpv4_members.append(nhp_grpv4_member) nhp_grpv6_members.append(nhp_grpv6_member) + # Give each NHG its own copy of the member port-index list. The v4 and v6 + # groups are mutated independently (remove/re-add member tests), so sharing + # one list object would let a v4 mutation corrupt the v6 group's port list + # (ValueError: x not in list) when both run in the same config group. member_port_indexs = [17, 18, 19, 20, 21, 22, 23, 24] - nhp_grpv4: NexthopGroup = NexthopGroup(nhop_groupv4_id, nhp_grpv4_members, member_port_indexs) - nhp_grpv6: NexthopGroup = NexthopGroup(nhop_groupv6_id, nhp_grpv6_members, member_port_indexs) + nhp_grpv4: NexthopGroup = NexthopGroup(nhop_groupv4_id, nhp_grpv4_members, list(member_port_indexs)) + nhp_grpv6: NexthopGroup = NexthopGroup(nhop_groupv6_id, nhp_grpv6_members, list(member_port_indexs)) self.test_obj.dut.nhp_grpv4_list.append(nhp_grpv4) self.test_obj.dut.nhp_grpv6_list.append(nhp_grpv6)