diff --git a/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml b/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml index 7a39616a88a..615d7633a1b 100644 --- a/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml +++ b/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml @@ -1108,6 +1108,12 @@ qos/test_qos_sai.py::TestQosSai::testQosSaiLossyQueueVoq: conditions: - "asic_type not in ['cisco-8000']" +qos/test_qos_sai.py::TestQosSai::testQosSaiLossyQueueVoqMultiSrc: + skip: + reason: "Lossy Queue Voq multiple source test is not supported" + conditions: + - "asic_type not in ['cisco-8000']" + qos/test_qos_sai.py::TestQosSai::testQosSaiPGDrop: skip: reason: "PG drop size test is not supported." diff --git a/tests/qos/files/cisco/qos_param_generator.py b/tests/qos/files/cisco/qos_param_generator.py index c40e9f2b991..3e2e65c7ed5 100644 --- a/tests/qos/files/cisco/qos_param_generator.py +++ b/tests/qos/files/cisco/qos_param_generator.py @@ -321,6 +321,25 @@ def __define_lossy_queue_voq(self): "packet_size": 64, "cell_size": self.buffer_size} self.write_params("lossy_queue_voq_1", params) + if self.should_autogen(["lossy_queue_voq_2"]): + params = {"dscp": 8, + "ecn": 1, + "pg": 0, + "flow_config": "shared", + "pkts_num_trig_egr_drp": self.max_depth // self.buffer_size, + "pkts_num_margin": 4, + "packet_size": 64, + "cell_size": self.buffer_size} + self.write_params("lossy_queue_voq_2", params) + if self.should_autogen(["lossy_queue_voq_3"]): + params = {"dscp": 8, + "ecn": 1, + "pg": 0, + "pkts_num_trig_egr_drp": self.max_depth // self.buffer_size, + "pkts_num_margin": 4, + "packet_size": 64, + "cell_size": self.buffer_size} + self.write_params("lossy_queue_voq_3", params) def __define_lossy_queue(self): if self.should_autogen(["lossy_queue_1"]): diff --git a/tests/qos/files/qos_params.gb.yaml b/tests/qos/files/qos_params.gb.yaml index 3c56825638e..aff59779ced 100644 --- a/tests/qos/files/qos_params.gb.yaml +++ b/tests/qos/files/qos_params.gb.yaml @@ -208,15 +208,6 @@ qos_params: pkts_num_margin: 4 packet_size: 1350 cell_size: 384 - lossy_queue_voq_2: - dscp: 8 - ecn: 1 - pg: 0 - flow_config: shared - pkts_num_trig_egr_drp: 16000 - pkts_num_margin: 4 - packet_size: 64 - cell_size: 384 wm_pg_shared_lossless: dscp: 3 ecn: 1 @@ -308,15 +299,6 @@ qos_params: pkts_num_margin: 4 packet_size: 1350 cell_size: 384 - lossy_queue_voq_2: - dscp: 8 - ecn: 1 - pg: 0 - flow_config: shared - pkts_num_trig_egr_drp: 16000 - pkts_num_margin: 4 - packet_size: 64 - cell_size: 384 wm_pg_shared_lossless: dscp: 3 ecn: 1 diff --git a/tests/qos/qos_sai_base.py b/tests/qos/qos_sai_base.py index 40c6d6e3fcf..15abcfc3690 100644 --- a/tests/qos/qos_sai_base.py +++ b/tests/qos/qos_sai_base.py @@ -2208,6 +2208,16 @@ def skip_pacific_dst_asic(self, dutConfig): yield return + @pytest.fixture(scope="function", autouse=False) + def skip_longlink(self, dutQosConfig): + portSpeedCableLength = dutQosConfig["portSpeedCableLength"] + match = re.search("_([0-9]*)m", portSpeedCableLength) + if match and int(match.group(1)) > 2000: + pytest.skip( + "This test is skipped for longlink.") + yield + return + def populate_arp_entries( self, get_src_dst_asic_and_duts, ptfhost, dutTestParams, dutConfig, releaseAllPorts, handleFdbAging, tbinfo, lower_tor_host # noqa F811 diff --git a/tests/qos/test_qos_sai.py b/tests/qos/test_qos_sai.py index 8d9e4cfa00d..366de0b5c49 100644 --- a/tests/qos/test_qos_sai.py +++ b/tests/qos/test_qos_sai.py @@ -1197,7 +1197,7 @@ def testQosSaiLossyQueue( testParams=testParams ) - @pytest.mark.parametrize("LossyVoq", ["lossy_queue_voq_1"]) + @pytest.mark.parametrize("LossyVoq", ["lossy_queue_voq_1", "lossy_queue_voq_2"]) def testQosSaiLossyQueueVoq( self, LossyVoq, ptfhost, dutTestParams, dutConfig, dutQosConfig, ingressLossyProfile, duthost, localhost, get_src_dst_asic_and_duts, @@ -2167,3 +2167,77 @@ def testQosSaiQWatermarkAllPorts( ptfhost, testCase="sai_qos_tests.QWatermarkAllPortTest", testParams=testParams ) + + def testQosSaiLossyQueueVoqMultiSrc( + self, ptfhost, dutTestParams, dutConfig, dutQosConfig, + get_src_dst_asic_and_duts, skip_longlink + ): + """ + Test QoS SAI Lossy queue with multiple source ports, applicable for fair-voq and split-voq + Args: + ptfhost (AnsibleHost): Packet Test Framework (PTF) + dutTestParams (Fixture, dict): DUT host test params + dutConfig (Fixture, dict): Map of DUT config containing dut interfaces, test port IDs, test port IPs, + and test ports + dutQosConfig (Fixture, dict): Map containing DUT host QoS configuration + Returns: + None + Raises: + RunAnsibleModuleFail if ptf test fails + """ + if not get_src_dst_asic_and_duts['single_asic_test']: + pytest.skip("LossyQueueVoqMultiSrc: This test is skipped on multi-asic," + "since same ingress backplane port will be used on egress asic.") + portSpeedCableLength = dutQosConfig["portSpeedCableLength"] + LossyVoq = "lossy_queue_voq_3" + if LossyVoq in dutQosConfig["param"][portSpeedCableLength].keys(): + qosConfig = dutQosConfig["param"][portSpeedCableLength] + else: + qosConfig = dutQosConfig["param"] + + self.updateTestPortIdIp(dutConfig, get_src_dst_asic_and_duts, qosConfig[LossyVoq]) + + src_dut_index = get_src_dst_asic_and_duts['src_dut_index'] + src_asic_index = get_src_dst_asic_and_duts['src_asic_index'] + + testParams = dict() + testParams.update(dutTestParams["basicParams"]) + all_src_ports = dutConfig["testPortIps"][src_dut_index][src_asic_index] + all_src_port_ids = set(all_src_ports.keys()) + if get_src_dst_asic_and_duts['single_asic_test']: + all_src_port_ids = set(all_src_ports.keys()) - \ + set([dutConfig["testPorts"]["src_port_id"], + dutConfig["testPorts"]["dst_port_id"], + dutConfig["testPorts"]["dst_port_2_id"], + dutConfig["testPorts"]["dst_port_3_id"]]) + all_src_port_ids = list(all_src_port_ids) + testParams.update({ + "dscp": qosConfig[LossyVoq]["dscp"], + "ecn": qosConfig[LossyVoq]["ecn"], + "pg": qosConfig[LossyVoq]["pg"], + "src_port_id": dutConfig["testPorts"]["src_port_id"], + "src_port_ip": dutConfig["testPorts"]["src_port_ip"], + "src_port_2_id": all_src_port_ids[0], + "src_port_2_ip": all_src_ports[all_src_port_ids[0]]['peer_addr'], + "dst_port_id": dutConfig["testPorts"]["dst_port_id"], + "dst_port_ip": dutConfig["testPorts"]["dst_port_ip"], + "pkts_num_leak_out": dutQosConfig["param"][portSpeedCableLength]["pkts_num_leak_out"], + "pkts_num_trig_egr_drp": qosConfig[LossyVoq]["pkts_num_trig_egr_drp"] + }) + + if "platform_asic" in dutTestParams["basicParams"]: + testParams["platform_asic"] = dutTestParams["basicParams"]["platform_asic"] + else: + testParams["platform_asic"] = None + + if "packet_size" in qosConfig[LossyVoq].keys(): + testParams["packet_size"] = qosConfig[LossyVoq]["packet_size"] + testParams["cell_size"] = qosConfig[LossyVoq]["cell_size"] + + if "pkts_num_margin" in qosConfig[LossyVoq].keys(): + testParams["pkts_num_margin"] = qosConfig[LossyVoq]["pkts_num_margin"] + + self.runPtfTest( + ptfhost, testCase="sai_qos_tests.LossyQueueVoqMultiSrcTest", + testParams=testParams + ) diff --git a/tests/saitests/py3/sai_qos_tests.py b/tests/saitests/py3/sai_qos_tests.py index 3066f54c922..ad60f32a629 100644 --- a/tests/saitests/py3/sai_qos_tests.py +++ b/tests/saitests/py3/sai_qos_tests.py @@ -5421,3 +5421,178 @@ def runTest(self): finally: self.sai_thrift_port_tx_enable(self.dst_client, asic_type, dst_port_ids) + + +class LossyQueueVoqMultiSrcTest(sai_base_test.ThriftInterfaceDataPlane): + def setUp(self): + sai_base_test.ThriftInterfaceDataPlane.setUp(self) + time.sleep(5) + switch_init(self.clients) + # Parse input parameters + self.dscp = int(self.test_params['dscp']) + self.ecn = int(self.test_params['ecn']) + # The pfc counter index starts from index 2 in sai_thrift_read_port_counters + self.pg = int(self.test_params['pg']) + 2 + self.sonic_version = self.test_params['sonic_version'] + self.dst_port_id = int(self.test_params['dst_port_id']) + self.dst_port_ip = self.test_params['dst_port_ip'] + self.dst_port_mac = self.dataplane.get_mac(0, self.dst_port_id) + router_mac = self.test_params['router_mac'] + self.dst_port_mac = router_mac if router_mac != '' else self.dst_port_mac + self.src_port_id = int(self.test_params['src_port_id']) + self.src_port_ip = self.test_params['src_port_ip'] + self.src_port_mac = self.dataplane.get_mac(0, self.src_port_id) + self.src_port_2_id = int(self.test_params['src_port_2_id']) + self.src_port_2_ip = self.test_params['src_port_2_ip'] + self.src_port_2_mac = self.dataplane.get_mac(0, self.src_port_2_id) + self.asic_type = self.test_params['sonic_asic_type'] + self.pkts_num_leak_out = int(self.test_params['pkts_num_leak_out']) + self.pkts_num_trig_egr_drp = int(self.test_params['pkts_num_trig_egr_drp']) + if 'packet_size' in self.test_params.keys(): + self.packet_length = int(self.test_params['packet_size']) + cell_size = int(self.test_params['cell_size']) + if self.packet_length != 64: + cell_occupancy = (self.packet_length + cell_size - 1) // cell_size + self.pkts_num_trig_egr_drp //= cell_occupancy + else: + self.packet_length = 64 + self.ttl = 64 + + def runTest(self): + print("dst_port_id: {}, src_port_id: {}, src_port_2_id: {}".format(self.dst_port_id, + self.src_port_id, + self.src_port_2_id), + file=sys.stderr) + # get counter names to query + ingress_counters, egress_counters = get_counter_names(self.sonic_version) + + port_counter_indexes = [self.pg] + port_counter_indexes += ingress_counters + port_counter_indexes += egress_counters + port_counter_indexes += [TRANSMITTED_PKTS, RECEIVED_PKTS, RECEIVED_NON_UC_PKTS, + TRANSMITTED_NON_UC_PKTS, EGRESS_PORT_QLEN] + + # construct packets + pkt = get_multiple_flows( + self, + self.dst_port_mac, + self.dst_port_id, + self.dst_port_ip, + None, + self.dscp, + self.ecn, + self.ttl, + self.packet_length, + [(self.src_port_id, self.src_port_ip)], + packets_per_port=1)[self.src_port_id][0][0] + pkt2 = get_multiple_flows( + self, + self.dst_port_mac, + self.dst_port_id, + self.dst_port_ip, + None, + self.dscp, + self.ecn, + self.ttl, + self.packet_length, + [(self.src_port_2_id, self.src_port_2_ip)], + packets_per_port=1)[self.src_port_2_id][0][0] + + # add slight tolerance in threshold characterization to consider + # the case that npu puts packets in the egress queue after we pause the egress + # or the leak out is simply less than expected as we have occasionally observed + if 'pkts_num_margin' in self.test_params.keys(): + margin = int(self.test_params['pkts_num_margin']) + else: + margin = 2 + + try: + # Test multi-flows + self.sai_thrift_port_tx_disable(self.dst_client, self.asic_type, [self.dst_port_id]) + recv_counters_base, _ = sai_thrift_read_port_counters(self.src_client, self.asic_type, + port_list['src'][self.src_port_id]) + recv_counters_2_base, _ = sai_thrift_read_port_counters(self.src_client, self.asic_type, + port_list['src'][self.src_port_2_id]) + xmit_counters_base, _ = sai_thrift_read_port_counters(self.dst_client, self.asic_type, + port_list['dst'][self.dst_port_id]) + fill_leakout_plus_one(self, self.src_port_id, self.dst_port_id, pkt, + int(self.test_params['pg']), self.asic_type) + multi_flow_drop_pkt_count = self.pkts_num_trig_egr_drp + # send packets short of triggering egress drop on both flows, uses the + # "multiple" packet count to cause a drop when 2 flows are present. + short_of_drop_npkts = self.pkts_num_leak_out + multi_flow_drop_pkt_count - 1 - margin + print("Sending {} packets on each of 2 streams to approach drop".format(short_of_drop_npkts), + file=sys.stderr) + send_packet(self, self.src_port_id, pkt, short_of_drop_npkts) + send_packet(self, self.src_port_2_id, pkt2, short_of_drop_npkts) + # allow enough time for counters to update + time.sleep(2) + recv_counters, _ = sai_thrift_read_port_counters(self.src_client, self.asic_type, + port_list['src'][self.src_port_id]) + recv_counters_2, _ = sai_thrift_read_port_counters(self.src_client, self.asic_type, + port_list['src'][self.src_port_2_id]) + xmit_counters, _ = sai_thrift_read_port_counters(self.dst_client, self.asic_type, + port_list['dst'][self.dst_port_id]) + + port_cnt_tbl = texttable.TextTable([''] + [port_counter_fields[idx] for idx in port_counter_indexes]) + port_cnt_tbl.add_row(['recv_counters_base'] + [recv_counters_base[idx] for idx in port_counter_indexes]) + port_cnt_tbl.add_row(['recv_counters'] + [recv_counters[idx] for idx in port_counter_indexes]) + port_cnt_tbl.add_row(['recv_counters_2_base'] + [recv_counters_2_base[idx] for idx in port_counter_indexes]) + port_cnt_tbl.add_row(['recv_counters_2'] + [recv_counters_2[idx] for idx in port_counter_indexes]) + port_cnt_tbl.add_row(['xmit_counters_base'] + [xmit_counters_base[idx] for idx in port_counter_indexes]) + port_cnt_tbl.add_row(['xmit_counters'] + [xmit_counters[idx] for idx in port_counter_indexes]) + sys.stderr.write('{}\n'.format(port_cnt_tbl)) + + # recv port no pfc + diff = recv_counters[self.pg] - recv_counters_base[self.pg] + assert diff == 0, "Unexpected PFC frames {} on port {}".format(diff, self.src_port_id) + diff = recv_counters_2[self.pg] - recv_counters_2_base[self.pg] + assert diff == 0, "Unexpected PFC frames {} on port {}".format(diff, self.src_port_2_id) + # recv port no ingress drop + for cntr in ingress_counters: + diff = recv_counters[cntr] - recv_counters_base[cntr] + assert diff == 0, "Unexpected ingress drop {} on port {}".format(diff, self.src_port_id) + for cntr in ingress_counters: + diff = recv_counters_2[cntr] - recv_counters_2_base[cntr] + assert diff == 0, "Unexpected ingress drop {} on port {}".format(diff, self.src_port_2_id) + # xmit port no egress drop + for cntr in egress_counters: + diff = xmit_counters[cntr] - xmit_counters_base[cntr] + assert diff == 0, "Unexpected TX drop {} on port {}".format(diff, self.dst_port_id) + + # send 1 packet to trigger egress drop + npkts = 1 + 2 * margin + print("Sending {} packets on 2 streams to trigger drop".format(npkts), + file=sys.stderr) + send_packet(self, self.src_port_id, pkt, npkts) + send_packet(self, self.src_port_2_id, pkt2, npkts) + # allow enough time for counters to update + time.sleep(2) + recv_counters, _ = sai_thrift_read_port_counters(self.src_client, + self.asic_type, + port_list['src'][self.src_port_id]) + recv_counters_2, _ = sai_thrift_read_port_counters(self.src_client, + self.asic_type, + port_list['src'][self.src_port_2_id]) + xmit_counters, _ = sai_thrift_read_port_counters(self.dst_client, + self.asic_type, + port_list['dst'][self.dst_port_id]) + # recv port no pfc + diff = recv_counters[self.pg] - recv_counters_base[self.pg] + assert diff == 0, "Unexpected PFC frames {} on port {}".format(diff, self.src_port_id) + diff = recv_counters_2[self.pg] - recv_counters_2_base[self.pg] + assert diff == 0, "Unexpected PFC frames {} on port {}".format(diff, self.src_port_2_id) + # recv port no ingress drop + for cntr in ingress_counters: + diff = recv_counters[cntr] - recv_counters_base[cntr] + assert diff == 0, "Unexpected ingress drop {} on port {}".format(diff, self.src_port_id) + for cntr in ingress_counters: + diff = recv_counters_2[cntr] - recv_counters_2_base[cntr] + assert diff == 0, "Unexpected ingress drop {} on port {}".format(diff, self.src_port_2_id) + # xmit port egress drop + for cntr in egress_counters: + drops = xmit_counters[cntr] - xmit_counters_base[cntr] + assert drops > 0, "Failed to detect egress drops ({})".format(drops) + print("Successfully dropped {} packets".format(drops), file=sys.stderr) + finally: + self.sai_thrift_port_tx_enable(self.dst_client, self.asic_type, [self.dst_port_id])