From b78c82f48b741c5165187014fddebd3fa45703ba Mon Sep 17 00:00:00 2001 From: Volodymyr Mytnyk Date: Mon, 19 Sep 2022 13:10:46 +0000 Subject: [PATCH 1/8] ptf: add vnet sample test cases for dash - Common VNet API for all tests - Test cases: - Inbound VNI/ENI - Inbound Routing PA validate - Inbound Routing VNI match - Inbound Routing PA SRC IP - Outbound Routing Vnet direct - Outbound Routing direct - CT (just placeholder) - Route (basic) Signed-off-by: Volodymyr Mytnyk Signed-off-by: Yuriy Harhas --- .../saithrift/ptf/platform_helper/__init__.py | 28 + .../ptf/platform_helper/common_sai_helper.py | 73 ++ .../tests/saithrift/ptf/sai_base_test.py | 1014 +++++++++++++++++ .../tests/saithrift/ptf/sai_utils.py | 329 ++++++ .../tests/saithrift/ptf/saidashvnet.py | 748 ++++++++++++ 5 files changed, 2192 insertions(+) create mode 100644 dash-pipeline/tests/saithrift/ptf/platform_helper/__init__.py create mode 100644 dash-pipeline/tests/saithrift/ptf/platform_helper/common_sai_helper.py create mode 100644 dash-pipeline/tests/saithrift/ptf/sai_base_test.py create mode 100644 dash-pipeline/tests/saithrift/ptf/sai_utils.py create mode 100644 dash-pipeline/tests/saithrift/ptf/saidashvnet.py diff --git a/dash-pipeline/tests/saithrift/ptf/platform_helper/__init__.py b/dash-pipeline/tests/saithrift/ptf/platform_helper/__init__.py new file mode 100644 index 000000000..101a1a2ea --- /dev/null +++ b/dash-pipeline/tests/saithrift/ptf/platform_helper/__init__.py @@ -0,0 +1,28 @@ +# 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. +# +# @file __init__.py +# +# @brief init +# + +""" +Init the platform helper module. + +platform_helper module contains classes to distiguish the different behivor on different platforms. +""" diff --git a/dash-pipeline/tests/saithrift/ptf/platform_helper/common_sai_helper.py b/dash-pipeline/tests/saithrift/ptf/platform_helper/common_sai_helper.py new file mode 100644 index 000000000..ad9209e10 --- /dev/null +++ b/dash-pipeline/tests/saithrift/ptf/platform_helper/common_sai_helper.py @@ -0,0 +1,73 @@ +# 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. + +""" +Class contains common functions. + +This file contains base class for other platform classes. +""" +from sai_base_test import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] + +class CommonSaiHelper(SaiHelper): + """ + This class contains the common functions for the platform setup and test context configuration. + """ + #TODO move the common methods from the sai_base_test. + + platform = 'common' + + def sai_thrift_create_fdb_entry_allow_mac_move(self, + client, + fdb_entry, + type=None, + packet_action=None, + user_trap_id=None, + bridge_port_id=None, + meta_data=None, + endpoint_ip=None, + counter_id=None, + allow_mac_move=None): + """ + Override the sai_thrift_create_fdb_entry when check the functionality related to allow_mac_move. + + This method will transfer allow_mac_move directly(not override). + For the encounter function, please refer to \r + \t :func:`BrcmSaiHelper.sai_thrift_create_fdb_entry_allow_mac_move` + """ + #TODO confirm the SPEC. Related to RFC9014 and RFC7432 + print("CommonSaiHelper::sai_thrift_create_fdb_entry_allow_mac_move") + sai_thrift_create_fdb_entry( + client=client, + fdb_entry=fdb_entry, + type=type, + packet_action=packet_action, + user_trap_id=user_trap_id, + bridge_port_id=bridge_port_id, + meta_data=meta_data, + endpoint_ip=endpoint_ip, + counter_id=counter_id, + allow_mac_move=allow_mac_move) + + + def remove_bridge_port(self): + """ + Remove all bridge ports. + """ + for index in range(0, len(self.port_list)): + port_bp = getattr(self, 'port%s_bp' % index) + sai_thrift_remove_bridge_port(self.client, port_bp) diff --git a/dash-pipeline/tests/saithrift/ptf/sai_base_test.py b/dash-pipeline/tests/saithrift/ptf/sai_base_test.py new file mode 100644 index 000000000..b43311f59 --- /dev/null +++ b/dash-pipeline/tests/saithrift/ptf/sai_base_test.py @@ -0,0 +1,1014 @@ +# Copyright 2021-present Intel Corporation. +# +# 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This file contains base classes for PTF test cases as well as a set of +additional useful functions. + +Tests will usually inherit from one of the base classes to have the controller +and/or dataplane automatically set up. +""" +import os +import time +from threading import Thread + +from collections import OrderedDict +from unittest import SkipTest + +from ptf import config +from ptf.base_tests import BaseTest + +from thrift.transport import TSocket +from thrift.transport import TTransport +from thrift.protocol import TBinaryProtocol + +from sai_thrift import sai_rpc + +from sai_utils import * +import sai_thrift.sai_adapter as adapter + +ROUTER_MAC = '00:77:66:55:44:00' +THRIFT_PORT = 9092 + +SKIP_TEST_NO_RESOURCES_MSG = 'Not enough resources to run test' +PLATFORM = os.environ.get('PLATFORM') +platform_map = {'broadcom': 'brcm', 'barefoot': 'bfn', + 'mellanox': 'mlnx', 'common': 'common'} + + +class ThriftInterface(BaseTest): + """ + Get and format a port map, retrieve test params, and create an RPC client + """ + def setUp(self): + super(ThriftInterface, self).setUp() + + self.interface_to_front_mapping = {} + self.port_map_loaded = False + self.transport = None + + self.test_params = test_params_get() + self.loadPortMap() + self.createRpcClient() + + def tearDown(self): + self.transport.close() + + super(ThriftInterface, self).tearDown() + + def loadPortMap(self): + """ + Get and format port_map + + port_map_file is a port map with following lines format: + [test_port_no]@[device_port_name] + e.g.: + 0@Veth1 + 1@Veth2 + 2@Veth3 ... + """ + if self.port_map_loaded: + print("port_map already loaded") + return + + if "port_map" in self.test_params: + user_input = self.test_params['port_map'] + splitted_map = user_input.split(",") + for item in splitted_map: + iface_front_pair = item.split("@") + self.interface_to_front_mapping[iface_front_pair[0]] = \ + iface_front_pair[1] + elif "port_map_file" in self.test_params: + user_input = self.test_params['port_map_file'] + with open(user_input, 'r') as map_file: + for line in map_file: + if (line and (line[0] == '#' or + line[0] == ';' or line[0] == '/')): + continue + iface_front_pair = line.split("@") + self.interface_to_front_mapping[iface_front_pair[0]] = \ + iface_front_pair[1].strip() + + self.port_map_loaded = True + + def createRpcClient(self): + """ + Set up thrift client and contact RPC server + """ + + if 'thrift_server' in self.test_params: + server = self.test_params['thrift_server'] + else: + server = 'localhost' + + self.transport = TSocket.TSocket(server, THRIFT_PORT) + self.transport = TTransport.TBufferedTransport(self.transport) + self.protocol = TBinaryProtocol.TBinaryProtocol(self.transport) + + self.client = sai_rpc.Client(self.protocol) + self.transport.open() + + +class ThriftInterfaceDataPlane(ThriftInterface): + """ + Sets up the thrift interface and dataplane + """ + def setUp(self): + super(ThriftInterfaceDataPlane, self).setUp() + + self.dataplane = ptf.dataplane_instance + if self.dataplane is not None: + self.dataplane.flush() + if config['log_dir'] is not None: + filename = os.path.join(config['log_dir'], str(self)) + ".pcap" + self.dataplane.start_pcap(filename) + + def tearDown(self): + if config['log_dir'] is not None: + self.dataplane.stop_pcap() + super(ThriftInterfaceDataPlane, self).tearDown() + + +class SaiHelperBase(ThriftInterfaceDataPlane): + """ + SAI test helper base class without initial switch ports setup + + Set the following class attributes: + self.default_vlan_id + self.default_vrf + 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) + """ + + platform = 'common' + + def get_active_port_list(self): + ''' + Method to get the active port list base on number_of_active_ports + + Sets the following class attributes: + + self.active_ports_no - number of active ports + + self.port_list - list of all active port objects + + self.portX objects for all active ports + ''' + + # get number of active ports + attr = sai_thrift_get_switch_attribute( + self.client, number_of_active_ports=True) + self.active_ports_no = attr['number_of_active_ports'] + + # get port_list and portX objects + attr = sai_thrift_get_switch_attribute( + self.client, port_list=sai_thrift_object_list_t( + idlist=[], count=self.active_ports_no)) + self.assertEqual(self.active_ports_no, attr['port_list'].count) + self.port_list = attr['port_list'].idlist + + #Gets self.portX objects for all active ports + for i, _ in enumerate(self.port_list): + setattr(self, 'port%s' % i, self.port_list[i]) + + + def turn_up_and_check_ports(self): + ''' + Method to turn up the ports. + ''' + #TODO check if this is common behivor or specified after check on more platform + print("For Common platform, Port already setup in recreate_ports.") + + + def shell(self): + ''' + Method use to start a sai shell in a thread. + ''' + def start_shell(): + sai_thrift_set_switch_attribute(self.client, switch_shell_enable=True) + thread = Thread(target = start_shell) + thread.start() + + + def recreate_ports(self): + ''' + Recreate the port base on file specified in 'port_config_ini' param. + ''' + #TODO check if this is common behivor or specified after check on more platform + if 'port_config_ini' in self.test_params: + if 'createPorts_has_been_called' not in config: + self.createPorts() + # check if ports became UP + #self.checkPortsUp() + config['createPorts_has_been_called'] = 1 + + + def get_default_1q_bridge_id(self): + ''' + Gets default 1q bridge 1d, set it to class attribute 'default_1q_bridge'. + + Sets the following class attributes: + + self.default_1q_bridge - default_1q_bridge_id + ''' + + attr = sai_thrift_get_switch_attribute( + self.client, default_1q_bridge_id=True) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.default_1q_bridge = attr['default_1q_bridge_id'] + + + def reset_1q_bridge_ports(self): + ''' + Reset all the 1Q bridge ports. + Needs the following class attributes: + self.default_1q_bridge - default_1q_bridge oid + + self.active_ports_no - number of active ports + + self.portX objects for all active ports + ''' + #TODO check if this is common behivor or specified after check on more platform + #TODO move this function to CommonSaiHelper + print("For Common platform, expecting bridge ports not been created by default.") + + + def check_cpu_port_hdl(self): + """ + Checks cpu port handler. + Expect the cpu_port_hdl equals to qos_queue port id, number_of_queues in qos equals to queue index. + + Needs the following class attributes: + + self.cpu_port_hdl - cpu_port_hdl id + + Seds the following class attributes: + + self.cpu_queueX - cpu queue id + + """ + #TODO move this function to CommonSaiHelper + attr = sai_thrift_get_port_attribute(self.client, + self.cpu_port_hdl, + qos_number_of_queues=True) + num_queues = attr['qos_number_of_queues'] + q_list = sai_thrift_object_list_t(count=num_queues) + attr = sai_thrift_get_port_attribute(self.client, + self.cpu_port_hdl, + qos_queue_list=q_list) + for queue in range(0, num_queues): + queue_id = attr['qos_queue_list'].idlist[queue] + setattr(self, 'cpu_queue%s' % queue, queue_id) + q_attr = sai_thrift_get_queue_attribute( + self.client, + queue_id, + port=True, + index=True, + parent_scheduler_node=True) + self.assertEqual(queue, q_attr['index']) + self.assertEqual(self.cpu_port_hdl, q_attr['port']) + + + def start_switch(self): + """ + Start switch. + """ + self.switch_id = sai_thrift_create_switch( + self.client, init_switch=True, src_mac_address=ROUTER_MAC) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + + + def setUp(self): + super(SaiHelperBase, self).setUp() + + self.getSwitchPorts() + # initialize switch + self.start_switch() + + self.switch_resources = self.saveNumberOfAvaiableResources(debug=True) + + # get default vlan + attr = sai_thrift_get_switch_attribute( + self.client, default_vlan_id=True) + self.default_vlan_id = attr['default_vlan_id'] + self.assertNotEqual(self.default_vlan_id, 0) + + self.recreate_ports() + + # get number of active ports + self.get_active_port_list() + + # get default vrf + attr = sai_thrift_get_switch_attribute( + self.client, default_virtual_router_id=True) + self.default_vrf = attr['default_virtual_router_id'] + self.assertNotEqual(self.default_vrf, 0) + + self.turn_up_and_check_ports() + + # get default 1Q bridge OID + self.get_default_1q_bridge_id() + + #remove all default 1Q bridge port + self.reset_1q_bridge_ports() + + # get cpu port + attr = sai_thrift_get_switch_attribute(self.client, cpu_port=True) + self.cpu_port_hdl = attr['cpu_port'] + self.assertNotEqual(self.cpu_port_hdl, 0) + + # get cpu port queue handles + self.check_cpu_port_hdl() + + print("Finish SaiHelperBase setup") + + + def tearDown(self): + try: + for port in self.port_list: + sai_thrift_clear_port_stats(self.client, port) + sai_thrift_set_port_attribute( + self.client, port, port_vlan_id=0) + #Todo: Remove this condition after brcm's remove_switch issue fixed + if get_platform() == 'brcm': + return + self.assertTrue(self.verifyNumberOfAvaiableResources( + self.switch_resources, debug=False)) + finally: + super(SaiHelperBase, self).tearDown() + + + def createPorts(self): + """ + Create ports after reading from port config file + """ + def fec_str_to_int(fec): + """ + Convert fec string to SAI enum + + Args: + fec (string): fec string from port_config + + Returns: + int: SAI enum value + """ + fec_dict = { + 'rs': SAI_PORT_FEC_MODE_RS, + 'fc': SAI_PORT_FEC_MODE_FC + } + return fec_dict.get(fec, SAI_PORT_FEC_MODE_NONE) + + # delete the existing ports + attr = sai_thrift_get_switch_attribute( + self.client, number_of_active_ports=True) + self.active_ports_no = attr['number_of_active_ports'] + attr = sai_thrift_get_switch_attribute( + self.client, port_list=sai_thrift_object_list_t( + idlist=[], count=self.active_ports_no)) + if self.active_ports_no: + self.port_list = attr['port_list'].idlist + for port in self.port_list: + sai_thrift_remove_port(self.client, port) + + # add new ports from port config file + self.ports_config = self.parsePortConfig( + self.test_params['port_config_ini']) + for name, port in self.ports_config.items(): + print("Creating port: %s" % name) + fec_mode = fec_str_to_int(port.get('fec', None)) + auto_neg_mode = True if port.get( + 'autoneg', "").lower() == "on" else False + sai_list = sai_thrift_u32_list_t( + count=len(port['lanes']), uint32list=port['lanes']) + sai_thrift_create_port(self.client, + hw_lane_list=sai_list, + fec_mode=fec_mode, + auto_neg_mode=auto_neg_mode, + speed=port['speed'], + admin_state=True) + + def parsePortConfig(self, port_config_file): + """ + Parse port_config.ini file + + Example of supported format for port_config.ini: + # name lanes alias index speed autoneg fec + Ethernet0 0 Ethernet0 1 25000 off none + Ethernet1 1 Ethernet1 1 25000 off none + Ethernet2 2 Ethernet2 1 25000 off none + Ethernet3 3 Ethernet3 1 25000 off none + Ethernet4 4 Ethernet4 2 25000 off none + Ethernet5 5 Ethernet5 2 25000 off none + Ethernet6 6 Ethernet6 2 25000 off none + Ethernet7 7 Ethernet7 2 25000 off none + Ethernet8 8 Ethernet8 3 25000 off none + Ethernet9 9 Ethernet9 3 25000 off none + Ethernet10 10 Ethernet10 3 25000 off none + Ethernet11 11 Ethernet11 3 25000 off none + etc + + Args: + port_config_file (string): path to port config file + + Returns: + dict: port configuation from file + + Raises: + e: exit if file not found + """ + ports = OrderedDict() + try: + with open(port_config_file) as conf: + for line in conf: + if line.startswith('#'): + if "name" in line: + titles = line.strip('#').split() + continue + tokens = line.split() + if len(tokens) < 2: + continue + name_index = titles.index('name') + name = tokens[name_index] + data = {} + for i, item in enumerate(tokens): + if i == name_index: + continue + data[titles[i]] = item + data['lanes'] = [int(lane) + for lane in data['lanes'].split(',')] + data['speed'] = int(data['speed']) + ports[name] = data + return ports + except Exception as e: + raise e + + def checkPortsUp(self, timeout=30): + """ + Wait for all ports to be UP + This may be required while testing on hardware + The test fails if all ports are not UP after timeout + + Args: + timeout (int): port verification timeout in sec + """ + allup = False + timer_start = time.time() + + while allup is False and time.time() - timer_start < timeout: + allup = True + for port in self.port_list: + attr = sai_thrift_get_port_attribute( + self.client, port, oper_status=True) + if attr['oper_status'] != SAI_SWITCH_OPER_STATUS_UP: + allup = False + break + if allup: + break + time.sleep(5) + + self.assertTrue(allup) + + def getSwitchPorts(self): + """ + Get device port numbers + """ + dev_no = 0 + for _, port, _ in config['interfaces']: + # remove after DASH will be introduced as confuses with "dev" prefix + # added tg as a shortcut for "traffic generator" + setattr(self, 'dev_port%d' % dev_no, port) + setattr(self, 'tg%d' % dev_no, port) + dev_no += 1 + + def printNumberOfAvaiableResources(self, resources_dict): + """ + Prints numbers of available resources + + Args: + resources_dict (dict): a dictionary with resources numbers + """ + + print("***** Number of available resources *****") + for key, value in resources_dict.items(): + print(key, ": ", value) + + def saveNumberOfAvaiableResources(self, debug=False): + """ + Save number of available resources + This allows to verify if all the test objects were removed + + Args: + debug (bool): enables debug option + Return: + dict: switch_resources dictionary with available resources + """ + + switch_resources = sai_thrift_get_switch_attribute( + self.client, + available_ipv4_route_entry=True, + available_ipv6_route_entry=True, + available_ipv4_nexthop_entry=True, + available_ipv6_nexthop_entry=True, + available_ipv4_neighbor_entry=True, + available_ipv6_neighbor_entry=True, + available_next_hop_group_entry=True, + available_next_hop_group_member_entry=True, + available_fdb_entry=True, + available_ipmc_entry=True, + available_snat_entry=True, + available_dnat_entry=True, + available_double_nat_entry=True, + number_of_ecmp_groups=True, + ecmp_members=True) + + if debug: + self.printNumberOfAvaiableResources(switch_resources) + + return switch_resources + + def verifyNumberOfAvaiableResources(self, init_resources, debug=False): + """ + Verify number of available resources + + Args: + init_resources (dict): a dictionary with initial resources numbers + debug (bool): enable debug option + + Returns: + bool: True if the numbers of resources are the same as before tests + """ + + available_resources = sai_thrift_get_switch_attribute( + self.client, + available_ipv4_route_entry=True, + available_ipv6_route_entry=True, + available_ipv4_nexthop_entry=True, + available_ipv6_nexthop_entry=True, + available_ipv4_neighbor_entry=True, + available_ipv6_neighbor_entry=True, + available_next_hop_group_entry=True, + available_next_hop_group_member_entry=True, + available_fdb_entry=True, + available_ipmc_entry=True, + available_snat_entry=True, + available_dnat_entry=True, + available_double_nat_entry=True, + number_of_ecmp_groups=True, + ecmp_members=True) + + for key, value in available_resources.items(): + if value != init_resources[key]: + if debug: + print("Number of %s incorrect! Current value: %d, Init value: %d" % (key, value, init_resources[key])) + return False + + return True + + @staticmethod + def status(): + """ + Returns the last operation status. + + Returns: + int: sai call result + """ + return adapter.status + + @staticmethod + def saiWaitFdbAge(timeout): + """ + Wait for fdb entry to ageout + + Args: + timeout (int): Timeout value in seconds + """ + print("Waiting for fdb entry to age") + aging_interval_buffer = 10 + time.sleep(timeout + aging_interval_buffer) + + +class SaiHelperUtilsMixin: + """ + Mixin utils class providing API for convenient SAI objects creation/deletion + """ + + def create_bridge_ports(self, ports=None): + """ + Create bridge ports base on port_list. + """ + ports = ports or range(0, len(self.port_list)) + for port_index in ports: + port_id = getattr(self, 'port%s' % port_index) + port_bp = sai_thrift_create_bridge_port( + self.client, + bridge_id=self.default_1q_bridge, + port_id=port_id, + type=SAI_BRIDGE_PORT_TYPE_PORT, + admin_state=True) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + setattr(self, 'port%s_bp' % port_index, port_bp) + self.def_bridge_port_list.append(port_bp) + + def destroy_bridge_ports(self): + for bridge_port in self.def_bridge_port_list: + sai_thrift_remove_bridge_port(self.client, bridge_port) + + def create_lag_with_members(self, lag_index, ports): + # create lag + lag_id = sai_thrift_create_lag(self.client) + setattr(self, 'lag%s' % lag_index, lag_id) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.def_lag_list.append(lag_id) + + # add LAG to bridge + lag_bp = sai_thrift_create_bridge_port( + self.client, + bridge_id=self.default_1q_bridge, + port_id=lag_id, + type=SAI_BRIDGE_PORT_TYPE_PORT, + admin_state=True) + + setattr(self, 'lag%s_bp' % lag_index, lag_bp) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.def_bridge_port_list.append(lag_bp) + + # add lag members + for member_index in ports: + port_id = getattr(self, 'port%s' % member_index) + lag_member = sai_thrift_create_lag_member( + self.client, lag_id=lag_id, port_id=port_id) + setattr(self, "lag%s_member%s" % (lag_index, member_index), lag_member) + + self.def_lag_member_list.append(lag_member) + + def destroy_lags_with_members(self): + for lag_member in self.def_lag_member_list: + sai_thrift_remove_lag_member(self.client, lag_member) + for lag in self.def_lag_list: + sai_thrift_remove_lag(self.client, lag) + + def create_vlan_with_members(self, vlan_index, members): + # create vlan + vlan_id = sai_thrift_create_vlan(self.client, vlan_id=vlan_index) + setattr(self, 'vlan%s' % vlan_index, vlan_id) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.def_vlan_list.append(vlan_id) + + # add members + idx = 0 + for member, tag in members.items(): + tag = SAI_VLAN_TAGGING_MODE_UNTAGGED + if tag == 'tagged': + tag = SAI_VLAN_TAGGING_MODE_TAGGED + vlan_member_id = sai_thrift_create_vlan_member( + self.client, + vlan_id=vlan_id, + bridge_port_id=member, + vlan_tagging_mode=tag) + setattr(self, 'vlan%s_member%s' % (vlan_index, idx), vlan_member_id) + self.def_vlan_member_list.append(vlan_member_id) + idx = idx + 1 + + def destroy_vlans_with_members(self): + for vlan_member in self.def_vlan_member_list: + sai_thrift_remove_vlan_member(self.client, vlan_member) + for vlan in self.def_vlan_list: + sai_thrift_remove_vlan(self.client, vlan) + + def create_routing_interfaces(self, vlans = None, lags = None, ports = None): + # iterate through vlans + if vlans is not None: + for vlan in vlans: + vlan_id = getattr(self, "vlan%s" % vlan) + vlan_rif = sai_thrift_create_router_interface( + self.client, + type=SAI_ROUTER_INTERFACE_TYPE_VLAN, + virtual_router_id=self.default_vrf, + vlan_id=vlan_id) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + setattr(self, "vlan%s_rif" % vlan, vlan_rif) + self.def_rif_list.append(vlan_rif) + + # iterate through lags + if lags is not None: + for lag in lags: + lag_id = getattr(self, "lag%s" % lag) + lag_rif = sai_thrift_create_router_interface( + self.client, + type=SAI_ROUTER_INTERFACE_TYPE_PORT, + virtual_router_id=self.default_vrf, + port_id=lag_id) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + setattr(self, "lag%s_rif" % lag, lag_rif) + self.def_rif_list.append(lag_rif) + + # iterate through ports + if ports is not None: + for port in ports: + port_id = getattr(self, 'port%s' % port) + port_rif = sai_thrift_create_router_interface( + self.client, + type=SAI_ROUTER_INTERFACE_TYPE_PORT, + virtual_router_id=self.default_vrf, + port_id=port_id) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + setattr(self, "port%s_rif" % port, port_rif) + self.def_rif_list.append(port_rif) + + def destroy_routing_interfaces(self): + for rif in self.def_rif_list: + sai_thrift_remove_router_interface(self.client, rif) + + +class SaiHelperSimplified(SaiHelperUtilsMixin, SaiHelperBase): + """ + SAI test helper class for DUT with limited port resources + without initial switch ports setup + """ + + def __getattr__(self, name): + """ + Skip the test in case of "port\d+" attribute does not exist + """ + # NOTE: check only ports for now + found = re.findall(r'^port\d+$', name) + if found and found[0]: + self.skipTest(SKIP_TEST_NO_RESOURCES_MSG) + + def setUp(self): + super(SaiHelperSimplified, self).setUp() + + # lists of default objects + self.def_bridge_port_list = [] + self.def_lag_list = [] + self.def_lag_member_list = [] + self.def_vlan_list = [] + self.def_vlan_member_list = [] + self.def_rif_list = [] + + def tearDown(self): + super(SaiHelperSimplified, self).tearDown() + + +class SaiHelper(SaiHelperUtilsMixin, SaiHelperBase): + """ + Set common base ports configuration for tests + + Common ports configuration: + * U/T = untagged/tagged VLAN member + +--------+------+-----------+-------------+--------+------------+------------+ + | Port | LAG | _member | Bridge port | VLAN | _member | RIF | + +========+======|===========+=============+========+============+============+ + | port0 | | | port0_bp | vlan10 | _member0 U | | + | port1 | | | port1_bp | | _member1 T | | + +--------+------+-----------+-------------+--------+------------+------------+ + | port2 | | | port2_bp | vlan20 | _member0 U | | + | port3 | | | port3_bp | | _member1 T | | + +--------+------+-----------+-------------+--------+------------+------------+ + | port4 | lag1 | _member4 | lag1_bp | vlan10 | _member2 U | | + | port5 | | _member5 | | | | | + | port6 | | _member6 | | | | | + +--------+------+-----------+-------------+--------+------------+------------+ + | port7 | lag2 | _member7 | lag2_bp | vlan20 | _member2 T | | + | port8 | | _member8 | | | | | + | port9 | | _member9 | | | | | + +--------+------+-----------+-------------+--------+------------+------------+ + | port10 | | | | | | port10_rif | + +--------+------+-----------+-------------+--------+------------+------------+ + | port11 | | | | | | port11_rif | + +--------+------+-----------+-------------+--------+------------+------------+ + | port12 | | | | | | port12_rif | + +--------+------+-----------+-------------+--------+------------+------------+ + | port13 | | | | | | port13_rif | + +--------+------+-----------+-------------+--------+------------+------------+ + | port14 | lag3 | _member14 | | | | lag3_rif | + | port15 | | _member15 | | | | | + | port16 | | _member16 | | | | | + +--------+------+-----------+-------------+--------+------------+------------+ + | port17 | lag4 | _member17 | | | | lag4_rif | + | port18 | | _member18 | | | | | + | port19 | | _member19 | | | | | + +--------+------+-----------+-------------+--------+------------+------------+ + | port20 | | | port20_bp | vlan30 | _member0 U | vlan30_rif | + | port21 | | | port21_bp | | _member1 T | | + +--------+------+-----------+-------------+--------+------------+------------+ + | port22 | lag5 | _member22 | lag5_bp | vlan30 | _member2 T | | + | port23 | | _member23 | | | | | + +--------+------+-----------+-------------+--------+------------+------------+ + | port24 | | + | port25 | | + | port26 | | + | port27 | UNASSIGNED | + | port28 | | + | port29 | | + | port30 | | + | port31 | | + +--------+-------------------------------------------------------------------+ + """ + + def setUp(self): + super(SaiHelper, self).setUp() + + # lists of default objects + self.def_bridge_port_list = [] + self.def_lag_list = [] + self.def_lag_member_list = [] + self.def_vlan_list = [] + self.def_vlan_member_list = [] + self.def_rif_list = [] + + # create bridge ports + self.create_bridge_ports(ports=[0, 1, 2, 3, 20, 21]) + + self.create_lag_with_members(1, ports=[4, 5, 6]) + self.create_lag_with_members(2, ports=[7, 8, 9]) + + # L3 lags + self.create_lag_with_members(3, ports=[14, 15, 16]) + self.create_lag_with_members(4, ports=[17, 18, 19]) + self.create_lag_with_members(5, ports=[22, 23]) + + # create vlan 10 with port0, port1 and lag1 + self.create_vlan_with_members(10, {self.port0_bp: 'untagged', + self.port1_bp: 'tagged', + self.lag1_bp: 'untagged'}) + + # create vlan 20 with port2, port3 and lag2 + self.create_vlan_with_members(20, {self.port2_bp: 'untagged', + self.port3_bp: 'tagged', + self.lag2_bp: 'untagged'}) + + # create vlan 30 with port20, port21 and lag5 + self.create_vlan_with_members(30, {self.port20_bp: 'untagged', + self.port21_bp: 'tagged', + self.lag5_bp: 'untagged'}) + + # setup untagged ports + sai_thrift_set_port_attribute(self.client, self.port0, port_vlan_id=10) + sai_thrift_set_lag_attribute(self.client, self.lag1, port_vlan_id=10) + sai_thrift_set_port_attribute(self.client, self.port2, port_vlan_id=20) + + # create L3 configuration + self.create_routing_interfaces(vlans=[30]) + self.create_routing_interfaces(lags=[3, 4]) + self.create_routing_interfaces(ports=[10, 11, 12, 13]) + + def tearDown(self): + sai_thrift_set_port_attribute(self.client, self.port2, port_vlan_id=0) + sai_thrift_set_lag_attribute(self.client, self.lag1, port_vlan_id=0) + sai_thrift_set_port_attribute(self.client, self.port0, port_vlan_id=0) + + self.destroy_routing_interfaces() + self.destroy_vlans_with_members() + self.destroy_bridge_ports() + self.destroy_lags_with_members() + + super(SaiHelper, self).tearDown() + + +class MinimalPortVlanConfig(SaiHelperBase): + """ + Minimal port and vlan configuration. Create port_num bridge ports and add + them to VLAN with vlan_id. Configure ports as untagged + """ + + def __init__(self, port_num, vlan_id=100): + """ + Args: + port_num (int): Number of ports to configure + vlan_id (int): ID of VLAN that will be created + """ + super(MinimalPortVlanConfig, self).__init__() + + self.port_num = port_num + self.vlan_id = vlan_id + + def setUp(self): + super(MinimalPortVlanConfig, self).setUp() + + if self.port_num > self.active_ports_no: + raise ValueError('Number of ports to configure %d is higher ' + 'than number of active ports %d' + % (self.port_num, self.active_ports_no)) + + self.def_bridge_port_list = [] + self.def_vlan_member_list = [] + + # create bridge ports + for port in self.port_list: + bp = sai_thrift_create_bridge_port( + self.client, bridge_id=self.default_1q_bridge, + port_id=port, type=SAI_BRIDGE_PORT_TYPE_PORT, + admin_state=True) + + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.def_bridge_port_list.append(bp) + + # create vlan + self.vlan = sai_thrift_create_vlan(self.client, vlan_id=self.vlan_id) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + + # add ports to vlan + for bridge_port in self.def_bridge_port_list: + vm = sai_thrift_create_vlan_member( + self.client, vlan_id=self.vlan, + bridge_port_id=bridge_port, + vlan_tagging_mode=SAI_VLAN_TAGGING_MODE_UNTAGGED) + + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.def_vlan_member_list.append(vm) + + # setup untagged ports + for port in self.port_list: + status = sai_thrift_set_port_attribute( + self.client, port, port_vlan_id=self.vlan_id) + + self.assertEqual(status, SAI_STATUS_SUCCESS) + + def tearDown(self): + # revert untagged ports configuration + for port in self.port_list: + sai_thrift_set_port_attribute( + self.client, port, port_vlan_id=0) + + # remove ports from vlan + for vlan_member in self.def_vlan_member_list: + sai_thrift_remove_vlan_member(self.client, vlan_member) + + # remove vlan + sai_thrift_remove_vlan(self.client, self.vlan) + + # remove bridge ports + for bridge_port in self.def_bridge_port_list: + sai_thrift_remove_bridge_port(self.client, bridge_port) + + super(MinimalPortVlanConfig, self).tearDown() + + +def get_platform(): + """ + Get the platform token. + + If environment variable [PLATFORM] doesn't exist, then the default platform will be 'common'. + If environment variable [PLATFORM] exist but platform name is unknown raise ValueError. + If environment variable [PLATFORM] exist but platform name is unspecified, + then the default platform will be 'common'. + If specified any one, it will try to concert it from standard name to a shortened name (case insensitive). \r + \ti.e. Broadcom -> brcm + """ + pl = 'common' + + if 'PLATFORM' in os.environ: + pl_low = PLATFORM.lower() + if pl_low in platform_map.keys(): + pl = platform_map[pl_low] + elif pl_low in platform_map.values(): + pl = pl_low + elif PLATFORM == '': + print("Platform not set. The common platform was selected") + else: + raise ValueError("Undefined platform: {}.".format(pl_low)) + else: + print("Platform not set. The common platform was selected") + + return pl + + +from platform_helper.common_sai_helper import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] +#from platform_helper.bfn_sai_helper import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] +#from platform_helper.brcm_sai_helper import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] +#from platform_helper.mlnx_sai_helper import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] + +class PlatformSaiHelper(SaiHelper): + """ + Class uses to extend from SaiHelper, base on the [platform] class attribute, + dynamic select a subclass from the platform_helper. + """ + def __new__(cls, *args, **kwargs): + sai_helper_subclass_map = {subclass.platform: subclass for subclass in SaiHelper.__subclasses__()} + common_sai_helper_subclass_map = {subclass.platform: subclass for subclass in CommonSaiHelper.__subclasses__()} + pl = get_platform() + + if pl in common_sai_helper_subclass_map: + target_base_class = common_sai_helper_subclass_map[pl] + else: + target_base_class = sai_helper_subclass_map[pl] + + cls.__bases__ = (target_base_class,) + + instance = target_base_class.__new__(cls, *args, **kwargs) + return instance diff --git a/dash-pipeline/tests/saithrift/ptf/sai_utils.py b/dash-pipeline/tests/saithrift/ptf/sai_utils.py new file mode 100644 index 000000000..da17e7fae --- /dev/null +++ b/dash-pipeline/tests/saithrift/ptf/sai_utils.py @@ -0,0 +1,329 @@ +# Copyright 2021-present Intel Corporation. +# +# 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Thrift SAI interface basic utils. +""" + +import time +import struct +import socket + +from functools import wraps + +from ptf.packet import * +from ptf.testutils import * + +from sai_thrift.sai_adapter import * + + +def sai_thrift_query_attribute_enum_values_capability(client, + obj_type, + attr_id=None): + """ + Call the sai_thrift_query_attribute_enum_values_capability() function + and return the list of supported aattr_is enum capabilities + + Args: + client (Client): SAI RPC client + obj_type (enum): SAI object type + attr_id (attr): SAI attribute name + + Returns: + list: list of switch object type enum capabilities + """ + max_cap_no = 20 + + enum_cap_list = client.sai_thrift_query_attribute_enum_values_capability( + obj_type, attr_id, max_cap_no) + + return enum_cap_list + + +def sai_thrift_object_type_get_availability(client, + obj_type, + attr_id=None, + attr_type=None): + """ + sai_thrift_object_type_get_availability() RPC client function + implementation + + Args: + client (Client): SAI RPC client + obj_type (enum): SAI object type + attr_id (attr): SAI attribute name + attr_type (type): SAI attribute type + + Returns: + uint: number of available resources with given parameters + """ + availability_cnt = client.sai_thrift_object_type_get_availability( + obj_type, attr_id, attr_type) + + return availability_cnt + + +def sai_thrift_object_type_query(client, + obj_id=None): + """ + sai_thrift_object_type_query() RPC client function + implementation + + Args: + client (Client): SAI RPC client + obj_id (obj): SAI object id + + Returns: + uint: object type + """ + obj_type = client.sai_object_type_query( + obj_id) + + return obj_type + + +def sai_thrift_switch_id_query(client, + obj_id=None): + """ + sai_thrift_switch_id_query() RPC client function + implementation + + Args: + client (Client): SAI RPC client + obj_id (obj): SAI object id + + Returns: + uint: object type + """ + switch_obj_id = client.sai_switch_id_query( + obj_id) + + return switch_obj_id + + +def sai_thrift_get_debug_counter_port_stats(client, port_oid, counter_ids): + """ + Get port statistics for given debug counters + + Args: + client (Client): SAI RPC client + port_oid (sai_thrift_object_id_t): object_id IN argument + counter_ids (sai_stat_id_t): list of requested counters + + Returns: + Dict[str, sai_thrift_uint64_t]: stats + """ + + stats = {} + counters = client.sai_thrift_get_port_stats(port_oid, counter_ids) + + for i, counter_id in enumerate(counter_ids): + stats[counter_id] = counters[i] + + return stats + + +def sai_thrift_get_debug_counter_switch_stats(client, counter_ids): + """ + Get switch statistics for given debug counters + + Args: + client (Client): SAI RPC client + counter_ids (sai_stat_id_t): list of requested counters + + Returns: + Dict[str, sai_thrift_uint64_t]: stats + """ + + stats = {} + counters = client.sai_thrift_get_switch_stats(counter_ids) + + for i, counter_id in enumerate(counter_ids): + stats[counter_id] = counters[i] + + return stats + + +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 + + +def sai_ipprefix(prefix_str): + """ + Set IP address prefix and mask and return ip_prefix object + + Args: + prefix_str (str): IP address and mask string (with slash notation) + + Return: + sai_thrift_ip_prefix_t: IP prefix object + """ + addr_mask = prefix_str.split('/') + if len(addr_mask) != 2: + print("Invalid IP prefix format") + return None + + if '.' in prefix_str: + family = SAI_IP_ADDR_FAMILY_IPV4 + addr = sai_thrift_ip_addr_t(ip4=addr_mask[0]) + mask = num_to_dotted_quad(addr_mask[1]) + mask = sai_thrift_ip_addr_t(ip4=mask) + if ':' in prefix_str: + family = SAI_IP_ADDR_FAMILY_IPV6 + addr = sai_thrift_ip_addr_t(ip6=addr_mask[0]) + mask = num_to_dotted_quad(int(addr_mask[1]), ipv4=False) + mask = sai_thrift_ip_addr_t(ip6=mask) + + ip_prefix = sai_thrift_ip_prefix_t( + addr_family=family, addr=addr, mask=mask) + return ip_prefix + + +def num_to_dotted_quad(address, ipv4=True): + """ + Helper function to convert the ip address + + Args: + address (str): IP address + ipv4 (bool): determines what IP version is handled + + Returns: + str: formatted IP address + """ + if ipv4 is True: + mask = (1 << 32) - (1 << 32 >> int(address)) + return socket.inet_ntop(socket.AF_INET, struct.pack('>L', mask)) + + mask = (1 << 128) - (1 << 128 >> int(address)) + i = 0 + result = '' + for sign in str(hex(mask)[2:]): + if (i + 1) % 4 == 0: + result = result + sign + ':' + else: + result = result + sign + i += 1 + return result[:-1] + + +def open_packet_socket(hostif_name): + """ + Open a linux socket + + Args: + hostif_name (str): socket interface name + + Return: + sock: socket ID + """ + eth_p_all = 3 + sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, + socket.htons(eth_p_all)) + sock.bind((hostif_name, eth_p_all)) + sock.setblocking(0) + + return sock + + +def socket_verify_packet(pkt, sock, timeout=2): + """ + Verify packet was received on a socket + + Args: + pkt (packet): packet to match with + sock (int): socket ID + timeout (int): timeout + + Return: + bool: True if packet matched + """ + max_pkt_size = 9100 + timeout = time.time() + timeout + match = False + + if isinstance(pkt, ptf.mask.Mask): + if not pkt.is_valid(): + return False + + while time.time() < timeout: + try: + packet_from_tap_device = Ether(sock.recv(max_pkt_size)) + + if isinstance(pkt, ptf.mask.Mask): + match = pkt.pkt_match(packet_from_tap_device) + else: + match = (str(packet_from_tap_device) == str(pkt)) + + if match: + break + + except BaseException: + pass + + return match + + +def delay_wrapper(func, delay=2): + """ + A wrapper extending given function by a delay + + Args: + func (function): function to be wrapped + delay (int): delay period in sec + + Return: + wrapped_function: wrapped function + """ + @wraps(func) + def wrapped_function(*args, **kwargs): + """ + A wrapper function adding a delay + + Args: + args (tuple): function arguments + kwargs (dict): keyword function arguments + + Return: + status: original function return value + """ + test_params = test_params_get() + if test_params['target'] != "hw": + time.sleep(delay) + + status = func(*args, **kwargs) + return status + + return wrapped_function + + +sai_thrift_flush_fdb_entries = delay_wrapper(sai_thrift_flush_fdb_entries) diff --git a/dash-pipeline/tests/saithrift/ptf/saidashvnet.py b/dash-pipeline/tests/saithrift/ptf/saidashvnet.py new file mode 100644 index 000000000..718770071 --- /dev/null +++ b/dash-pipeline/tests/saithrift/ptf/saidashvnet.py @@ -0,0 +1,748 @@ +# Copyright 2022-present Intel Corporation. +# +# 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Thrift SAI interface VNet tests +""" + +from sai_thrift.sai_headers import * +from sai_base_test import * + +from copy import copy +import pdb + + +class VNetObjects(SaiHelperSimplified): + def setUp(self): + super(VNetObjects, self).setUp() + self.teardown_objects = list() + + def tearDown(self): + super(VNetObjects, self).tearDown() + + def add_teardown_obj(self, func, *args): + self.teardown_objects.insert(0, (func, *args)) + + def destroy_teardown_obj(self): + for obj_func, obj_args in self.teardown_objects: + obj_func(obj_args) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + + +class VNetAPI(VNetObjects): + def setUp(self): + super(VNetAPI, self).setUp() + + def tearDown(self): + self.destroy_teardown_obj() + super(VNetAPI, self).tearDown() + + def vip_create(self, vip): + """ + Add VIP for Appliance + """ + + sai_vip_entry = sai_thrift_vip_entry_t(switch_id=self.switch_id, vip=sai_ipaddress(vip)) + sai_thrift_create_vip_entry(self.client, sai_vip_entry, action=SAI_VIP_ENTRY_ACTION_ACCEPT) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.vip_remove, sai_vip_entry) + + def vip_remove(self, vip_entry): + sai_thrift_remove_vip_entry(self.client, vip_entry) + + def eni_create(self, **kwargs): + """ + Create ENI + """ + + eni_id = sai_thrift_create_eni(self.client, **kwargs) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.assertNotEqual(eni_id, 0) + self.add_teardown_obj(self.eni_remove, eni_id) + + return eni_id + + def eni_remove(self, eni_id): + sai_thrift_remove_eni(self.client, eni_id) + + def direction_lookup_create(self, vni, drop=False): + """ + Create direction lookup entry + """ + + act = SAI_DIRECTION_LOOKUP_ENTRY_ACTION_SET_OUTBOUND_DIRECTION + if drop: + act = SAI_DIRECTION_LOOKUP_ENTRY_ACTION_DENY + direction_lookup_entry = sai_thrift_direction_lookup_entry_t(switch_id=self.switch_id, vni=vni) + sai_thrift_create_direction_lookup_entry(self.client, direction_lookup_entry, action=act) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.direction_lookup_remove, direction_lookup_entry) + + def direction_lookup_remove(self, direction_lookup_entry): + sai_thrift_remove_direction_lookup_entry(self.client, direction_lookup_entry) + + def eni_mac_map_create(self, eni_id, mac): + """ + Create ENI - MAC mapping + """ + + eni_ether_address_map_entry = sai_thrift_eni_ether_address_map_entry_t(switch_id=self.switch_id, address=mac) + sai_thrift_create_eni_ether_address_map_entry(self.client, eni_ether_address_map_entry, eni_id=eni_id) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.eni_mac_map_remove, eni_ether_address_map_entry) + + def eni_mac_map_remove(self, eni_ether_address_map_entry): + sai_thrift_remove_eni_ether_address_map_entry(self.client, eni_ether_address_map_entry) + + def vnet_create(self, vni): + """ + Create VNET + """ + + vnet_id = sai_thrift_create_vnet(self.client, vni=vni) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.assertNotEqual(vnet_id, 0) + self.add_teardown_obj(self.vnet_remove, vnet_id) + + return vnet_id + + def vnet_remove(self, vnet_id): + sai_thrift_remove_vnet(self.client, vnet_id) + + def inbound_routing_decap_validate_create(self, eni_id, vni, sip, sip_mask, src_vnet_id): + """ + Create inbound routing entry with + SAI_INBOUND_ROUTING_ENTRY_ACTION_VXLAN_DECAP_PA_VALIDATE action + """ + + inbound_routing_entry = sai_thrift_inbound_routing_entry_t( + switch_id=self.switch_id, vni=vni, + eni_id=eni_id, sip=sai_ipaddress(sip), + sip_mask=sai_ipaddress(sip_mask), priority=0) + sai_thrift_create_inbound_routing_entry(self.client, inbound_routing_entry, + action=SAI_INBOUND_ROUTING_ENTRY_ACTION_VXLAN_DECAP_PA_VALIDATE, + src_vnet_id=src_vnet_id) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.inbound_routing_remove, inbound_routing_entry) + + def inbound_routing_decap_create(self, eni_id, vni, sip, sip_mask): + """ + Create inbound routing entry with + SAI_INBOUND_ROUTING_ENTRY_ACTION_VXLAN_DECAP action + """ + + inbound_routing_entry = sai_thrift_inbound_routing_entry_t( + switch_id=self.switch_id, vni=vni, + eni_id=eni_id, sip=sai_ipaddress(sip), + sip_mask=sai_ipaddress(sip_mask), priority=0) + sai_thrift_create_inbound_routing_entry(self.client, inbound_routing_entry, + action=SAI_INBOUND_ROUTING_ENTRY_ACTION_VXLAN_DECAP) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.inbound_routing_remove, inbound_routing_entry) + + def inbound_routing_remove(self, inbound_routing_entry): + sai_thrift_remove_inbound_routing_entry(self.client, inbound_routing_entry) + + def pa_validation_create(self, sip, vnet_id): + """ + Create source PA validation entry + """ + + pa_validation_entry = sai_thrift_pa_validation_entry_t(switch_id=self.switch_id, + sip=sai_ipaddress(sip), + vnet_id=vnet_id) + sai_thrift_create_pa_validation_entry(self.client, + pa_validation_entry, + action=SAI_PA_VALIDATION_ENTRY_ACTION_PERMIT) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.pa_validation_remove, pa_validation_entry) + + def pa_validation_remove(self, pa_validation_entry): + sai_thrift_remove_pa_validation_entry(self.client, pa_validation_entry) + + def outbound_routing_vnet_direct_create(self, eni_id, lpm, dst_vnet_id, overlay_ip): + """ + Create outband vnet direct routing entry + """ + + outbound_routing_entry = sai_thrift_outbound_routing_entry_t( + switch_id=self.switch_id, eni_id=eni_id, + destination=sai_ipprefix(lpm)) + sai_thrift_create_outbound_routing_entry(self.client, outbound_routing_entry, + dst_vnet_id=dst_vnet_id, overlay_ip=sai_ipaddress(overlay_ip), + action=SAI_OUTBOUND_ROUTING_ENTRY_ACTION_ROUTE_VNET_DIRECT) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.outbound_routing_vnet_direct_remove, outbound_routing_entry) + + def outbound_routing_direct_create(self, eni_id, lpm): + """ + Create outband vnet direct routing entry + """ + + outbound_routing_entry = sai_thrift_outbound_routing_entry_t( + switch_id=self.switch_id, eni_id=eni_id, + destination=sai_ipprefix(lpm)) + sai_thrift_create_outbound_routing_entry(self.client, outbound_routing_entry, + action=SAI_OUTBOUND_ROUTING_ENTRY_ACTION_ROUTE_DIRECT) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.outbound_routing_vnet_direct_remove, outbound_routing_entry) + + def outbound_routing_vnet_direct_remove(self, entry): + sai_thrift_remove_outbound_routing_entry(self.client, entry) + + def outbound_ca_to_pa_create(self, dst_vnet_id, dip, underlay_dip): + """ + Create outband CA PA mapping + """ + + ca_to_pa_entry = sai_thrift_outbound_ca_to_pa_entry_t(switch_id=self.switch_id, + dst_vnet_id=dst_vnet_id, dip=sai_ipaddress(dip)) + sai_thrift_create_outbound_ca_to_pa_entry(self.client, ca_to_pa_entry, + underlay_dip=sai_ipaddress(underlay_dip), use_dst_vnet_vni=True) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.outbound_ca_to_pa_remove, ca_to_pa_entry) + + def outbound_ca_to_pa_remove(self, ca_to_pa_entry): + sai_thrift_remove_outbound_ca_to_pa_entry(self.client, ca_to_pa_entry) + + def router_interface_create(self, port, src_mac=None): + """ + RIF create + """ + + rif = sai_thrift_create_router_interface(self.client, + type=SAI_ROUTER_INTERFACE_TYPE_PORT, + virtual_router_id=self.default_vrf, + src_mac_address=src_mac, port_id=port) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.router_interface_remove, rif) + + return rif + + def router_interface_remove(self, rif): + sai_thrift_remove_router_interface(self.client, rif) + + def nexthop_create(self, rif, ip): + """ + Nexthop create + """ + + nhop = sai_thrift_create_next_hop( + self.client, + ip=sai_ipaddress(ip), + router_interface_id=rif, + type=SAI_NEXT_HOP_TYPE_IP) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.nexthop_remove, nhop) + + return nhop + + def nexthop_remove(self, nhop): + sai_thrift_remove_next_hop(self.client, nhop) + + def neighbor_create(self, rif, ip, dmac): + """ + Neighbor create + """ + + neighbor_entry = sai_thrift_neighbor_entry_t( + rif_id=rif, ip_address=sai_ipaddress(ip)) + sai_thrift_create_neighbor_entry( + self.client, neighbor_entry, dst_mac_address=dmac) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.neighbor_remove, neighbor_entry) + + def neighbor_remove(self, entry): + sai_thrift_remove_neighbor_entry(self.client, entry) + + def route_create(self, prefix, nhop): + """ + Route create + """ + + route_entry = sai_thrift_route_entry_t( + vr_id=self.default_vrf, destination=sai_ipprefix(prefix)) + sai_thrift_create_route_entry( + self.client, route_entry, next_hop_id=nhop) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) + self.add_teardown_obj(self.route_remove, route_entry) + + def route_remove(self, entry): + sai_thrift_remove_route_entry(self.client, entry) + + +@group("draft") +class Vnet2VnetCTTest(VNetAPI): + """ + Vnet to Vnet scenario test case Inbound + """ + + def runTest(self): + self.configureTest() + + pdb.set_trace() + + def configureTest(self): + """ + Setup DUT in accordance with test purpose + """ + + self.vip_create("10.10.1.1") # Appliance VIP + self.direction_lookup_create(1) # direction lookup VNI, reserved VNI assigned to the VM->Appliance + eni_id = self.eni_create(vm_vni=1) # VM VNI = 1 + self.eni_mac_map_create(eni_id, "00:01:00:00:03:14") # ENI MAC address + vnet_id_2 = self.vnet_create(2) # VNET VNI = 2 + # inbound routing + self.inbound_routing_decap_validate_create(eni_id, 2, # routing VNI lookup = 2 + "10.10.2.0", "255.255.255.0", vnet_id_2) + self.pa_validation_create("10.10.2.10", vnet_id_2) + # outbound routing + self.outbound_routing_vnet_direct_create(eni_id, "192.168.1.0/24", vnet_id_2, "192.168.1.1") + self.outbound_ca_to_pa_create(vnet_id_2, # DST vnet id + "192.168.1.1", # DST IP addr + "10.10.2.10") # Underlay DIP + # underlay routing + self.router_interface_create(self.port0) + rif1 = self.router_interface_create(self.port1, src_mac="00:77:66:55:44:00") + nhop = self.nexthop_create(rif1, "10.10.2.10") + self.neighbor_create(rif1, "10.10.2.10", "aa:bb:cc:11:22:33") + self.route_create("10.10.2.0/24", nhop) + + +@group("draft") +class Vnet2VnetInboundTest(VNetAPI): + """ + Inbound Vnet to Vnet scenario test case + """ + + def setUp(self): + super(Vnet2VnetInboundTest, self).setUp() + """ + Configuration + +----------+-----------+ + | port0 | port0_rif | + +----------+-----------+ + | port1 | port1_rif | + +----------+-----------+ + """ + self.PA_VALIDATION_SIP = "10.10.2.10" # PA validation PERMIT + self.ENI_MAC = "00:01:00:00:03:14" # ENI MAC address + self.VM_VNI = 1 # DST VM VNI (Inbound VNI) + self.ROUTE_VNI = 2 # Inbound route VNI + + self.VIP_ADDRESS = "10.1.1.1" # Appliance IP address + + self.OUTER_DMAC = "aa:bb:cc:dd:ee:11" # DST MAC for outer VxLAN pkt and Neighbor MAC + self.OUTER_DIP = "10.10.1.10" # DST IP for outer IP pkt and Next-hop/Neighbor IP + + # SDN Appliance rif`s MAC addresses + self.RIF0_RIF_MAC = "00:77:66:55:44:00" + self.RIF1_RIF_MAC = "00:88:77:66:55:00" + + def runTest(self): + self.configureTest() + self.vnet2VnetInboundPaValidatePermitTest() + self.vnet2VnetInboundDenyVniTest() + self.vnet2VnetInboundRouteInvalidVniTest() + self.vnet2VnetInboundInvalidEniMacTest() + self.vnet2VnetInboundInvalidPaSrcIpTest() + + def configureTest(self): + """ + Setup DUT in accordance with test purpose + """ + # Underlay routing + self.router_interface_create(self.port0, src_mac=self.RIF0_RIF_MAC) + rif1 = self.router_interface_create(self.port1, src_mac=self.RIF1_RIF_MAC) + + nhop = self.nexthop_create(rif1, self.OUTER_DIP) + self.neighbor_create(rif1, self.OUTER_DIP, self.OUTER_DMAC) + self.route_create("10.10.1.0/24", nhop) + + # Overlay routing + self.vip_create(self.VIP_ADDRESS) # Appliance VIP + + # direction lookup VNI, reserved VNI assigned to the VM->Appliance + self.direction_lookup_create(self.VM_VNI) + + vnet1 = self.vnet_create(self.VM_VNI) + eni_id = self.eni_create(admin_state=True, + vm_underlay_dip=sai_ipaddress(self.OUTER_DIP), + vm_vni=self.VM_VNI, + vnet_id=vnet1) + self.eni_mac_map_create(eni_id, self.ENI_MAC) + + vnet2 = self.vnet_create(self.ROUTE_VNI) + + # Inbound routing PA Validate + self.inbound_routing_decap_validate_create(eni_id, vni=self.ROUTE_VNI, # routing VNI lookup = 2 + sip="10.10.2.0", sip_mask="255.255.255.0", src_vnet_id=vnet2) + # PA validation entry with Permit action + self.pa_validation_create(self.PA_VALIDATION_SIP, vnet2) + + # Create VxLAN packets + self.inner_pkt = simple_tcp_packet(eth_dst=self.ENI_MAC, + eth_src="20:30:40:50:60:70", + ip_dst="192.168.0.1", + ip_src="192.168.1.1", + ip_id=108, + ip_ttl=64) + + self.vxlan_pkt = simple_vxlan_packet(eth_dst=self.RIF0_RIF_MAC, + eth_src="aa:bb:cc:11:22:33", + ip_dst=self.VIP_ADDRESS, + ip_src=self.PA_VALIDATION_SIP, + ip_id=0, + ip_ttl=64, + ip_flags=0x2, + with_udp_chksum=False, + vxlan_vni=self.ROUTE_VNI, + inner_frame=self.inner_pkt) + + self.exp_vxlan_pkt = simple_vxlan_packet(eth_dst=self.OUTER_DMAC, + eth_src=self.RIF1_RIF_MAC, + ip_dst=self.OUTER_DIP, + ip_src=self.VIP_ADDRESS, + ip_id=0, + ip_ttl=64, + ip_flags=0x2, + with_udp_chksum=False, + vxlan_vni=self.VM_VNI, + inner_frame=self.inner_pkt) + + # Create Eth packets for verify_no_packet method + self.inner_eth_packet = simple_eth_packet(eth_dst=self.inner_pkt.getlayer('Ether').dst, + eth_src=self.inner_pkt.getlayer('Ether').src, eth_type=0x800) + self.outer_eth_packet = simple_eth_packet(eth_dst=self.exp_vxlan_pkt.getlayer('Ether').dst, + eth_src=self.exp_vxlan_pkt.getlayer('Ether').src, eth_type=0x800) + + def vnet2VnetInboundPaValidatePermitTest(self): + """ + Inbound VNET to VNET test with PA validation entry Permit action + Verifies correct packet routing + """ + + print("Sending VxLAN IPv4 packet, expect VxLAN packet routed") + + send_packet(self, self.dev_port0, self.vxlan_pkt) + verify_packet(self, self.exp_vxlan_pkt, self.dev_port1, timeout=1) + + print('\n', self.vnet2VnetInboundPaValidatePermitTest.__name__, ' OK') + + def vnet2VnetInboundDenyVniTest(self): + """ + Inbound VNET to VNET test with SAI_DIRECTION_LOOKUP_ENTRY_ACTION_DENY action + Verifies packet drop + """ + + # drop direction lookup VNI + drop_vni = 3 + self.direction_lookup_create(drop_vni, drop=True) + + # VNI matches Direction lookup Deny action + vxlan_pkt_invalid_vni = copy(self.vxlan_pkt) + vxlan_pkt_invalid_vni.getlayer('VXLAN').vni = drop_vni + + print("Sending VxLAN IPv4 packet with VNI that matches direction lookup action Deny, expect drop") + + send_packet(self, self.dev_port0, vxlan_pkt_invalid_vni) + verify_no_packet(self, self.inner_eth_packet, self.dev_port1, timeout=1) + verify_no_packet(self, self.outer_eth_packet, self.dev_port1, timeout=1) + + print('\n', self.vnet2VnetInboundDenyVniTest.__name__, ' OK') + + def vnet2VnetInboundInvalidEniMacTest(self): + """ + Inbound VNET to VNET test + Verifies packet drop in case of invalid ENI MAC address + """ + + # Invalid CA (ENI) DST MAC + vxlan_pkt_invalid_dmac = copy(self.vxlan_pkt) + vxlan_pkt_invalid_dmac.getlayer('VXLAN').getlayer('Ether').dst = "9e:ba:ce:98:d9:e2" + + print("Sending VxLAN IPv4 packet with invalid destination mac, expect drop") + + send_packet(self, self.dev_port0, vxlan_pkt_invalid_dmac) + verify_no_packet(self, self.inner_eth_packet, self.dev_port1, timeout=1) + verify_no_packet(self, self.outer_eth_packet, self.dev_port1, timeout=1) + + print('\n', self.vnet2VnetInboundInvalidEniMacTest.__name__, ' OK') + + def vnet2VnetInboundInvalidPaSrcIpTest(self): + """ + Inbound VNET to VNET test + Verifies packet drop in case of invalid Physical source IP address + """ + + # Invalid PA IP + vxlan_pkt_invalid_pa_ip = copy(self.vxlan_pkt) + vxlan_pkt_invalid_pa_ip.getlayer('IP').src = "192.168.56.12" + + print("Sending VxLAN IPv4 packet with invalid pa validation ip, expect drop") + + send_packet(self, self.dev_port0, vxlan_pkt_invalid_pa_ip) + verify_no_packet(self, self.inner_eth_packet, self.dev_port1, timeout=1) + verify_no_packet(self, self.outer_eth_packet, self.dev_port1, timeout=1) + + print('\n', self.vnet2VnetInboundInvalidPaSrcIpTest.__name__, ' OK') + + def vnet2VnetInboundRouteInvalidVniTest(self): + """ + Inbound VNET to VNET test scenario + Verifies packet drop in case of invalid routing VNI lookup + """ + + vxlan_pkt_invalid_vni = copy(self.vxlan_pkt) + vxlan_pkt_invalid_vni.getlayer('VXLAN').vni = 1000 + + print("Sending VxLAN IPv4 packet with invalid routing VNI lookup, expect drop") + + send_packet(self, self.dev_port0, vxlan_pkt_invalid_vni) + verify_no_packet(self, self.inner_eth_packet, self.dev_port1, timeout=1) + verify_no_packet(self, self.outer_eth_packet, self.dev_port1, timeout=1) + + print('\n', self.vnet2VnetInboundRouteInvalidVniTest.__name__, ' OK') + +@group("draft") +class Vnet2VnetOutboundRouteVnetDirectTest(VNetAPI): + """ + Outbound VNet to VNet test scenario with Outbound routing entry + SAI_OUTBOUND_ROUTING_ENTRY_ACTION_ROUTE_VNET_DIRECT action + """ + + def setUp(self): + super(Vnet2VnetOutboundRouteVnetDirectTest, self).setUp() + """ + Configuration + +----------+-----------+ + | port0 | port0_rif | + +----------+-----------+ + | port1 | port1_rif | + +----------+-----------+ + """ + self.VIP_ADDRESS = "10.1.1.1" # Appliance IP address + self.SRC_VM_VNI = 1 + self.DST_VM_VNI = 2 + + def configureTest(self): + """ + Setup DUT in accordance with test purpose + """ + + self.vip_create(self.VIP_ADDRESS) # Appliance VIP + + # direction lookup VNI, reserved VNI assigned to the VM->Appliance + self.direction_lookup_create(self.SRC_VM_VNI) + + eni_id = self.eni_create(vm_vni=self.SRC_VM_VNI) # VM VNI = 1 + self.eni_mac_map_create(eni_id, "00:01:00:00:03:14") # ENI MAC address + vnet_id_2 = self.vnet_create(self.DST_VM_VNI) # VNET VNI = 2 + # outbound routing + self.outbound_routing_vnet_direct_create(eni_id, "192.168.1.0/24", vnet_id_2, + overlay_ip="192.168.1.10") + self.outbound_ca_to_pa_create(vnet_id_2, # DST vnet id + "192.168.1.10", # DST IP addr + "10.10.2.10") # Underlay DIP + # underlay routing + self.router_interface_create(self.port1) + rif0 = self.router_interface_create(self.port0, src_mac="00:77:66:55:44:00") + nhop = self.nexthop_create(rif0, "10.10.2.10") + self.neighbor_create(rif0, "10.10.2.10", "aa:bb:cc:11:22:33") + self.route_create("10.10.2.0/24", nhop) + + def runTest(self): + self.configureTest() + + # send packet and check + inner_pkt = simple_udp_packet(eth_src="00:00:00:09:03:14", + eth_dst="20:30:40:50:60:70", + ip_dst="192.168.1.1", + ip_src="192.168.0.1", + ip_ttl=64, + ip_ihl=5, + with_udp_chksum=True) + + vxlan_pkt = simple_vxlan_packet(eth_dst="00:00:cc:11:22:33", + eth_src="00:00:66:00:44:00", + ip_dst=self.VIP_ADDRESS, + ip_src="10.10.1.10", + with_udp_chksum=True, + vxlan_vni=self.SRC_VM_VNI, + ip_ttl=0, + ip_ihl=5, + ip_id=0, + udp_sport=5000, + vxlan_flags=0x8, + vxlan_reserved0=None, + vxlan_reserved1=0, + vxlan_reserved2=0, + ip_flags=0x2, + inner_frame=inner_pkt) + + exp_vxlan_pkt = simple_vxlan_packet(eth_dst="aa:bb:cc:11:22:33", + eth_src="00:77:66:55:44:00", + ip_dst="10.10.2.10", + ip_src=self.VIP_ADDRESS, + with_udp_chksum=True, + vxlan_vni=self.DST_VM_VNI, + ip_ttl=0, + ip_ihl=5, + ip_id=0, + udp_sport=5000, + vxlan_flags=0x8, + vxlan_reserved0=None, + vxlan_reserved1=0, + vxlan_reserved2=0, + ip_flags=0x2, + inner_frame=inner_pkt) + + print("Sending VxLAN IPv4 packet, expect VxLAN IPv4 packet forwarded") + send_packet(self, self.dev_port1, vxlan_pkt) + verify_packet(self, exp_vxlan_pkt, self.dev_port0) + + +@group("draft") +class Vnet2VnetOutboundRouteDirectTest(VNetAPI): + """ + Outbound VNet to VNet test scenario with Outbound routing entry + SAI_OUTBOUND_ROUTING_ENTRY_ACTION_ROUTE_DIRECT action + """ + + def setUp(self): + super(Vnet2VnetOutboundRouteDirectTest, self).setUp() + """ + Configuration + +----------+-----------+ + | port0 | port0_rif | + +----------+-----------+ + | port1 | port1_rif | + +----------+-----------+ + """ + + self.VIP_ADDRESS = "10.1.1.1" # Appliance VIP address + self.SRC_VM_VNI = 1 + + def configureTest(self): + """ + Setup DUT in accordance with test purpose + """ + + self.vip_create(self.VIP_ADDRESS) # Appliance VIP + + # direction lookup VNI, reserved VNI assigned to the VM->Appliance + self.direction_lookup_create(self.SRC_VM_VNI) + + eni_id = self.eni_create(vm_vni=self.SRC_VM_VNI) # VM VNI = 1 + self.eni_mac_map_create(eni_id, "00:01:00:00:03:14") # ENI MAC address + + # outbound routing + self.outbound_routing_direct_create(eni_id, "192.168.1.0/24") + + # underlay routing + self.router_interface_create(self.port1) + rif0 = self.router_interface_create(self.port0, src_mac="00:77:66:55:44:00") + nhop = self.nexthop_create(rif0, "10.10.2.10") + self.neighbor_create(rif0, "10.10.2.10", "aa:bb:cc:11:22:33") + self.route_create("10.10.2.0/24", nhop) + + def runTest(self): + self.configureTest() + + # send packet and check + inner_pkt = simple_udp_packet(eth_src="00:00:00:09:03:14", + eth_dst="20:30:40:50:60:70", + ip_dst="192.168.1.1", + ip_src="192.168.0.1", + ip_ttl=64, + ip_ihl=5, + with_udp_chksum=True) + + vxlan_pkt = simple_vxlan_packet(eth_dst="00:00:cc:11:22:33", + eth_src="00:00:66:00:44:00", + ip_dst=self.VIP_ADDRESS, + ip_src="10.10.1.10", + with_udp_chksum=True, + vxlan_vni=self.SRC_VM_VNI, + ip_ttl=0, + ip_ihl=5, + ip_id=0, + udp_sport=5000, + vxlan_flags=0x8, + vxlan_reserved0=None, + vxlan_reserved1=0, + vxlan_reserved2=0, + ip_flags=0x2, + inner_frame=inner_pkt) + + direct_pkt = simple_udp_packet(eth_src="00:77:66:55:44:00", + eth_dst="aa:bb:cc:11:22:33", + ip_dst="192.168.1.1", + ip_src="192.168.0.1", + ip_ttl=63, + ip_ihl=5, + with_udp_chksum=True) + + print("Sending VxLAN IPv4 packet, expected UDP packet forwarded") + send_packet(self, self.dev_port1, vxlan_pkt) + verify_packet(self, direct_pkt, self.dev_port0) + +@group("draft") +class VnetRouteTest(VNetAPI): + """ + Vnet to Vnet scenario test case Outbound + """ + + def setUp(self): + super(VnetRouteTest, self).setUp() + """ + Configuration + +----------+-----------+ + | port0 | port0_rif | + +----------+-----------+ + | port1 | port1_rif | + +----------+-----------+ + """ + self.RIF_SRC_MAC = "44:33:33:22:55:66" + self.NEIGH_DMAC = "aa:bb:cc:11:22:33" + + def configureTest(self): + """ + Setup DUT in accordance with test purpose + """ + + # underlay routing + self.router_interface_create(self.port1) + rif0 = self.router_interface_create(self.port0, src_mac=self.RIF_SRC_MAC) + nhop = self.nexthop_create(rif0, "10.10.2.10") + self.neighbor_create(rif0, "10.10.2.10", self.NEIGH_DMAC) + self.route_create("10.10.2.2/24", nhop) + + def runTest(self): + self.configureTest() + + out_pkt = simple_udp_packet(eth_src="00:00:00:01:03:14", + eth_dst="20:30:40:50:60:70", + ip_dst="10.10.2.2", + ip_src="10.10.20.20", + ip_ttl=64) + exp_pkt = simple_udp_packet(eth_src=self.RIF_SRC_MAC, + eth_dst=self.NEIGH_DMAC, + ip_dst="10.10.2.2", + ip_src="10.10.20.20", + ip_ttl=64) + + print("Sending simple UDP packet, expecting routed packet") + send_packet(self, self.dev_port1, out_pkt) + verify_packet(self, exp_pkt, self.dev_port0) From cbd70b2b48a4bbc581852b9192e525d26ab5a47c Mon Sep 17 00:00:00 2001 From: Volodymyr Mytnyk Date: Mon, 19 Sep 2022 13:21:14 +0000 Subject: [PATCH 2/8] ptf: run only bmv2 ready test case on CI Signed-off-by: Volodymyr Mytnyk --- dash-pipeline/tests/saithrift/ptf/run-saithrift-ptftests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash-pipeline/tests/saithrift/ptf/run-saithrift-ptftests.sh b/dash-pipeline/tests/saithrift/ptf/run-saithrift-ptftests.sh index cc1fb26fb..9206edc38 100755 --- a/dash-pipeline/tests/saithrift/ptf/run-saithrift-ptftests.sh +++ b/dash-pipeline/tests/saithrift/ptf/run-saithrift-ptftests.sh @@ -1,5 +1,5 @@ #!/bin/bash # To be run inside saithrift-client container, assumes SAI repo portions exist under /SAI directory -sudo ptf --test-dir . --pypath /SAI/ptf \ +sudo ptf --test-dir vnet --pypath /SAI/ptf \ --interface 0@veth1 --interface 1@veth3 From 0166d9bde97e37a6c8582de402c17ae84e98adfb Mon Sep 17 00:00:00 2001 From: Volodymyr Mytnyk Date: Mon, 19 Sep 2022 13:21:47 +0000 Subject: [PATCH 3/8] ptf: test_saithrift_vnet: removed unused libs Signed-off-by: Volodymyr Mytnyk --- .../tests/saithrift/ptf/vnet/test_saithrift_vnet.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dash-pipeline/tests/saithrift/ptf/vnet/test_saithrift_vnet.py b/dash-pipeline/tests/saithrift/ptf/vnet/test_saithrift_vnet.py index da4c56281..296bb1547 100644 --- a/dash-pipeline/tests/saithrift/ptf/vnet/test_saithrift_vnet.py +++ b/dash-pipeline/tests/saithrift/ptf/vnet/test_saithrift_vnet.py @@ -1,8 +1,3 @@ -import pytest -import snappi -import scapy -import time - from sai_thrift.sai_headers import * from sai_base_test import * # TODO - when switch APIs implemented: From 13d33efbe262b08a3ba005f387760d42d8c1ed0a Mon Sep 17 00:00:00 2001 From: Anton Putria Date: Tue, 27 Sep 2022 21:45:43 -0400 Subject: [PATCH 4/8] test-cases folder restructure. - created two subfolders in root: functional and scale - removed outdated bmv2_model folder - moved all existing content of test-cases to test-cases/scale Signed-off-by: Anton Putria --- test/test-cases/README.md | 20 ++- .../test-cases/bmv2_model/test_hello_world.py | 143 ------------------ .../{ => scale}/credentials.py.template | 0 test/test-cases/{ => scale}/testbed.py | 0 .../{ => scale}/vnet2vnet/48K-ips/README.md | 0 .../48K-ips/test_vxlan_8vpc_48K-ips.py | 0 .../48K-ips/testdata_vxlan_8vpc_48K-ips.py | 0 .../{ => scale}/vnet2vnet/README.md | 0 .../vnet2vnet/bgp_stateful_config.py | 0 .../{ => scale}/vnet2vnet/conftest.py | 0 .../{ => scale}/vnet2vnet/one-ip/README.md | 0 .../vnet2vnet/one-ip/dash_1vpc_1ip_peer.json | 0 .../vnet2vnet/one-ip/test_vxlan_1vpc_1ip.py | 0 .../one-ip/testdata_vxlan_1vpc_1ip.py | 0 .../{ => scale}/vnet2vnet/utils/__init__.py | 0 .../{ => scale}/vnet2vnet/utils/common.py | 0 .../vlan_to_vxlan_stateful_config.py | 0 17 files changed, 17 insertions(+), 146 deletions(-) delete mode 100644 test/test-cases/bmv2_model/test_hello_world.py rename test/test-cases/{ => scale}/credentials.py.template (100%) rename test/test-cases/{ => scale}/testbed.py (100%) rename test/test-cases/{ => scale}/vnet2vnet/48K-ips/README.md (100%) rename test/test-cases/{ => scale}/vnet2vnet/48K-ips/test_vxlan_8vpc_48K-ips.py (100%) rename test/test-cases/{ => scale}/vnet2vnet/48K-ips/testdata_vxlan_8vpc_48K-ips.py (100%) rename test/test-cases/{ => scale}/vnet2vnet/README.md (100%) rename test/test-cases/{ => scale}/vnet2vnet/bgp_stateful_config.py (100%) rename test/test-cases/{ => scale}/vnet2vnet/conftest.py (100%) rename test/test-cases/{ => scale}/vnet2vnet/one-ip/README.md (100%) rename test/test-cases/{ => scale}/vnet2vnet/one-ip/dash_1vpc_1ip_peer.json (100%) rename test/test-cases/{ => scale}/vnet2vnet/one-ip/test_vxlan_1vpc_1ip.py (100%) rename test/test-cases/{ => scale}/vnet2vnet/one-ip/testdata_vxlan_1vpc_1ip.py (100%) rename test/test-cases/{ => scale}/vnet2vnet/utils/__init__.py (100%) rename test/test-cases/{ => scale}/vnet2vnet/utils/common.py (100%) rename test/test-cases/{ => scale}/vnet2vnet/vlan_to_vxlan_stateful_config.py (100%) diff --git a/test/test-cases/README.md b/test/test-cases/README.md index ca9b174cb..b6ca1796e 100644 --- a/test/test-cases/README.md +++ b/test/test-cases/README.md @@ -3,6 +3,20 @@ This contains a hierarchical set of directories containing DASH test cases organ # Contents -| Folder | Description | -| ---------------------------------------------- | --------------------------------------------------------- | -| [vnet2vnet](vnet2vnet/README.md) | DASH vnet2vnet Tests +| Folder | Description | +| --- | --- | +| functional | Tests to verify essential functionality using low-rate traffic (SAI PTF). +| scale | Tests with hight-rate traffic and complex configuration to verify scaling real-world scenarios. + +## functional + +| Folder/File | Description | +| --- | --- | +| saidashvnet.py | VNET-to-VNET test cases + + +## scale + +| Folder/File | Description | +| --- | --- | +| [vnet2vnet](vnet2vnet/README.md) | DASH vnet2vnet Tests diff --git a/test/test-cases/bmv2_model/test_hello_world.py b/test/test-cases/bmv2_model/test_hello_world.py deleted file mode 100644 index 3f9a6d01e..000000000 --- a/test/test-cases/bmv2_model/test_hello_world.py +++ /dev/null @@ -1,143 +0,0 @@ -import snappi - - -def test_udp_unidirectional(): - """ - This script does following: - - Send 1000 packets from one port to another at a rate of - 1000 packets per second. - - Validate that total packets sent are received on the same interface - """ - # create a new API instance where location points to controller - api = snappi.api(location="https://localhost", verify=False) - # and an empty traffic configuration to be pushed to controller later on - cfg = api.config() - - # add two ports where location points to traffic-engine (aka ports) - p1, p2 = cfg.ports.port(name="p1", location="localhost:5555").port( - name="p2", location="localhost:5556" - ) - - # add layer 1 property to configure same speed on both ports - ly = cfg.layer1.layer1(name="ly")[-1] - ly.port_names = [p1.name, p2.name] - ly.speed = ly.SPEED_1_GBPS - - - # add two traffic flows - f1, f2 = cfg.flows.flow(name="flow p1->p2").flow(name="flow p2->p1") - # and assign source and destination ports for each - f1.tx_rx.port.tx_name, f1.tx_rx.port.rx_name = p1.name, p2.name - f2.tx_rx.port.tx_name, f2.tx_rx.port.rx_name = p2.name, p1.name - - # configure packet size, rate and duration for both flows - f1.size.fixed, f2.size.fixed = 128, 256 - pkt_count=500 - pps=100 - for f in cfg.flows: - # send pkt_count packets and stop - f.duration.fixed_packets.packets = pkt_count - # send pps packets per second - f.rate.pps = pps - - # configure packet with Ethernet, IPv4 and UDP headers for both flows - eth1, ip1, udp1 = f1.packet.ethernet().ipv4().udp() - eth2, ip2, udp2 = f2.packet.ethernet().ipv4().udp() - - # set source and destination MAC addresses - eth1.src.value, eth1.dst.value = "00:AA:00:00:04:00", "00:AA:00:00:00:AA" - eth2.src.value, eth2.dst.value = "00:AA:00:00:00:AA", "00:AA:00:00:04:00" - - # set source and destination IPv4 addresses - ip1.src.value, ip1.dst.value = "10.0.0.1", "10.0.0.2" - ip2.src.value, ip2.dst.value = "10.0.0.2", "10.0.0.1" - - # set incrementing port numbers as source UDP ports - udp1.src_port.increment.start = 5000 - udp1.src_port.increment.step = 2 - udp1.src_port.increment.count = 10 - - udp2.src_port.increment.start = 6000 - udp2.src_port.increment.step = 4 - udp2.src_port.increment.count = 10 - - # assign list of port numbers as destination UDP ports - udp1.dst_port.values = [4000, 4044, 4060, 4074] - udp2.dst_port.values = [8000, 8044, 8060, 8074, 8082, 8084] - - print("Pushing traffic configuration ...") - api.set_config(cfg) - - print("Starting transmit on all configured flows ...") - ts = api.transmit_state() - ts.state = ts.START - api.set_transmit_state(ts) - - print("Checking metrics on all configured ports ...") - print("Expected\tTotal Tx\tTotal Rx") - assert wait_for(lambda: metrics_ok(api, cfg)), "Metrics validation failed!" - - print("Test passed !") - - -def metrics_ok(api, cfg): - # create a port metrics request and filter based on port names - req = api.metrics_request() - req.port.port_names = [p.name for p in cfg.ports] - # include only sent and received packet counts - req.port.column_names = [req.port.FRAMES_TX, req.port.FRAMES_RX] - - # fetch port metrics - res = api.get_metrics(req) - # calculate total frames sent and received across all configured ports - total_tx = sum([m.frames_tx for m in res.port_metrics]) - total_rx = sum([m.frames_rx for m in res.port_metrics]) - expected = sum([f.duration.fixed_packets.packets for f in cfg.flows]) - - print("%d\t\t%d\t\t%d" % (expected, total_tx, total_rx)) - - return expected == total_tx and total_rx >= expected - - -def captures_ok(api, cfg): - import dpkt - - print("Checking captured packets on all configured ports ...") - print("Port Name\tExpected\tUDP packets") - - result = [] - for p in cfg.ports: - exp, act = 1000, 0 - # create capture request and filter based on port name - req = api.capture_request() - req.port_name = p.name - # fetch captured pcap bytes and feed it to pcap parser dpkt - pcap = dpkt.pcapng.Reader(api.get_capture(req)) - for _, buf in pcap: - # check if current packet is a valid UDP packet - eth = dpkt.ethernet.Ethernet(buf) - if isinstance(eth.data.data, dpkt.udp.UDP): - act += 1 - - print("%s\t\t%d\t\t%d" % (p.name, exp, act)) - result.append(exp == act) - - return all(result) - - -def wait_for(func, timeout=60, interval=0.2): - """ - Keeps calling the `func` until it returns true or `timeout` occurs - every `interval` seconds. - """ - import time - - start = time.time() - - while time.time() - start <= timeout: - if func(): - return True - time.sleep(interval) - - print("Timeout occurred !") - return False \ No newline at end of file diff --git a/test/test-cases/credentials.py.template b/test/test-cases/scale/credentials.py.template similarity index 100% rename from test/test-cases/credentials.py.template rename to test/test-cases/scale/credentials.py.template diff --git a/test/test-cases/testbed.py b/test/test-cases/scale/testbed.py similarity index 100% rename from test/test-cases/testbed.py rename to test/test-cases/scale/testbed.py diff --git a/test/test-cases/vnet2vnet/48K-ips/README.md b/test/test-cases/scale/vnet2vnet/48K-ips/README.md similarity index 100% rename from test/test-cases/vnet2vnet/48K-ips/README.md rename to test/test-cases/scale/vnet2vnet/48K-ips/README.md diff --git a/test/test-cases/vnet2vnet/48K-ips/test_vxlan_8vpc_48K-ips.py b/test/test-cases/scale/vnet2vnet/48K-ips/test_vxlan_8vpc_48K-ips.py similarity index 100% rename from test/test-cases/vnet2vnet/48K-ips/test_vxlan_8vpc_48K-ips.py rename to test/test-cases/scale/vnet2vnet/48K-ips/test_vxlan_8vpc_48K-ips.py diff --git a/test/test-cases/vnet2vnet/48K-ips/testdata_vxlan_8vpc_48K-ips.py b/test/test-cases/scale/vnet2vnet/48K-ips/testdata_vxlan_8vpc_48K-ips.py similarity index 100% rename from test/test-cases/vnet2vnet/48K-ips/testdata_vxlan_8vpc_48K-ips.py rename to test/test-cases/scale/vnet2vnet/48K-ips/testdata_vxlan_8vpc_48K-ips.py diff --git a/test/test-cases/vnet2vnet/README.md b/test/test-cases/scale/vnet2vnet/README.md similarity index 100% rename from test/test-cases/vnet2vnet/README.md rename to test/test-cases/scale/vnet2vnet/README.md diff --git a/test/test-cases/vnet2vnet/bgp_stateful_config.py b/test/test-cases/scale/vnet2vnet/bgp_stateful_config.py similarity index 100% rename from test/test-cases/vnet2vnet/bgp_stateful_config.py rename to test/test-cases/scale/vnet2vnet/bgp_stateful_config.py diff --git a/test/test-cases/vnet2vnet/conftest.py b/test/test-cases/scale/vnet2vnet/conftest.py similarity index 100% rename from test/test-cases/vnet2vnet/conftest.py rename to test/test-cases/scale/vnet2vnet/conftest.py diff --git a/test/test-cases/vnet2vnet/one-ip/README.md b/test/test-cases/scale/vnet2vnet/one-ip/README.md similarity index 100% rename from test/test-cases/vnet2vnet/one-ip/README.md rename to test/test-cases/scale/vnet2vnet/one-ip/README.md diff --git a/test/test-cases/vnet2vnet/one-ip/dash_1vpc_1ip_peer.json b/test/test-cases/scale/vnet2vnet/one-ip/dash_1vpc_1ip_peer.json similarity index 100% rename from test/test-cases/vnet2vnet/one-ip/dash_1vpc_1ip_peer.json rename to test/test-cases/scale/vnet2vnet/one-ip/dash_1vpc_1ip_peer.json diff --git a/test/test-cases/vnet2vnet/one-ip/test_vxlan_1vpc_1ip.py b/test/test-cases/scale/vnet2vnet/one-ip/test_vxlan_1vpc_1ip.py similarity index 100% rename from test/test-cases/vnet2vnet/one-ip/test_vxlan_1vpc_1ip.py rename to test/test-cases/scale/vnet2vnet/one-ip/test_vxlan_1vpc_1ip.py diff --git a/test/test-cases/vnet2vnet/one-ip/testdata_vxlan_1vpc_1ip.py b/test/test-cases/scale/vnet2vnet/one-ip/testdata_vxlan_1vpc_1ip.py similarity index 100% rename from test/test-cases/vnet2vnet/one-ip/testdata_vxlan_1vpc_1ip.py rename to test/test-cases/scale/vnet2vnet/one-ip/testdata_vxlan_1vpc_1ip.py diff --git a/test/test-cases/vnet2vnet/utils/__init__.py b/test/test-cases/scale/vnet2vnet/utils/__init__.py similarity index 100% rename from test/test-cases/vnet2vnet/utils/__init__.py rename to test/test-cases/scale/vnet2vnet/utils/__init__.py diff --git a/test/test-cases/vnet2vnet/utils/common.py b/test/test-cases/scale/vnet2vnet/utils/common.py similarity index 100% rename from test/test-cases/vnet2vnet/utils/common.py rename to test/test-cases/scale/vnet2vnet/utils/common.py diff --git a/test/test-cases/vnet2vnet/vlan_to_vxlan_stateful_config.py b/test/test-cases/scale/vnet2vnet/vlan_to_vxlan_stateful_config.py similarity index 100% rename from test/test-cases/vnet2vnet/vlan_to_vxlan_stateful_config.py rename to test/test-cases/scale/vnet2vnet/vlan_to_vxlan_stateful_config.py From cfc5af897fb8ebec83ceeeed1dba34e06cbbbb6a Mon Sep 17 00:00:00 2001 From: Anton Putria Date: Tue, 27 Sep 2022 22:37:07 -0400 Subject: [PATCH 5/8] Moved saidashvnet.py to a proper location. Signed-off-by: Anton Putria --- .../saithrift/ptf/platform_helper/__init__.py | 28 - .../ptf/platform_helper/common_sai_helper.py | 73 -- .../saithrift/ptf/run-saithrift-ptftests.sh | 2 +- .../tests/saithrift/ptf/sai_base_test.py | 1014 ----------------- .../tests/saithrift/ptf/sai_utils.py | 329 ------ .../test-cases/functional}/saidashvnet.py | 29 +- test/test-cases/run-functional-tests.sh | 4 + 7 files changed, 33 insertions(+), 1446 deletions(-) delete mode 100644 dash-pipeline/tests/saithrift/ptf/platform_helper/__init__.py delete mode 100644 dash-pipeline/tests/saithrift/ptf/platform_helper/common_sai_helper.py delete mode 100644 dash-pipeline/tests/saithrift/ptf/sai_base_test.py delete mode 100644 dash-pipeline/tests/saithrift/ptf/sai_utils.py rename {dash-pipeline/tests/saithrift/ptf => test/test-cases/functional}/saidashvnet.py (93%) create mode 100755 test/test-cases/run-functional-tests.sh diff --git a/dash-pipeline/tests/saithrift/ptf/platform_helper/__init__.py b/dash-pipeline/tests/saithrift/ptf/platform_helper/__init__.py deleted file mode 100644 index 101a1a2ea..000000000 --- a/dash-pipeline/tests/saithrift/ptf/platform_helper/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# 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. -# -# @file __init__.py -# -# @brief init -# - -""" -Init the platform helper module. - -platform_helper module contains classes to distiguish the different behivor on different platforms. -""" diff --git a/dash-pipeline/tests/saithrift/ptf/platform_helper/common_sai_helper.py b/dash-pipeline/tests/saithrift/ptf/platform_helper/common_sai_helper.py deleted file mode 100644 index ad9209e10..000000000 --- a/dash-pipeline/tests/saithrift/ptf/platform_helper/common_sai_helper.py +++ /dev/null @@ -1,73 +0,0 @@ -# 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. - -""" -Class contains common functions. - -This file contains base class for other platform classes. -""" -from sai_base_test import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] - -class CommonSaiHelper(SaiHelper): - """ - This class contains the common functions for the platform setup and test context configuration. - """ - #TODO move the common methods from the sai_base_test. - - platform = 'common' - - def sai_thrift_create_fdb_entry_allow_mac_move(self, - client, - fdb_entry, - type=None, - packet_action=None, - user_trap_id=None, - bridge_port_id=None, - meta_data=None, - endpoint_ip=None, - counter_id=None, - allow_mac_move=None): - """ - Override the sai_thrift_create_fdb_entry when check the functionality related to allow_mac_move. - - This method will transfer allow_mac_move directly(not override). - For the encounter function, please refer to \r - \t :func:`BrcmSaiHelper.sai_thrift_create_fdb_entry_allow_mac_move` - """ - #TODO confirm the SPEC. Related to RFC9014 and RFC7432 - print("CommonSaiHelper::sai_thrift_create_fdb_entry_allow_mac_move") - sai_thrift_create_fdb_entry( - client=client, - fdb_entry=fdb_entry, - type=type, - packet_action=packet_action, - user_trap_id=user_trap_id, - bridge_port_id=bridge_port_id, - meta_data=meta_data, - endpoint_ip=endpoint_ip, - counter_id=counter_id, - allow_mac_move=allow_mac_move) - - - def remove_bridge_port(self): - """ - Remove all bridge ports. - """ - for index in range(0, len(self.port_list)): - port_bp = getattr(self, 'port%s_bp' % index) - sai_thrift_remove_bridge_port(self.client, port_bp) diff --git a/dash-pipeline/tests/saithrift/ptf/run-saithrift-ptftests.sh b/dash-pipeline/tests/saithrift/ptf/run-saithrift-ptftests.sh index 9206edc38..cc1fb26fb 100755 --- a/dash-pipeline/tests/saithrift/ptf/run-saithrift-ptftests.sh +++ b/dash-pipeline/tests/saithrift/ptf/run-saithrift-ptftests.sh @@ -1,5 +1,5 @@ #!/bin/bash # To be run inside saithrift-client container, assumes SAI repo portions exist under /SAI directory -sudo ptf --test-dir vnet --pypath /SAI/ptf \ +sudo ptf --test-dir . --pypath /SAI/ptf \ --interface 0@veth1 --interface 1@veth3 diff --git a/dash-pipeline/tests/saithrift/ptf/sai_base_test.py b/dash-pipeline/tests/saithrift/ptf/sai_base_test.py deleted file mode 100644 index b43311f59..000000000 --- a/dash-pipeline/tests/saithrift/ptf/sai_base_test.py +++ /dev/null @@ -1,1014 +0,0 @@ -# Copyright 2021-present Intel Corporation. -# -# 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 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -This file contains base classes for PTF test cases as well as a set of -additional useful functions. - -Tests will usually inherit from one of the base classes to have the controller -and/or dataplane automatically set up. -""" -import os -import time -from threading import Thread - -from collections import OrderedDict -from unittest import SkipTest - -from ptf import config -from ptf.base_tests import BaseTest - -from thrift.transport import TSocket -from thrift.transport import TTransport -from thrift.protocol import TBinaryProtocol - -from sai_thrift import sai_rpc - -from sai_utils import * -import sai_thrift.sai_adapter as adapter - -ROUTER_MAC = '00:77:66:55:44:00' -THRIFT_PORT = 9092 - -SKIP_TEST_NO_RESOURCES_MSG = 'Not enough resources to run test' -PLATFORM = os.environ.get('PLATFORM') -platform_map = {'broadcom': 'brcm', 'barefoot': 'bfn', - 'mellanox': 'mlnx', 'common': 'common'} - - -class ThriftInterface(BaseTest): - """ - Get and format a port map, retrieve test params, and create an RPC client - """ - def setUp(self): - super(ThriftInterface, self).setUp() - - self.interface_to_front_mapping = {} - self.port_map_loaded = False - self.transport = None - - self.test_params = test_params_get() - self.loadPortMap() - self.createRpcClient() - - def tearDown(self): - self.transport.close() - - super(ThriftInterface, self).tearDown() - - def loadPortMap(self): - """ - Get and format port_map - - port_map_file is a port map with following lines format: - [test_port_no]@[device_port_name] - e.g.: - 0@Veth1 - 1@Veth2 - 2@Veth3 ... - """ - if self.port_map_loaded: - print("port_map already loaded") - return - - if "port_map" in self.test_params: - user_input = self.test_params['port_map'] - splitted_map = user_input.split(",") - for item in splitted_map: - iface_front_pair = item.split("@") - self.interface_to_front_mapping[iface_front_pair[0]] = \ - iface_front_pair[1] - elif "port_map_file" in self.test_params: - user_input = self.test_params['port_map_file'] - with open(user_input, 'r') as map_file: - for line in map_file: - if (line and (line[0] == '#' or - line[0] == ';' or line[0] == '/')): - continue - iface_front_pair = line.split("@") - self.interface_to_front_mapping[iface_front_pair[0]] = \ - iface_front_pair[1].strip() - - self.port_map_loaded = True - - def createRpcClient(self): - """ - Set up thrift client and contact RPC server - """ - - if 'thrift_server' in self.test_params: - server = self.test_params['thrift_server'] - else: - server = 'localhost' - - self.transport = TSocket.TSocket(server, THRIFT_PORT) - self.transport = TTransport.TBufferedTransport(self.transport) - self.protocol = TBinaryProtocol.TBinaryProtocol(self.transport) - - self.client = sai_rpc.Client(self.protocol) - self.transport.open() - - -class ThriftInterfaceDataPlane(ThriftInterface): - """ - Sets up the thrift interface and dataplane - """ - def setUp(self): - super(ThriftInterfaceDataPlane, self).setUp() - - self.dataplane = ptf.dataplane_instance - if self.dataplane is not None: - self.dataplane.flush() - if config['log_dir'] is not None: - filename = os.path.join(config['log_dir'], str(self)) + ".pcap" - self.dataplane.start_pcap(filename) - - def tearDown(self): - if config['log_dir'] is not None: - self.dataplane.stop_pcap() - super(ThriftInterfaceDataPlane, self).tearDown() - - -class SaiHelperBase(ThriftInterfaceDataPlane): - """ - SAI test helper base class without initial switch ports setup - - Set the following class attributes: - self.default_vlan_id - self.default_vrf - 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) - """ - - platform = 'common' - - def get_active_port_list(self): - ''' - Method to get the active port list base on number_of_active_ports - - Sets the following class attributes: - - self.active_ports_no - number of active ports - - self.port_list - list of all active port objects - - self.portX objects for all active ports - ''' - - # get number of active ports - attr = sai_thrift_get_switch_attribute( - self.client, number_of_active_ports=True) - self.active_ports_no = attr['number_of_active_ports'] - - # get port_list and portX objects - attr = sai_thrift_get_switch_attribute( - self.client, port_list=sai_thrift_object_list_t( - idlist=[], count=self.active_ports_no)) - self.assertEqual(self.active_ports_no, attr['port_list'].count) - self.port_list = attr['port_list'].idlist - - #Gets self.portX objects for all active ports - for i, _ in enumerate(self.port_list): - setattr(self, 'port%s' % i, self.port_list[i]) - - - def turn_up_and_check_ports(self): - ''' - Method to turn up the ports. - ''' - #TODO check if this is common behivor or specified after check on more platform - print("For Common platform, Port already setup in recreate_ports.") - - - def shell(self): - ''' - Method use to start a sai shell in a thread. - ''' - def start_shell(): - sai_thrift_set_switch_attribute(self.client, switch_shell_enable=True) - thread = Thread(target = start_shell) - thread.start() - - - def recreate_ports(self): - ''' - Recreate the port base on file specified in 'port_config_ini' param. - ''' - #TODO check if this is common behivor or specified after check on more platform - if 'port_config_ini' in self.test_params: - if 'createPorts_has_been_called' not in config: - self.createPorts() - # check if ports became UP - #self.checkPortsUp() - config['createPorts_has_been_called'] = 1 - - - def get_default_1q_bridge_id(self): - ''' - Gets default 1q bridge 1d, set it to class attribute 'default_1q_bridge'. - - Sets the following class attributes: - - self.default_1q_bridge - default_1q_bridge_id - ''' - - attr = sai_thrift_get_switch_attribute( - self.client, default_1q_bridge_id=True) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - self.default_1q_bridge = attr['default_1q_bridge_id'] - - - def reset_1q_bridge_ports(self): - ''' - Reset all the 1Q bridge ports. - Needs the following class attributes: - self.default_1q_bridge - default_1q_bridge oid - - self.active_ports_no - number of active ports - - self.portX objects for all active ports - ''' - #TODO check if this is common behivor or specified after check on more platform - #TODO move this function to CommonSaiHelper - print("For Common platform, expecting bridge ports not been created by default.") - - - def check_cpu_port_hdl(self): - """ - Checks cpu port handler. - Expect the cpu_port_hdl equals to qos_queue port id, number_of_queues in qos equals to queue index. - - Needs the following class attributes: - - self.cpu_port_hdl - cpu_port_hdl id - - Seds the following class attributes: - - self.cpu_queueX - cpu queue id - - """ - #TODO move this function to CommonSaiHelper - attr = sai_thrift_get_port_attribute(self.client, - self.cpu_port_hdl, - qos_number_of_queues=True) - num_queues = attr['qos_number_of_queues'] - q_list = sai_thrift_object_list_t(count=num_queues) - attr = sai_thrift_get_port_attribute(self.client, - self.cpu_port_hdl, - qos_queue_list=q_list) - for queue in range(0, num_queues): - queue_id = attr['qos_queue_list'].idlist[queue] - setattr(self, 'cpu_queue%s' % queue, queue_id) - q_attr = sai_thrift_get_queue_attribute( - self.client, - queue_id, - port=True, - index=True, - parent_scheduler_node=True) - self.assertEqual(queue, q_attr['index']) - self.assertEqual(self.cpu_port_hdl, q_attr['port']) - - - def start_switch(self): - """ - Start switch. - """ - self.switch_id = sai_thrift_create_switch( - self.client, init_switch=True, src_mac_address=ROUTER_MAC) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - - - def setUp(self): - super(SaiHelperBase, self).setUp() - - self.getSwitchPorts() - # initialize switch - self.start_switch() - - self.switch_resources = self.saveNumberOfAvaiableResources(debug=True) - - # get default vlan - attr = sai_thrift_get_switch_attribute( - self.client, default_vlan_id=True) - self.default_vlan_id = attr['default_vlan_id'] - self.assertNotEqual(self.default_vlan_id, 0) - - self.recreate_ports() - - # get number of active ports - self.get_active_port_list() - - # get default vrf - attr = sai_thrift_get_switch_attribute( - self.client, default_virtual_router_id=True) - self.default_vrf = attr['default_virtual_router_id'] - self.assertNotEqual(self.default_vrf, 0) - - self.turn_up_and_check_ports() - - # get default 1Q bridge OID - self.get_default_1q_bridge_id() - - #remove all default 1Q bridge port - self.reset_1q_bridge_ports() - - # get cpu port - attr = sai_thrift_get_switch_attribute(self.client, cpu_port=True) - self.cpu_port_hdl = attr['cpu_port'] - self.assertNotEqual(self.cpu_port_hdl, 0) - - # get cpu port queue handles - self.check_cpu_port_hdl() - - print("Finish SaiHelperBase setup") - - - def tearDown(self): - try: - for port in self.port_list: - sai_thrift_clear_port_stats(self.client, port) - sai_thrift_set_port_attribute( - self.client, port, port_vlan_id=0) - #Todo: Remove this condition after brcm's remove_switch issue fixed - if get_platform() == 'brcm': - return - self.assertTrue(self.verifyNumberOfAvaiableResources( - self.switch_resources, debug=False)) - finally: - super(SaiHelperBase, self).tearDown() - - - def createPorts(self): - """ - Create ports after reading from port config file - """ - def fec_str_to_int(fec): - """ - Convert fec string to SAI enum - - Args: - fec (string): fec string from port_config - - Returns: - int: SAI enum value - """ - fec_dict = { - 'rs': SAI_PORT_FEC_MODE_RS, - 'fc': SAI_PORT_FEC_MODE_FC - } - return fec_dict.get(fec, SAI_PORT_FEC_MODE_NONE) - - # delete the existing ports - attr = sai_thrift_get_switch_attribute( - self.client, number_of_active_ports=True) - self.active_ports_no = attr['number_of_active_ports'] - attr = sai_thrift_get_switch_attribute( - self.client, port_list=sai_thrift_object_list_t( - idlist=[], count=self.active_ports_no)) - if self.active_ports_no: - self.port_list = attr['port_list'].idlist - for port in self.port_list: - sai_thrift_remove_port(self.client, port) - - # add new ports from port config file - self.ports_config = self.parsePortConfig( - self.test_params['port_config_ini']) - for name, port in self.ports_config.items(): - print("Creating port: %s" % name) - fec_mode = fec_str_to_int(port.get('fec', None)) - auto_neg_mode = True if port.get( - 'autoneg', "").lower() == "on" else False - sai_list = sai_thrift_u32_list_t( - count=len(port['lanes']), uint32list=port['lanes']) - sai_thrift_create_port(self.client, - hw_lane_list=sai_list, - fec_mode=fec_mode, - auto_neg_mode=auto_neg_mode, - speed=port['speed'], - admin_state=True) - - def parsePortConfig(self, port_config_file): - """ - Parse port_config.ini file - - Example of supported format for port_config.ini: - # name lanes alias index speed autoneg fec - Ethernet0 0 Ethernet0 1 25000 off none - Ethernet1 1 Ethernet1 1 25000 off none - Ethernet2 2 Ethernet2 1 25000 off none - Ethernet3 3 Ethernet3 1 25000 off none - Ethernet4 4 Ethernet4 2 25000 off none - Ethernet5 5 Ethernet5 2 25000 off none - Ethernet6 6 Ethernet6 2 25000 off none - Ethernet7 7 Ethernet7 2 25000 off none - Ethernet8 8 Ethernet8 3 25000 off none - Ethernet9 9 Ethernet9 3 25000 off none - Ethernet10 10 Ethernet10 3 25000 off none - Ethernet11 11 Ethernet11 3 25000 off none - etc - - Args: - port_config_file (string): path to port config file - - Returns: - dict: port configuation from file - - Raises: - e: exit if file not found - """ - ports = OrderedDict() - try: - with open(port_config_file) as conf: - for line in conf: - if line.startswith('#'): - if "name" in line: - titles = line.strip('#').split() - continue - tokens = line.split() - if len(tokens) < 2: - continue - name_index = titles.index('name') - name = tokens[name_index] - data = {} - for i, item in enumerate(tokens): - if i == name_index: - continue - data[titles[i]] = item - data['lanes'] = [int(lane) - for lane in data['lanes'].split(',')] - data['speed'] = int(data['speed']) - ports[name] = data - return ports - except Exception as e: - raise e - - def checkPortsUp(self, timeout=30): - """ - Wait for all ports to be UP - This may be required while testing on hardware - The test fails if all ports are not UP after timeout - - Args: - timeout (int): port verification timeout in sec - """ - allup = False - timer_start = time.time() - - while allup is False and time.time() - timer_start < timeout: - allup = True - for port in self.port_list: - attr = sai_thrift_get_port_attribute( - self.client, port, oper_status=True) - if attr['oper_status'] != SAI_SWITCH_OPER_STATUS_UP: - allup = False - break - if allup: - break - time.sleep(5) - - self.assertTrue(allup) - - def getSwitchPorts(self): - """ - Get device port numbers - """ - dev_no = 0 - for _, port, _ in config['interfaces']: - # remove after DASH will be introduced as confuses with "dev" prefix - # added tg as a shortcut for "traffic generator" - setattr(self, 'dev_port%d' % dev_no, port) - setattr(self, 'tg%d' % dev_no, port) - dev_no += 1 - - def printNumberOfAvaiableResources(self, resources_dict): - """ - Prints numbers of available resources - - Args: - resources_dict (dict): a dictionary with resources numbers - """ - - print("***** Number of available resources *****") - for key, value in resources_dict.items(): - print(key, ": ", value) - - def saveNumberOfAvaiableResources(self, debug=False): - """ - Save number of available resources - This allows to verify if all the test objects were removed - - Args: - debug (bool): enables debug option - Return: - dict: switch_resources dictionary with available resources - """ - - switch_resources = sai_thrift_get_switch_attribute( - self.client, - available_ipv4_route_entry=True, - available_ipv6_route_entry=True, - available_ipv4_nexthop_entry=True, - available_ipv6_nexthop_entry=True, - available_ipv4_neighbor_entry=True, - available_ipv6_neighbor_entry=True, - available_next_hop_group_entry=True, - available_next_hop_group_member_entry=True, - available_fdb_entry=True, - available_ipmc_entry=True, - available_snat_entry=True, - available_dnat_entry=True, - available_double_nat_entry=True, - number_of_ecmp_groups=True, - ecmp_members=True) - - if debug: - self.printNumberOfAvaiableResources(switch_resources) - - return switch_resources - - def verifyNumberOfAvaiableResources(self, init_resources, debug=False): - """ - Verify number of available resources - - Args: - init_resources (dict): a dictionary with initial resources numbers - debug (bool): enable debug option - - Returns: - bool: True if the numbers of resources are the same as before tests - """ - - available_resources = sai_thrift_get_switch_attribute( - self.client, - available_ipv4_route_entry=True, - available_ipv6_route_entry=True, - available_ipv4_nexthop_entry=True, - available_ipv6_nexthop_entry=True, - available_ipv4_neighbor_entry=True, - available_ipv6_neighbor_entry=True, - available_next_hop_group_entry=True, - available_next_hop_group_member_entry=True, - available_fdb_entry=True, - available_ipmc_entry=True, - available_snat_entry=True, - available_dnat_entry=True, - available_double_nat_entry=True, - number_of_ecmp_groups=True, - ecmp_members=True) - - for key, value in available_resources.items(): - if value != init_resources[key]: - if debug: - print("Number of %s incorrect! Current value: %d, Init value: %d" % (key, value, init_resources[key])) - return False - - return True - - @staticmethod - def status(): - """ - Returns the last operation status. - - Returns: - int: sai call result - """ - return adapter.status - - @staticmethod - def saiWaitFdbAge(timeout): - """ - Wait for fdb entry to ageout - - Args: - timeout (int): Timeout value in seconds - """ - print("Waiting for fdb entry to age") - aging_interval_buffer = 10 - time.sleep(timeout + aging_interval_buffer) - - -class SaiHelperUtilsMixin: - """ - Mixin utils class providing API for convenient SAI objects creation/deletion - """ - - def create_bridge_ports(self, ports=None): - """ - Create bridge ports base on port_list. - """ - ports = ports or range(0, len(self.port_list)) - for port_index in ports: - port_id = getattr(self, 'port%s' % port_index) - port_bp = sai_thrift_create_bridge_port( - self.client, - bridge_id=self.default_1q_bridge, - port_id=port_id, - type=SAI_BRIDGE_PORT_TYPE_PORT, - admin_state=True) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - setattr(self, 'port%s_bp' % port_index, port_bp) - self.def_bridge_port_list.append(port_bp) - - def destroy_bridge_ports(self): - for bridge_port in self.def_bridge_port_list: - sai_thrift_remove_bridge_port(self.client, bridge_port) - - def create_lag_with_members(self, lag_index, ports): - # create lag - lag_id = sai_thrift_create_lag(self.client) - setattr(self, 'lag%s' % lag_index, lag_id) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - self.def_lag_list.append(lag_id) - - # add LAG to bridge - lag_bp = sai_thrift_create_bridge_port( - self.client, - bridge_id=self.default_1q_bridge, - port_id=lag_id, - type=SAI_BRIDGE_PORT_TYPE_PORT, - admin_state=True) - - setattr(self, 'lag%s_bp' % lag_index, lag_bp) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - self.def_bridge_port_list.append(lag_bp) - - # add lag members - for member_index in ports: - port_id = getattr(self, 'port%s' % member_index) - lag_member = sai_thrift_create_lag_member( - self.client, lag_id=lag_id, port_id=port_id) - setattr(self, "lag%s_member%s" % (lag_index, member_index), lag_member) - - self.def_lag_member_list.append(lag_member) - - def destroy_lags_with_members(self): - for lag_member in self.def_lag_member_list: - sai_thrift_remove_lag_member(self.client, lag_member) - for lag in self.def_lag_list: - sai_thrift_remove_lag(self.client, lag) - - def create_vlan_with_members(self, vlan_index, members): - # create vlan - vlan_id = sai_thrift_create_vlan(self.client, vlan_id=vlan_index) - setattr(self, 'vlan%s' % vlan_index, vlan_id) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - self.def_vlan_list.append(vlan_id) - - # add members - idx = 0 - for member, tag in members.items(): - tag = SAI_VLAN_TAGGING_MODE_UNTAGGED - if tag == 'tagged': - tag = SAI_VLAN_TAGGING_MODE_TAGGED - vlan_member_id = sai_thrift_create_vlan_member( - self.client, - vlan_id=vlan_id, - bridge_port_id=member, - vlan_tagging_mode=tag) - setattr(self, 'vlan%s_member%s' % (vlan_index, idx), vlan_member_id) - self.def_vlan_member_list.append(vlan_member_id) - idx = idx + 1 - - def destroy_vlans_with_members(self): - for vlan_member in self.def_vlan_member_list: - sai_thrift_remove_vlan_member(self.client, vlan_member) - for vlan in self.def_vlan_list: - sai_thrift_remove_vlan(self.client, vlan) - - def create_routing_interfaces(self, vlans = None, lags = None, ports = None): - # iterate through vlans - if vlans is not None: - for vlan in vlans: - vlan_id = getattr(self, "vlan%s" % vlan) - vlan_rif = sai_thrift_create_router_interface( - self.client, - type=SAI_ROUTER_INTERFACE_TYPE_VLAN, - virtual_router_id=self.default_vrf, - vlan_id=vlan_id) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - setattr(self, "vlan%s_rif" % vlan, vlan_rif) - self.def_rif_list.append(vlan_rif) - - # iterate through lags - if lags is not None: - for lag in lags: - lag_id = getattr(self, "lag%s" % lag) - lag_rif = sai_thrift_create_router_interface( - self.client, - type=SAI_ROUTER_INTERFACE_TYPE_PORT, - virtual_router_id=self.default_vrf, - port_id=lag_id) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - setattr(self, "lag%s_rif" % lag, lag_rif) - self.def_rif_list.append(lag_rif) - - # iterate through ports - if ports is not None: - for port in ports: - port_id = getattr(self, 'port%s' % port) - port_rif = sai_thrift_create_router_interface( - self.client, - type=SAI_ROUTER_INTERFACE_TYPE_PORT, - virtual_router_id=self.default_vrf, - port_id=port_id) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - setattr(self, "port%s_rif" % port, port_rif) - self.def_rif_list.append(port_rif) - - def destroy_routing_interfaces(self): - for rif in self.def_rif_list: - sai_thrift_remove_router_interface(self.client, rif) - - -class SaiHelperSimplified(SaiHelperUtilsMixin, SaiHelperBase): - """ - SAI test helper class for DUT with limited port resources - without initial switch ports setup - """ - - def __getattr__(self, name): - """ - Skip the test in case of "port\d+" attribute does not exist - """ - # NOTE: check only ports for now - found = re.findall(r'^port\d+$', name) - if found and found[0]: - self.skipTest(SKIP_TEST_NO_RESOURCES_MSG) - - def setUp(self): - super(SaiHelperSimplified, self).setUp() - - # lists of default objects - self.def_bridge_port_list = [] - self.def_lag_list = [] - self.def_lag_member_list = [] - self.def_vlan_list = [] - self.def_vlan_member_list = [] - self.def_rif_list = [] - - def tearDown(self): - super(SaiHelperSimplified, self).tearDown() - - -class SaiHelper(SaiHelperUtilsMixin, SaiHelperBase): - """ - Set common base ports configuration for tests - - Common ports configuration: - * U/T = untagged/tagged VLAN member - +--------+------+-----------+-------------+--------+------------+------------+ - | Port | LAG | _member | Bridge port | VLAN | _member | RIF | - +========+======|===========+=============+========+============+============+ - | port0 | | | port0_bp | vlan10 | _member0 U | | - | port1 | | | port1_bp | | _member1 T | | - +--------+------+-----------+-------------+--------+------------+------------+ - | port2 | | | port2_bp | vlan20 | _member0 U | | - | port3 | | | port3_bp | | _member1 T | | - +--------+------+-----------+-------------+--------+------------+------------+ - | port4 | lag1 | _member4 | lag1_bp | vlan10 | _member2 U | | - | port5 | | _member5 | | | | | - | port6 | | _member6 | | | | | - +--------+------+-----------+-------------+--------+------------+------------+ - | port7 | lag2 | _member7 | lag2_bp | vlan20 | _member2 T | | - | port8 | | _member8 | | | | | - | port9 | | _member9 | | | | | - +--------+------+-----------+-------------+--------+------------+------------+ - | port10 | | | | | | port10_rif | - +--------+------+-----------+-------------+--------+------------+------------+ - | port11 | | | | | | port11_rif | - +--------+------+-----------+-------------+--------+------------+------------+ - | port12 | | | | | | port12_rif | - +--------+------+-----------+-------------+--------+------------+------------+ - | port13 | | | | | | port13_rif | - +--------+------+-----------+-------------+--------+------------+------------+ - | port14 | lag3 | _member14 | | | | lag3_rif | - | port15 | | _member15 | | | | | - | port16 | | _member16 | | | | | - +--------+------+-----------+-------------+--------+------------+------------+ - | port17 | lag4 | _member17 | | | | lag4_rif | - | port18 | | _member18 | | | | | - | port19 | | _member19 | | | | | - +--------+------+-----------+-------------+--------+------------+------------+ - | port20 | | | port20_bp | vlan30 | _member0 U | vlan30_rif | - | port21 | | | port21_bp | | _member1 T | | - +--------+------+-----------+-------------+--------+------------+------------+ - | port22 | lag5 | _member22 | lag5_bp | vlan30 | _member2 T | | - | port23 | | _member23 | | | | | - +--------+------+-----------+-------------+--------+------------+------------+ - | port24 | | - | port25 | | - | port26 | | - | port27 | UNASSIGNED | - | port28 | | - | port29 | | - | port30 | | - | port31 | | - +--------+-------------------------------------------------------------------+ - """ - - def setUp(self): - super(SaiHelper, self).setUp() - - # lists of default objects - self.def_bridge_port_list = [] - self.def_lag_list = [] - self.def_lag_member_list = [] - self.def_vlan_list = [] - self.def_vlan_member_list = [] - self.def_rif_list = [] - - # create bridge ports - self.create_bridge_ports(ports=[0, 1, 2, 3, 20, 21]) - - self.create_lag_with_members(1, ports=[4, 5, 6]) - self.create_lag_with_members(2, ports=[7, 8, 9]) - - # L3 lags - self.create_lag_with_members(3, ports=[14, 15, 16]) - self.create_lag_with_members(4, ports=[17, 18, 19]) - self.create_lag_with_members(5, ports=[22, 23]) - - # create vlan 10 with port0, port1 and lag1 - self.create_vlan_with_members(10, {self.port0_bp: 'untagged', - self.port1_bp: 'tagged', - self.lag1_bp: 'untagged'}) - - # create vlan 20 with port2, port3 and lag2 - self.create_vlan_with_members(20, {self.port2_bp: 'untagged', - self.port3_bp: 'tagged', - self.lag2_bp: 'untagged'}) - - # create vlan 30 with port20, port21 and lag5 - self.create_vlan_with_members(30, {self.port20_bp: 'untagged', - self.port21_bp: 'tagged', - self.lag5_bp: 'untagged'}) - - # setup untagged ports - sai_thrift_set_port_attribute(self.client, self.port0, port_vlan_id=10) - sai_thrift_set_lag_attribute(self.client, self.lag1, port_vlan_id=10) - sai_thrift_set_port_attribute(self.client, self.port2, port_vlan_id=20) - - # create L3 configuration - self.create_routing_interfaces(vlans=[30]) - self.create_routing_interfaces(lags=[3, 4]) - self.create_routing_interfaces(ports=[10, 11, 12, 13]) - - def tearDown(self): - sai_thrift_set_port_attribute(self.client, self.port2, port_vlan_id=0) - sai_thrift_set_lag_attribute(self.client, self.lag1, port_vlan_id=0) - sai_thrift_set_port_attribute(self.client, self.port0, port_vlan_id=0) - - self.destroy_routing_interfaces() - self.destroy_vlans_with_members() - self.destroy_bridge_ports() - self.destroy_lags_with_members() - - super(SaiHelper, self).tearDown() - - -class MinimalPortVlanConfig(SaiHelperBase): - """ - Minimal port and vlan configuration. Create port_num bridge ports and add - them to VLAN with vlan_id. Configure ports as untagged - """ - - def __init__(self, port_num, vlan_id=100): - """ - Args: - port_num (int): Number of ports to configure - vlan_id (int): ID of VLAN that will be created - """ - super(MinimalPortVlanConfig, self).__init__() - - self.port_num = port_num - self.vlan_id = vlan_id - - def setUp(self): - super(MinimalPortVlanConfig, self).setUp() - - if self.port_num > self.active_ports_no: - raise ValueError('Number of ports to configure %d is higher ' - 'than number of active ports %d' - % (self.port_num, self.active_ports_no)) - - self.def_bridge_port_list = [] - self.def_vlan_member_list = [] - - # create bridge ports - for port in self.port_list: - bp = sai_thrift_create_bridge_port( - self.client, bridge_id=self.default_1q_bridge, - port_id=port, type=SAI_BRIDGE_PORT_TYPE_PORT, - admin_state=True) - - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - self.def_bridge_port_list.append(bp) - - # create vlan - self.vlan = sai_thrift_create_vlan(self.client, vlan_id=self.vlan_id) - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - - # add ports to vlan - for bridge_port in self.def_bridge_port_list: - vm = sai_thrift_create_vlan_member( - self.client, vlan_id=self.vlan, - bridge_port_id=bridge_port, - vlan_tagging_mode=SAI_VLAN_TAGGING_MODE_UNTAGGED) - - self.assertEqual(self.status(), SAI_STATUS_SUCCESS) - self.def_vlan_member_list.append(vm) - - # setup untagged ports - for port in self.port_list: - status = sai_thrift_set_port_attribute( - self.client, port, port_vlan_id=self.vlan_id) - - self.assertEqual(status, SAI_STATUS_SUCCESS) - - def tearDown(self): - # revert untagged ports configuration - for port in self.port_list: - sai_thrift_set_port_attribute( - self.client, port, port_vlan_id=0) - - # remove ports from vlan - for vlan_member in self.def_vlan_member_list: - sai_thrift_remove_vlan_member(self.client, vlan_member) - - # remove vlan - sai_thrift_remove_vlan(self.client, self.vlan) - - # remove bridge ports - for bridge_port in self.def_bridge_port_list: - sai_thrift_remove_bridge_port(self.client, bridge_port) - - super(MinimalPortVlanConfig, self).tearDown() - - -def get_platform(): - """ - Get the platform token. - - If environment variable [PLATFORM] doesn't exist, then the default platform will be 'common'. - If environment variable [PLATFORM] exist but platform name is unknown raise ValueError. - If environment variable [PLATFORM] exist but platform name is unspecified, - then the default platform will be 'common'. - If specified any one, it will try to concert it from standard name to a shortened name (case insensitive). \r - \ti.e. Broadcom -> brcm - """ - pl = 'common' - - if 'PLATFORM' in os.environ: - pl_low = PLATFORM.lower() - if pl_low in platform_map.keys(): - pl = platform_map[pl_low] - elif pl_low in platform_map.values(): - pl = pl_low - elif PLATFORM == '': - print("Platform not set. The common platform was selected") - else: - raise ValueError("Undefined platform: {}.".format(pl_low)) - else: - print("Platform not set. The common platform was selected") - - return pl - - -from platform_helper.common_sai_helper import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] -#from platform_helper.bfn_sai_helper import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] -#from platform_helper.brcm_sai_helper import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] -#from platform_helper.mlnx_sai_helper import * # pylint: disable=wildcard-import; lgtm[py/polluting-import] - -class PlatformSaiHelper(SaiHelper): - """ - Class uses to extend from SaiHelper, base on the [platform] class attribute, - dynamic select a subclass from the platform_helper. - """ - def __new__(cls, *args, **kwargs): - sai_helper_subclass_map = {subclass.platform: subclass for subclass in SaiHelper.__subclasses__()} - common_sai_helper_subclass_map = {subclass.platform: subclass for subclass in CommonSaiHelper.__subclasses__()} - pl = get_platform() - - if pl in common_sai_helper_subclass_map: - target_base_class = common_sai_helper_subclass_map[pl] - else: - target_base_class = sai_helper_subclass_map[pl] - - cls.__bases__ = (target_base_class,) - - instance = target_base_class.__new__(cls, *args, **kwargs) - return instance diff --git a/dash-pipeline/tests/saithrift/ptf/sai_utils.py b/dash-pipeline/tests/saithrift/ptf/sai_utils.py deleted file mode 100644 index da17e7fae..000000000 --- a/dash-pipeline/tests/saithrift/ptf/sai_utils.py +++ /dev/null @@ -1,329 +0,0 @@ -# Copyright 2021-present Intel Corporation. -# -# 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 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Thrift SAI interface basic utils. -""" - -import time -import struct -import socket - -from functools import wraps - -from ptf.packet import * -from ptf.testutils import * - -from sai_thrift.sai_adapter import * - - -def sai_thrift_query_attribute_enum_values_capability(client, - obj_type, - attr_id=None): - """ - Call the sai_thrift_query_attribute_enum_values_capability() function - and return the list of supported aattr_is enum capabilities - - Args: - client (Client): SAI RPC client - obj_type (enum): SAI object type - attr_id (attr): SAI attribute name - - Returns: - list: list of switch object type enum capabilities - """ - max_cap_no = 20 - - enum_cap_list = client.sai_thrift_query_attribute_enum_values_capability( - obj_type, attr_id, max_cap_no) - - return enum_cap_list - - -def sai_thrift_object_type_get_availability(client, - obj_type, - attr_id=None, - attr_type=None): - """ - sai_thrift_object_type_get_availability() RPC client function - implementation - - Args: - client (Client): SAI RPC client - obj_type (enum): SAI object type - attr_id (attr): SAI attribute name - attr_type (type): SAI attribute type - - Returns: - uint: number of available resources with given parameters - """ - availability_cnt = client.sai_thrift_object_type_get_availability( - obj_type, attr_id, attr_type) - - return availability_cnt - - -def sai_thrift_object_type_query(client, - obj_id=None): - """ - sai_thrift_object_type_query() RPC client function - implementation - - Args: - client (Client): SAI RPC client - obj_id (obj): SAI object id - - Returns: - uint: object type - """ - obj_type = client.sai_object_type_query( - obj_id) - - return obj_type - - -def sai_thrift_switch_id_query(client, - obj_id=None): - """ - sai_thrift_switch_id_query() RPC client function - implementation - - Args: - client (Client): SAI RPC client - obj_id (obj): SAI object id - - Returns: - uint: object type - """ - switch_obj_id = client.sai_switch_id_query( - obj_id) - - return switch_obj_id - - -def sai_thrift_get_debug_counter_port_stats(client, port_oid, counter_ids): - """ - Get port statistics for given debug counters - - Args: - client (Client): SAI RPC client - port_oid (sai_thrift_object_id_t): object_id IN argument - counter_ids (sai_stat_id_t): list of requested counters - - Returns: - Dict[str, sai_thrift_uint64_t]: stats - """ - - stats = {} - counters = client.sai_thrift_get_port_stats(port_oid, counter_ids) - - for i, counter_id in enumerate(counter_ids): - stats[counter_id] = counters[i] - - return stats - - -def sai_thrift_get_debug_counter_switch_stats(client, counter_ids): - """ - Get switch statistics for given debug counters - - Args: - client (Client): SAI RPC client - counter_ids (sai_stat_id_t): list of requested counters - - Returns: - Dict[str, sai_thrift_uint64_t]: stats - """ - - stats = {} - counters = client.sai_thrift_get_switch_stats(counter_ids) - - for i, counter_id in enumerate(counter_ids): - stats[counter_id] = counters[i] - - return stats - - -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 - - -def sai_ipprefix(prefix_str): - """ - Set IP address prefix and mask and return ip_prefix object - - Args: - prefix_str (str): IP address and mask string (with slash notation) - - Return: - sai_thrift_ip_prefix_t: IP prefix object - """ - addr_mask = prefix_str.split('/') - if len(addr_mask) != 2: - print("Invalid IP prefix format") - return None - - if '.' in prefix_str: - family = SAI_IP_ADDR_FAMILY_IPV4 - addr = sai_thrift_ip_addr_t(ip4=addr_mask[0]) - mask = num_to_dotted_quad(addr_mask[1]) - mask = sai_thrift_ip_addr_t(ip4=mask) - if ':' in prefix_str: - family = SAI_IP_ADDR_FAMILY_IPV6 - addr = sai_thrift_ip_addr_t(ip6=addr_mask[0]) - mask = num_to_dotted_quad(int(addr_mask[1]), ipv4=False) - mask = sai_thrift_ip_addr_t(ip6=mask) - - ip_prefix = sai_thrift_ip_prefix_t( - addr_family=family, addr=addr, mask=mask) - return ip_prefix - - -def num_to_dotted_quad(address, ipv4=True): - """ - Helper function to convert the ip address - - Args: - address (str): IP address - ipv4 (bool): determines what IP version is handled - - Returns: - str: formatted IP address - """ - if ipv4 is True: - mask = (1 << 32) - (1 << 32 >> int(address)) - return socket.inet_ntop(socket.AF_INET, struct.pack('>L', mask)) - - mask = (1 << 128) - (1 << 128 >> int(address)) - i = 0 - result = '' - for sign in str(hex(mask)[2:]): - if (i + 1) % 4 == 0: - result = result + sign + ':' - else: - result = result + sign - i += 1 - return result[:-1] - - -def open_packet_socket(hostif_name): - """ - Open a linux socket - - Args: - hostif_name (str): socket interface name - - Return: - sock: socket ID - """ - eth_p_all = 3 - sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, - socket.htons(eth_p_all)) - sock.bind((hostif_name, eth_p_all)) - sock.setblocking(0) - - return sock - - -def socket_verify_packet(pkt, sock, timeout=2): - """ - Verify packet was received on a socket - - Args: - pkt (packet): packet to match with - sock (int): socket ID - timeout (int): timeout - - Return: - bool: True if packet matched - """ - max_pkt_size = 9100 - timeout = time.time() + timeout - match = False - - if isinstance(pkt, ptf.mask.Mask): - if not pkt.is_valid(): - return False - - while time.time() < timeout: - try: - packet_from_tap_device = Ether(sock.recv(max_pkt_size)) - - if isinstance(pkt, ptf.mask.Mask): - match = pkt.pkt_match(packet_from_tap_device) - else: - match = (str(packet_from_tap_device) == str(pkt)) - - if match: - break - - except BaseException: - pass - - return match - - -def delay_wrapper(func, delay=2): - """ - A wrapper extending given function by a delay - - Args: - func (function): function to be wrapped - delay (int): delay period in sec - - Return: - wrapped_function: wrapped function - """ - @wraps(func) - def wrapped_function(*args, **kwargs): - """ - A wrapper function adding a delay - - Args: - args (tuple): function arguments - kwargs (dict): keyword function arguments - - Return: - status: original function return value - """ - test_params = test_params_get() - if test_params['target'] != "hw": - time.sleep(delay) - - status = func(*args, **kwargs) - return status - - return wrapped_function - - -sai_thrift_flush_fdb_entries = delay_wrapper(sai_thrift_flush_fdb_entries) diff --git a/dash-pipeline/tests/saithrift/ptf/saidashvnet.py b/test/test-cases/functional/saidashvnet.py similarity index 93% rename from dash-pipeline/tests/saithrift/ptf/saidashvnet.py rename to test/test-cases/functional/saidashvnet.py index 718770071..1331f280e 100644 --- a/dash-pipeline/tests/saithrift/ptf/saidashvnet.py +++ b/test/test-cases/functional/saidashvnet.py @@ -66,7 +66,34 @@ def eni_create(self, **kwargs): Create ENI """ - eni_id = sai_thrift_create_eni(self.client, **kwargs) + default_kwargs = { + "admin_state": True, + "vm_underlay_dip": sai_ipaddress("0.0.0.0"), + "vm_vni": 1, + "vnet_id": 1, + "inbound_v4_stage1_dash_acl_group_id": 0, + "inbound_v4_stage2_dash_acl_group_id": 0, + "inbound_v4_stage3_dash_acl_group_id": 0, + "inbound_v4_stage4_dash_acl_group_id": 0, + "inbound_v4_stage5_dash_acl_group_id": 0, + "outbound_v4_stage1_dash_acl_group_id": 0, + "outbound_v4_stage2_dash_acl_group_id": 0, + "outbound_v4_stage3_dash_acl_group_id": 0, + "outbound_v4_stage4_dash_acl_group_id": 0, + "outbound_v4_stage5_dash_acl_group_id": 0, + "inbound_v6_stage1_dash_acl_group_id": 0, + "inbound_v6_stage2_dash_acl_group_id": 0, + "inbound_v6_stage3_dash_acl_group_id": 0, + "inbound_v6_stage4_dash_acl_group_id": 0, + "inbound_v6_stage5_dash_acl_group_id": 0, + "outbound_v6_stage1_dash_acl_group_id": 0, + "outbound_v6_stage2_dash_acl_group_id": 0, + "outbound_v6_stage3_dash_acl_group_id": 0, + "outbound_v6_stage4_dash_acl_group_id": 0, + "outbound_v6_stage5_dash_acl_group_id": 0 + } + default_kwargs.update(kwargs) + eni_id = sai_thrift_create_eni(self.client, **default_kwargs) self.assertEqual(self.status(), SAI_STATUS_SUCCESS) self.assertNotEqual(eni_id, 0) self.add_teardown_obj(self.eni_remove, eni_id) diff --git a/test/test-cases/run-functional-tests.sh b/test/test-cases/run-functional-tests.sh new file mode 100755 index 000000000..accbd8a40 --- /dev/null +++ b/test/test-cases/run-functional-tests.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# To be run inside saithrift-client container, assumes SAI repo portions exist under /SAI directory + +ptf --test-dir functional --pypath /SAI/ptf --interface 0@veth1 --interface 1@veth3 $@ From 624d5b3d3714e79a02db26cf63a8b98e4e6764b1 Mon Sep 17 00:00:00 2001 From: Yuriy Harhas Date: Wed, 28 Sep 2022 11:20:23 +0200 Subject: [PATCH 6/8] Fixed ENI creation in Outbound tests Signed-off-by: Yuriy Harhas --- test/test-cases/functional/saidashvnet.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/test/test-cases/functional/saidashvnet.py b/test/test-cases/functional/saidashvnet.py index 1331f280e..36cb507d9 100644 --- a/test/test-cases/functional/saidashvnet.py +++ b/test/test-cases/functional/saidashvnet.py @@ -560,6 +560,7 @@ def setUp(self): +----------+-----------+ """ self.VIP_ADDRESS = "10.1.1.1" # Appliance IP address + self.ENI_MAC = "00:01:00:00:03:14" self.SRC_VM_VNI = 1 self.DST_VM_VNI = 2 @@ -572,9 +573,13 @@ def configureTest(self): # direction lookup VNI, reserved VNI assigned to the VM->Appliance self.direction_lookup_create(self.SRC_VM_VNI) + vnet_id_1 = self.vnet_create(self.SRC_VM_VNI) + + eni_id = self.eni_create(vm_vni=self.SRC_VM_VNI, + vm_underlay_dip=sai_ipaddress("10.10.1.10"), + vnet_id=vnet_id_1) + self.eni_mac_map_create(eni_id, self.ENI_MAC) # ENI MAC address - eni_id = self.eni_create(vm_vni=self.SRC_VM_VNI) # VM VNI = 1 - self.eni_mac_map_create(eni_id, "00:01:00:00:03:14") # ENI MAC address vnet_id_2 = self.vnet_create(self.DST_VM_VNI) # VNET VNI = 2 # outbound routing self.outbound_routing_vnet_direct_create(eni_id, "192.168.1.0/24", vnet_id_2, @@ -593,7 +598,7 @@ def runTest(self): self.configureTest() # send packet and check - inner_pkt = simple_udp_packet(eth_src="00:00:00:09:03:14", + inner_pkt = simple_udp_packet(eth_src=self.ENI_MAC, eth_dst="20:30:40:50:60:70", ip_dst="192.168.1.1", ip_src="192.168.0.1", @@ -659,6 +664,7 @@ def setUp(self): """ self.VIP_ADDRESS = "10.1.1.1" # Appliance VIP address + self.ENI_MAC = "00:01:00:00:03:14" self.SRC_VM_VNI = 1 def configureTest(self): @@ -671,8 +677,12 @@ def configureTest(self): # direction lookup VNI, reserved VNI assigned to the VM->Appliance self.direction_lookup_create(self.SRC_VM_VNI) - eni_id = self.eni_create(vm_vni=self.SRC_VM_VNI) # VM VNI = 1 - self.eni_mac_map_create(eni_id, "00:01:00:00:03:14") # ENI MAC address + vnet_id_1 = self.vnet_create(self.SRC_VM_VNI) + + eni_id = self.eni_create(vm_vni=self.SRC_VM_VNI, + vm_underlay_dip=sai_ipaddress("10.10.1.10"), + vnet_id=vnet_id_1) + self.eni_mac_map_create(eni_id, self.ENI_MAC) # ENI MAC address # outbound routing self.outbound_routing_direct_create(eni_id, "192.168.1.0/24") @@ -688,7 +698,7 @@ def runTest(self): self.configureTest() # send packet and check - inner_pkt = simple_udp_packet(eth_src="00:00:00:09:03:14", + inner_pkt = simple_udp_packet(eth_src=self.ENI_MAC, eth_dst="20:30:40:50:60:70", ip_dst="192.168.1.1", ip_src="192.168.0.1", From 42c06acfbbdf5e06f8f00836b8ccbdc9d2064cb5 Mon Sep 17 00:00:00 2001 From: Anton Putria Date: Thu, 29 Sep 2022 11:10:55 +0300 Subject: [PATCH 7/8] Spellchecker related fixes. Signed-off-by: Anton Putria --- test/test-cases/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-cases/README.md b/test/test-cases/README.md index b6ca1796e..be8fc23f5 100644 --- a/test/test-cases/README.md +++ b/test/test-cases/README.md @@ -6,13 +6,13 @@ This contains a hierarchical set of directories containing DASH test cases organ | Folder | Description | | --- | --- | | functional | Tests to verify essential functionality using low-rate traffic (SAI PTF). -| scale | Tests with hight-rate traffic and complex configuration to verify scaling real-world scenarios. +| scale | Tests with high-rate traffic and complex configuration to verify scaling real-world scenarios. ## functional | Folder/File | Description | | --- | --- | -| saidashvnet.py | VNET-to-VNET test cases +| `saidashvnet.py` | VNET-to-VNET test cases ## scale From bc8fc58fefff9e2fb9ddb854b45ddb86e4db4bcd Mon Sep 17 00:00:00 2001 From: Yuriy Harhas Date: Thu, 29 Sep 2022 12:31:33 +0200 Subject: [PATCH 8/8] Updated VNET test cases based on the PR #238. Signed-off-by: Yuriy Harhas --- test/test-cases/functional/saidashvnet.py | 30 +++-------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/test/test-cases/functional/saidashvnet.py b/test/test-cases/functional/saidashvnet.py index 36cb507d9..e8bb05cd3 100644 --- a/test/test-cases/functional/saidashvnet.py +++ b/test/test-cases/functional/saidashvnet.py @@ -103,16 +103,16 @@ def eni_create(self, **kwargs): def eni_remove(self, eni_id): sai_thrift_remove_eni(self.client, eni_id) - def direction_lookup_create(self, vni, drop=False): + def direction_lookup_create(self, vni): """ Create direction lookup entry """ act = SAI_DIRECTION_LOOKUP_ENTRY_ACTION_SET_OUTBOUND_DIRECTION - if drop: - act = SAI_DIRECTION_LOOKUP_ENTRY_ACTION_DENY + direction_lookup_entry = sai_thrift_direction_lookup_entry_t(switch_id=self.switch_id, vni=vni) sai_thrift_create_direction_lookup_entry(self.client, direction_lookup_entry, action=act) + self.assertEqual(self.status(), SAI_STATUS_SUCCESS) self.add_teardown_obj(self.direction_lookup_remove, direction_lookup_entry) @@ -380,7 +380,6 @@ def setUp(self): def runTest(self): self.configureTest() self.vnet2VnetInboundPaValidatePermitTest() - self.vnet2VnetInboundDenyVniTest() self.vnet2VnetInboundRouteInvalidVniTest() self.vnet2VnetInboundInvalidEniMacTest() self.vnet2VnetInboundInvalidPaSrcIpTest() @@ -467,28 +466,6 @@ def vnet2VnetInboundPaValidatePermitTest(self): print('\n', self.vnet2VnetInboundPaValidatePermitTest.__name__, ' OK') - def vnet2VnetInboundDenyVniTest(self): - """ - Inbound VNET to VNET test with SAI_DIRECTION_LOOKUP_ENTRY_ACTION_DENY action - Verifies packet drop - """ - - # drop direction lookup VNI - drop_vni = 3 - self.direction_lookup_create(drop_vni, drop=True) - - # VNI matches Direction lookup Deny action - vxlan_pkt_invalid_vni = copy(self.vxlan_pkt) - vxlan_pkt_invalid_vni.getlayer('VXLAN').vni = drop_vni - - print("Sending VxLAN IPv4 packet with VNI that matches direction lookup action Deny, expect drop") - - send_packet(self, self.dev_port0, vxlan_pkt_invalid_vni) - verify_no_packet(self, self.inner_eth_packet, self.dev_port1, timeout=1) - verify_no_packet(self, self.outer_eth_packet, self.dev_port1, timeout=1) - - print('\n', self.vnet2VnetInboundDenyVniTest.__name__, ' OK') - def vnet2VnetInboundInvalidEniMacTest(self): """ Inbound VNET to VNET test @@ -542,6 +519,7 @@ def vnet2VnetInboundRouteInvalidVniTest(self): print('\n', self.vnet2VnetInboundRouteInvalidVniTest.__name__, ' OK') + @group("draft") class Vnet2VnetOutboundRouteVnetDirectTest(VNetAPI): """