diff --git a/test/sai_test/config/lag_configer.py b/test/sai_test/config/lag_configer.py new file mode 100644 index 000000000..05828bfc7 --- /dev/null +++ b/test/sai_test/config/lag_configer.py @@ -0,0 +1,162 @@ +# Copyright (c) 2021 Microsoft Open Technologies, Inc. +# +# 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. +# +# + + +from sai_thrift.sai_adapter import * +from sai_utils import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] + + +def t0_lag_config_helper(test_obj, is_create_lag=True): + """ + Make lag configurations base on the configuration in the test plan. + set the configuration in test directly. + + set the following test_obj attributes: + lag object + + """ + lag_configer = LagConfiger(test_obj) + + if is_create_lag: + test_obj.lag1 = lag_configer.create_lag([17, 18]) + test_obj.lag2 = lag_configer.create_lag([19, 20]) + + """ + lag_configer.set_lag_hash_algorithm() + lag_configer.setup_lag_v4_hash() + lag_configer.set_lag_hash_seed() + """ + + +class LagConfiger(object): + """ + Class use to make all the Lag configurations. + """ + + def __init__(self, test_obj) -> None: + """ + Init Lag configrer. + + Args: + test_obj: the test object + """ + self.test_obj = test_obj + self.client = test_obj.client + + def create_lag(self, lag_port_idxs): + """ + Create lag and its members. + + Args: + lag_port_idxs: lag port indexs + + Returns: + Lag: lag object + """ + + lag = Lag() + lag_id = sai_thrift_create_lag(self.client) + lag_members = self.create_lag_member(lag_id, lag_port_idxs) + self.test_obj.assertEqual(self.test_obj.status(), SAI_STATUS_SUCCESS) + lag.lag_id = lag_id + lag.lag_members = lag_members + return lag + + def create_lag_member(self, lag_id, lag_port_idxs): + """ + Create lag members for a lag. + + Args: + lag: lag object + lag_port_idxs: lag member port indexs + + Returns: + lag_members: list of lag_member + """ + + lag_members = [] + for port_index in lag_port_idxs: + lag_member = sai_thrift_create_lag_member(self.client, + lag_id=lag_id, + port_id=self.test_obj.port_list[port_index]) + self.test_obj.assertEqual(self.test_obj.status(), SAI_STATUS_SUCCESS) + lag_members.append(lag_member) + return lag_members + + def set_lag_hash_algorithm(self, algo=SAI_HASH_ALGORITHM_CRC): + """ + Set lag hash algorithm. + + Args: + algo (int): hash algorithm id + """ + sai_thrift_set_switch_attribute(self.client, lag_default_hash_algorithm=algo) + + def setup_lag_v4_hash(self, hash_fields_list=None, lag_hash_ipv4=None): + if hash_fields_list is None: + hash_fields_list = [SAI_NATIVE_HASH_FIELD_SRC_IP, + SAI_NATIVE_HASH_FIELD_DST_IP, + SAI_NATIVE_HASH_FIELD_IP_PROTOCOL, + SAI_NATIVE_HASH_FIELD_L4_DST_PORT, + SAI_NATIVE_HASH_FIELD_L4_SRC_PORT] + + if lag_hash_ipv4 is None: + # create new hash + s32list = sai_thrift_s32_list_t(count=len(hash_fields_list), int32list=hash_fields_list) + lag_hash_ipv4 = sai_thrift_create_hash(self.client, native_hash_field_list=s32list) + self.test_obj.assertTrue(lag_hash_ipv4 != 0, "Failed to create IPv4 lag hash") + status = sai_thrift_set_switch_attribute(self.client, lag_hash_ipv4=lag_hash_ipv4) + self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) + else: + # update existing hash + s32list = sai_thrift_s32_list_t(count=len(hash_fields_list), int32list=hash_fields_list) + status = sai_thrift_set_hash_attribute(self.client, lag_hash_ipv4, native_hash_field_list=s32list) + self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) + + def set_lag_hash_seed(self, seed=400): + """ + Set lag hash seed. + + Args: + seed: hash seed value + """ + status = sai_thrift_set_switch_attribute(self.client, lag_default_hash_seed=seed) + self.test_obj.assertEqual(status, SAI_STATUS_SUCCESS) + + def remove_lag_member(self, lag_member): + sai_thrift_remove_lag_member(self, lag_member) + + def remove_all_lag_members(self, lag_members): + for lag_member in lag_members: + sai_thrift_remove_lag_member(self.client, lag_member) + + def remove_lag(self, lag_id): + sai_thrift_remove_lag(self.client, lag_id) + +class Lag(object): + """ + Represent the lag object. + Attrs: + lag_id: lag id + lag_members: lag members + """ + def __init__(self, lag_id=None, lag_members=None): + self.lag_id = lag_id + self.lag_members = lag_members diff --git a/test/sai_test/config/route_configer.py b/test/sai_test/config/route_configer.py new file mode 100644 index 000000000..94d01fcef --- /dev/null +++ b/test/sai_test/config/route_configer.py @@ -0,0 +1,138 @@ +# Copyright (c) 2021 Microsoft Open Technologies, Inc. +# +# 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. +# +# + + +from sai_thrift.sai_adapter import * +from sai_utils import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] + +def t0_route_config_helper(test_obj, is_create_route=True, is_create_route_for_lag=True): + route_configer = RouteConfiger(test_obj) + + if is_create_route: + route_configer.create_default_route() + + if is_create_route_for_lag: + ip_addr1 = '10.10.10.0' + mac_addr1 = '02:04:02:01:01:01' + route_configer.create_route_and_neighbor_entry_for_port(ip_addr=ip_addr1, + mac_addr=mac_addr1, + port_id=test_obj.lag1.lag_id, + virtual_router_id=test_obj.default_vrf) + + ip_addr2 = '10.1.2.100' + mac_addr2 = '02:04:02:01:02:01' + route_configer.create_route_and_neighbor_entry_for_port(ip_addr=ip_addr1, + mac_addr=mac_addr2, + port_id=test_obj.lag2.lag_id, + virtual_router_id=test_obj.default_vrf) + + +class RouteConfiger(object): + """ + Class use to make all the route configurations. + """ + + def __init__(self, test_obj) -> None: + """ + Init Route configer. + + Args: + test_obj: the test object + """ + self.test_obj = test_obj + self.client = test_obj.client + + def create_default_route(self): + self.create_default_route_intf() + self.create_default_v4_v6_route_entry() + self.create_local_v6_route() + + def create_default_route_intf(self): + """ + Create default route interface on loop back interface. + """ + print("Create loop back interface...") + attr = sai_thrift_get_switch_attribute(self.client, default_virtual_router_id=True) + self.test_obj.assertNotEqual(attr['default_virtual_router_id'], 0) + self.test_obj.default_vrf = attr['default_virtual_router_id'] + + self.test_obj.loopback_intf = sai_thrift_create_router_interface(self.client, + type=SAI_ROUTER_INTERFACE_TYPE_LOOPBACK, virtual_router_id=self.test_obj.default_vrf) + self.test_obj.assertEqual(self.test_obj.status(), SAI_STATUS_SUCCESS) + + def create_default_v4_v6_route_entry(self): + """ + Create default v4 and v6 route entry. + """ + + print("Create default v4&v6 route entry...") + v6_default = sai_thrift_ip_prefix_t(addr_family=1, + addr=sai_thrift_ip_addr_t(ip6=DEFAULT_IP_V6_PREFIX), + mask=sai_thrift_ip_addr_t(ip6=DEFAULT_IP_V6_PREFIX)) + entry = sai_thrift_route_entry_t(vr_id=self.test_obj.default_vrf, + destination=v6_default) + self.test_obj.default_ipv6_route_entry = sai_thrift_create_route_entry( + self.client, + route_entry=entry, + packet_action=SAI_PACKET_ACTION_DROP) + self.test_obj.assertEqual(self.test_obj.status(), SAI_STATUS_SUCCESS) + + entry = sai_thrift_route_entry_t(vr_id=self.test_obj.default_vrf, + destination=sai_ipprefix(DEFAULT_IP_V4_PREFIX)) + self.test_obj.default_ipv4_route_entry = sai_thrift_create_route_entry( + self.client, + route_entry=entry, + packet_action=SAI_PACKET_ACTION_DROP) + self.test_obj.assertEqual(self.test_obj.status(), SAI_STATUS_SUCCESS) + + def create_local_v6_route(self): + """ + Create local v6 route base on the configuration of the actual switch. + """ + + print("Create local v6 route...") + entry = sai_thrift_route_entry_t(vr_id=self.test_obj.default_vrf, + destination=sai_ipprefix(LOCAL_IP_10V6_PREFIX)) + self.test_obj.local_10v6_route_entry = sai_thrift_create_route_entry( + self.client, + route_entry=entry, + packet_action=SAI_PACKET_ACTION_FORWARD) + self.test_obj.assertEqual(self.test_obj.status(), SAI_STATUS_SUCCESS) + + entry = sai_thrift_route_entry_t(vr_id=self.test_obj.default_vrf, + destination=sai_ipprefix(LOCAL_IP_128V6_PREFIX)) + self.test_obj.local_128v6_route_entry = sai_thrift_create_route_entry( + self.client, + route_entry=entry, + packet_action=SAI_PACKET_ACTION_FORWARD) + self.test_obj.assertEqual(self.test_obj.status(), SAI_STATUS_SUCCESS) + + def create_route_and_neighbor_entry_for_port(self, ip_addr, mac_addr, port_id, virtual_router_id=None): + if virtual_router_id is None: + virtual_router_id = self.test_obj.default_vrf + + rif_id1 = sai_thrift_create_router_interface(self.client, virtual_router_id=virtual_router_id, type=SAI_ROUTER_INTERFACE_TYPE_PORT, port_id=port_id) + + nbr_entry_v4 = sai_thrift_neighbor_entry_t(rif_id=rif_id1, ip_address=sai_ipaddress(ip_addr)) + sai_thrift_create_neighbor_entry(self.client, nbr_entry_v4, dst_mac_address=mac_addr) + + nhop = sai_thrift_create_next_hop(self.client, ip=sai_ipaddress(ip_addr), router_interface_id=rif_id1, type=SAI_NEXT_HOP_TYPE_IP) + route1 =sai_thrift_route_entry_t(vr_id=virtual_router_id, destination=sai_ipprefix(ip_addr+'/24')) + sai_thrift_create_route_entry(self.client, route1, next_hop_id=nhop) diff --git a/test/sai_test/sai_lag_test.py b/test/sai_test/sai_lag_test.py new file mode 100644 index 000000000..653bd3a03 --- /dev/null +++ b/test/sai_test/sai_lag_test.py @@ -0,0 +1,233 @@ +# Copyright (c) 2021 Microsoft Open Technologies, Inc. +# +# 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. +# +# + +from sai_test_base import T0TestBase +from sai_utils import * + +class LagConfigTest(T0TestBase): + """ + Verify the load-balance of l3 + """ + + def setUp(self): + """ + Test the basic setup process. + """ + T0TestBase.setUp(self) + + def load_balance_on_src_ip(self): + sai_thrift_create_router_interface(self.client, virtual_router_id=self.default_vrf, type=SAI_ROUTER_INTERFACE_TYPE_PORT, port_id=self.port_list[21]) + router_mac = '00:77:66:55:44:00' + ip_src1 = '192.168.0.1' + ip_src2 = '192.168.0.2' + ip_dst = '10.10.10.1' + pkt1 = simple_tcp_packet(eth_dst=router_mac, + eth_src='00:22:22:22:22:22', + ip_dst=ip_dst, + ip_src=ip_src1, + ip_id=105, + ip_ttl=64) + pkt2 = simple_tcp_packet(eth_dst=router_mac, + eth_src='00:22:22:22:22:22', + ip_dst=ip_dst, + ip_src=ip_src2, + ip_id=105, + ip_ttl=64) + exp_pkt1 = simple_tcp_packet(eth_dst='02:04:02:01:01:01', + eth_src=router_mac, + ip_dst=ip_dst, + ip_src=ip_src1, + ip_id=105, + ip_ttl=63) + exp_pkt2 = simple_tcp_packet(eth_dst='02:04:02:01:01:01', + eth_src=router_mac, + ip_dst=ip_dst, + ip_src=ip_src2, + ip_id=105, + ip_ttl=63) + + send_packet(self, 21, pkt1) + verify_packet_any_port(self, exp_pkt1, [17, 18]) + send_packet(self, 21, pkt2) + verify_packet_any_port(self, exp_pkt2, [17, 18]) + + def runTest(self): + try: + self.load_balance_on_src_ip() + finally: + pass + +class LoadbalanceOnSrcPortTest(T0TestBase): + """ + Test load balance of l3 by source port. + """ + def setUp(self): + T0TestBase.setUp(self) + + def runTest(self): + try: + print("Lag l3 load balancing test based on src port") + sai_thrift_create_router_interface(self.client, virtual_router_id=self.default_vrf, type=SAI_ROUTER_INTERFACE_TYPE_PORT, port_id=self.port_list[21]) + eth_src = '00:22:22:22:22:22' + eth_dst = '00:77:66:55:44:00' + ip_src = '192.168.0.1' + ip_dst = '10.10.10.1' + + max_itrs = 150 + begin_port = 2000 + rcv_count = [0, 0] + for i in range(0, max_itrs): + src_port = begin_port + i + pkt = simple_tcp_packet(eth_dst=eth_dst, + eth_src=eth_src, + ip_dst=ip_dst, + ip_src=ip_src, + tcp_sport=src_port, + ip_id=105, + ip_ttl=64) + exp_pkt = simple_tcp_packet(eth_dst='02:04:02:01:01:01', + eth_src=eth_dst, + ip_dst=ip_dst, + ip_src=ip_src, + tcp_sport=src_port, + ip_id=105, + ip_ttl=63) + send_packet(self, 21, pkt) + rcv_idx, _ = verify_packet_any_port(self, exp_pkt, [17, 18]) + print('src_port={}, rcv_port={}'.format(src_port, rcv_idx)) + rcv_count[rcv_idx] += 1 + + print(rcv_count) + for i in range(0, 2): + self.assertTrue((rcv_count[i] >= ((max_itrs/2) * 0.8)), "Not all paths are equally balanced") + finally: + pass + + +class DisableEgressTest(T0TestBase): + """ + When disable egress on a lag member, we expect traffic drop on the disabled lag member. + """ + def setUp(self): + T0TestBase.setUp(self) + + def runTest(self): + try: + print("Lag disable egress lag member test") + sai_thrift_create_router_interface(self.client, virtual_router_id=self.default_vrf, type=SAI_ROUTER_INTERFACE_TYPE_PORT, port_id=self.port_list[21]) + eth_src = '00:22:22:22:22:22' + eth_dst = '00:77:66:55:44:00' + ip_src = '192.168.0.1' + ip_dst = '10.10.10.1' + + pkts_num = 10 + begin_port = 2000 + exp_drop = [] + for i in range(0, pkts_num): + src_port = begin_port + i + pkt = simple_tcp_packet(eth_dst=eth_dst, + eth_src=eth_src, + ip_dst=ip_dst, + ip_src=ip_src, + tcp_sport=src_port, + ip_id=105, + ip_ttl=64) + exp_pkt = simple_tcp_packet(eth_dst='02:04:02:01:01:01', + eth_src=eth_dst, + ip_dst=ip_dst, + ip_src=ip_src, + tcp_sport=src_port, + ip_id=105, + ip_ttl=63) + send_packet(self, 21, pkt) + rcv_idx, _ = verify_packet_any_port(self, exp_pkt, [17, 18]) + if rcv_idx == 18: + exp_drop.append(src_port) + + # disable egress of lag member: port18 + print("disable port18 egress") + status = sai_thrift_set_lag_member_attribute(self.client, self.lag1.lag_members[1], egress_disable=True) + self.assertEqual(status, SAI_STATUS_SUCCESS) + + for i in range(0, pkts_num): + src_port = begin_port + i + pkt = simple_tcp_packet(eth_dst=eth_dst, + eth_src=eth_src, + ip_dst=ip_dst, + ip_src=ip_src, + tcp_sport=src_port, + ip_id=105, + ip_ttl=64) + exp_pkt = simple_tcp_packet(eth_dst='02:04:02:01:01:01', + eth_src=eth_dst, + ip_dst=ip_dst, + ip_src=ip_src, + tcp_sport=src_port, + ip_id=105, + ip_ttl=63) + send_packet(self, 21, pkt) + if src_port in exp_drop: + verify_no_packet(self, exp_pkt, 18) + else: + verify_packet(self, exp_pkt, 17) + finally: + pass + + +class IndifferenceIngressPortTest(T0TestBase): + """ + Verify the ingress ports should not be as a hash factor in lag load balance. + Forwarding the same packet from different ingress ports, if only the ingress + port changed, the load balance should not happen among lag members. + """ + def setUp(self): + T0TestBase.setUp(self) + + def runTest(self): + try: + sai_thrift_create_router_interface(self.client, virtual_router_id=self.default_vrf, type=SAI_ROUTER_INTERFACE_TYPE_VLAN, vlan_id=10) + eth_src = '00:22:22:22:22:22' + eth_dst = '00:77:66:55:44:00' + ip_src = '192.168.0.1' + ip_dst = '10.10.10.1' + + pkt = simple_tcp_packet(eth_dst=eth_dst, + eth_src=eth_src, + ip_dst=ip_dst, + ip_src=ip_src, + ip_id=105, + ip_ttl=64) + exp_pkt = simple_tcp_packet(eth_dst='02:04:02:01:01:01', + eth_src=eth_dst, + ip_dst=ip_dst, + ip_src=ip_src, + ip_id=105, + ip_ttl=63) + + exp_port_idx = -1 + exp_port_list = [17, 18] + for i in range(1, 9): + send_packet(self, i, pkt) + if exp_port_idx == -1: + exp_port_idx, _ = verify_packet_any_port(self, exp_pkt, exp_port_list) + else: + verify_packet(self, exp_pkt, exp_port_list[exp_port_idx]) + finally: + pass diff --git a/test/sai_test/sai_sanity_test.py b/test/sai_test/sai_sanity_test.py index 1fe0ac5b5..e2994d509 100644 --- a/test/sai_test/sai_sanity_test.py +++ b/test/sai_test/sai_sanity_test.py @@ -46,7 +46,7 @@ def runTest(self): def tearDown(self): """ - Test the basic tearDown proecss + Test the basic tearDown process """ pass diff --git a/test/sai_test/sai_test_base.py b/test/sai_test/sai_test_base.py index 44398568c..ebed801b6 100644 --- a/test/sai_test/sai_test_base.py +++ b/test/sai_test/sai_test_base.py @@ -41,6 +41,10 @@ from config.vlan_configer import VlanConfiger from config.fdb_configer import t0_fdb_config_helper from config.fdb_configer import FdbConfiger +from config.lag_configer import t0_lag_config_helper +from config.lag_configer import LagConfiger +from config.route_configer import t0_route_config_helper +from config.route_configer import RouteConfiger THRIFT_PORT = 9092 is_configured = False @@ -176,11 +180,17 @@ class T0TestBase(ThriftInterfaceDataPlane): Set the following class attributes: self.default_vlan_id self.default_vrf + self.lookback_intf + self.default_ipv4_route_entry + self.default_ipv6_route_entry + self.local_10v6_route_entry + self.local_128v6_route_entry self.default_1q_bridge self.cpu_port_hdl self.active_ports_no - number of active ports self.port_list - list of all active port objects self.portX objects for all active ports (where X is a port number) + self.lagX objects for all lag """ def setUp(self, @@ -190,12 +200,17 @@ def setUp(self, is_reset_default_vlan=True, is_create_vlan=True, is_create_fdb=True, + is_create_route=True, + is_create_lag=True, + is_create_route_for_lag=True, wait_sec=5): super(T0TestBase, self).setUp() self.port_configer = PortConfiger(self) self.switch_configer = SwitchConfiger(self) self.fdb_configer = FdbConfiger(self) self.vlan_configer = VlanConfiger(self) + self.route_configer = RouteConfiger(self) + self.lag_configer = LagConfiger(self) if force_config or not is_configured: t0_switch_config_helper(self) @@ -210,6 +225,14 @@ def setUp(self, t0_fdb_config_helper( test_obj=self, is_create_fdb=is_create_fdb) + t0_lag_config_helper( + test_obj=self, + is_create_lag=is_create_lag) + t0_route_config_helper( + test_obj=self, + is_create_route=is_create_route, + is_create_route_for_lag=is_create_route_for_lag) + print("Waiting for switch to get ready before test, {} seconds ...".format( wait_sec)) time.sleep(wait_sec) diff --git a/test/sai_test/sai_utils.py b/test/sai_test/sai_utils.py index 0e7a2a55e..400db63ce 100644 --- a/test/sai_test/sai_utils.py +++ b/test/sai_test/sai_utils.py @@ -114,3 +114,26 @@ def get_port_config(self): port_conf = self.config_json.get('PORT') key_0 = list(port_conf.keys())[0] return self.config_json.get('PORT').get(key_0) + + +def sai_ipaddress(addr_str): + """ + Set SAI IP address, assign appropriate type and return + sai_thrift_ip_address_t object + + Args: + addr_str (str): IP address string + + Returns: + sai_thrift_ip_address_t: object containing IP address family and number + """ + + if '.' in addr_str: + family = SAI_IP_ADDR_FAMILY_IPV4 + addr = sai_thrift_ip_addr_t(ip4=addr_str) + if ':' in addr_str: + family = SAI_IP_ADDR_FAMILY_IPV6 + addr = sai_thrift_ip_addr_t(ip6=addr_str) + ip_addr = sai_thrift_ip_address_t(addr_family=family, addr=addr) + + return ip_addr