diff --git a/tests/common/dualtor/tunnel_traffic_utils.py b/tests/common/dualtor/tunnel_traffic_utils.py index 16c12b24cf3..4e82d7d4e50 100644 --- a/tests/common/dualtor/tunnel_traffic_utils.py +++ b/tests/common/dualtor/tunnel_traffic_utils.py @@ -4,53 +4,116 @@ import operator import pytest import re +import json from ptf import mask, testutils from scapy.all import IP, IPv6, Ether from tests.common.dualtor import dual_tor_utils from tests.common.utilities import dump_scapy_packet_show_output from tests.common.utilities import wait_until +from tests.common.dualtor.dual_tor_utils import is_tunnel_qos_remap_enabled -def derive_queue_id_from_dscp(dscp): - """ Derive queue id form DSCP using following mapping - DSCP -> Queue mapping - 8 0 - 5 2 - 3 3 - 4 4 - 46 5 - 48 6 - Rest 1 +def dut_dscp_tc_queue_maps(duthost): """ + A module level fixture to get QoS map from DUT host. + Return a dict + { + "dscp_to_tc_map": { + "AZURE": { + "0": "1", + ... + }, + ... + }, + "tc_to_queue_map": { + "AZURE": { + "0": "0", + ... + }, + ... + }, + "tc_to_dscp_map": { + "AZURE_TUNNEL": { + "0": "8", + ... + } + } + } + or an empty dict if failed to parse the output + """ + maps = {} + try: + # dscp_to_tc_map + maps['dscp_to_tc_map'] = json.loads(duthost.shell("sonic-cfggen -d --var-json 'DSCP_TO_TC_MAP'")['stdout']) + # tc_to_queue_map + maps['tc_to_queue_map'] = json.loads(duthost.shell("sonic-cfggen -d --var-json 'TC_TO_QUEUE_MAP'")['stdout']) + # tc_to_dscp_map + maps['tc_to_dscp_map'] = json.loads(duthost.shell("sonic-cfggen -d --var-json 'TC_TO_DSCP_MAP'")['stdout']) + except Exception as e: + logging.error("Failed to retrieve map on {}, exception {}".format(duthost.hostname, repr(e))) + return maps + +def derive_queue_id_from_dscp(duthost, dscp, is_tunnel): + """ + Helper function to find Queue ID for a DSCP ID. + """ + if is_tunnel_qos_remap_enabled(duthost) and is_tunnel: + dscp_to_tc_map_name = "AZURE" + tc_to_queue_map_name = "AZURE_TUNNEL" + logging.info("Enable pcbb") + else: + dscp_to_tc_map_name = "AZURE" + tc_to_queue_map_name = "AZURE" + try: + map = dut_dscp_tc_queue_maps(duthost) + # Load dscp_to_tc_map + tc_id = map['dscp_to_tc_map'][dscp_to_tc_map_name][str(dscp)] + # Load tc_to_queue_map + queue_id = map['tc_to_queue_map'][tc_to_queue_map_name][str(tc_id)] + except Exception as e: + logging.error("Failed to retrieve queue id for dscp {} on {}, exception {}".format(dscp, duthost.hostname, repr(e))) + return + return int(queue_id) + + +def derive_out_dscp_from_inner_dscp(duthost, inner_dscp): + """ + Helper function to find outer DSCP ID for a inner DSCP ID. + """ + if is_tunnel_qos_remap_enabled(duthost): + tc_to_dscp_map_name = "AZURE_TUNNEL" + map = dut_dscp_tc_queue_maps(duthost) + # Load tc_to_dscp_map + dscp_id = map['tc_to_dscp_map'][tc_to_dscp_map_name][str(inner_dscp)] + return int(dscp_id) + else: + return inner_dscp - dscp_to_queue = { 8 : 0, 5 : 2, 3 : 3, 4 : 4, 46 : 5, 48 : 6} - - return dscp_to_queue.get(dscp, 1) - -def queue_stats_check(dut, exp_queue): +def queue_stats_check(dut, exp_queue, packet_count): queue_counter = dut.shell('show queue counters | grep "UC"')['stdout'] logging.debug('queue_counter:\n{}'.format(queue_counter)) + # In case of other noise packets + DIFF = 0.1 """ regex search will look for following pattern in queue_counter outpute ----------------------------------------------------------------------------_--- Port TxQ Counter/pkts Counter/bytes Drop/pkts Drop/bytes ----------- ----- -------------- --------------- ----------- -------------- - Ethernet124 UC1 10 1000 0 0 + Ethernet124 UC1 100 12,400 0 0 """ - result = re.findall(r'\S+\s+UC%d\s+10+\s+\S+\s+\S+\s+\S+' % exp_queue, queue_counter) + result = re.findall(r'\S+\s+UC%d\s+(\d+)+\s+\S+\s+\S+\s+\S+' % exp_queue, queue_counter) if result: - for line in result: - rec_queue = int(line.split()[1][2]) - if rec_queue != exp_queue: - logging.debug("the expected Queue : {} not matching with received Queue : {}".format(exp_queue, rec_queue)) - else: - logging.info("the expected Queue : {} matching with received Queue : {}".format(exp_queue, rec_queue)) + for number in result: + if int(number) <= packet_count * (1 + DIFF) and int(number) >= packet_count: + logging.info("the expected Queue : {} received expected numbers of packet {}".format(exp_queue, number)) return True + logging.debug("the expected Queue : {} did not receive expected numbers of packet : {}".format(exp_queue, packet_count)) + return False else: - logging.debug("Could not find queue counter matches.") + logging.debug("Could not find expected queue counter matches.") return False @@ -121,8 +184,7 @@ def _check_ttl(packet): return "outer packet's TTL expected TTL 255, actual %s" % outer_ttl return "" - @staticmethod - def _check_tos(packet): + def _check_tos(self, packet): """Check ToS field in the packet.""" def _disassemble_ip_tos(tos): @@ -141,10 +203,11 @@ def _disassemble_ip_tos(tos): logging.info("Outer packet DSCP: {0:06b}, inner packet DSCP: {1:06b}".format(outer_dscp, inner_dscp)) logging.info("Outer packet ECN: {0:02b}, inner packet ECN: {0:02b}".format(outer_ecn, inner_ecn)) check_res = [] - if outer_dscp != inner_dscp: - check_res.append("outer packet DSCP not same as inner packet DSCP") + expected_outer_dscp = derive_out_dscp_from_inner_dscp(self.standby_tor, inner_dscp) + if outer_dscp != expected_outer_dscp: + check_res.append("outer packet DSCP {0:06b} not same as expected packet DSCP {0:06b}".format(outer_dscp, expected_outer_dscp)) if outer_ecn != inner_ecn: - check_res.append("outer packet ECN not same as inner packet ECN") + check_res.append("outer packet ECN {0:02b} not same as inner packet ECN {0:02b}".format(outer_ecn, inner_ecn)) return " ,".join(check_res) def _check_queue(self, packet): @@ -165,13 +228,13 @@ def _disassemble_ip_tos(tos): inner_dscp, _ = _disassemble_ip_tos(inner_tos) logging.info("Outer packet DSCP: {0:06b}, inner packet DSCP: {1:06b}".format(outer_dscp, inner_dscp)) check_res = [] - exp_queue = derive_queue_id_from_dscp(outer_dscp) + exp_queue = derive_queue_id_from_dscp(self.standby_tor, inner_dscp, True) logging.info("Expect queue: %s", exp_queue) - if not wait_until(60, 5, 0, queue_stats_check, self.standby_tor, exp_queue): + if not wait_until(60, 5, 0, queue_stats_check, self.standby_tor, exp_queue, self.packet_count): check_res.append("no expect counter in the expected queue %s" % exp_queue) return " ,".join(check_res) - def __init__(self, standby_tor, active_tor=None, existing=True, inner_packet=None, check_items=("ttl", "tos", "queue")): + def __init__(self, standby_tor, active_tor=None, existing=True, inner_packet=None, check_items=("ttl", "tos", "queue"), packet_count=10): """ Init the tunnel traffic monitor. @@ -182,6 +245,7 @@ def __init__(self, standby_tor, active_tor=None, existing=True, inner_packet=Non self.standby_tor = standby_tor self.listen_ports = sorted(self._get_t1_ptf_port_indexes(standby_tor, tbinfo)) self.ptfadapter = ptfadapter + self.packet_count = packet_count standby_tor_cfg_facts = self.standby_tor.config_facts( host=self.standby_tor.hostname, source="running" diff --git a/tests/dualtor/test_tor_ecn.py b/tests/dualtor/test_tor_ecn.py index 97bde4efcc4..55863748693 100644 --- a/tests/dualtor/test_tor_ecn.py +++ b/tests/dualtor/test_tor_ecn.py @@ -31,9 +31,12 @@ from tests.common.dualtor.tunnel_traffic_utils import derive_queue_id_from_dscp pytestmark = [ - pytest.mark.topology("t0") + pytest.mark.topology("dualtor") ] +# The packet number for test +PACKET_NUM = 100 + @contextlib.contextmanager def stop_garp(ptfhost): """Temporarily stop garp service.""" @@ -69,12 +72,12 @@ def setup_dualtor_tor_standby( else: request.getfixturevalue('toggle_all_simulator_ports_to_rand_selected_tor') -@pytest.fixture(scope="function") + def build_encapsulated_ip_packet( + inner_dscp, rand_selected_interface, ptfadapter, - rand_selected_dut, - tunnel_traffic_monitor + rand_selected_dut ): """ Build the encapsulated packet to be sent from T1 to ToR. @@ -92,9 +95,9 @@ def build_encapsulated_ip_packet( if is_ipv4_address(addr.split("/")[0])][0] tor_ipv4_address = tor_ipv4_address.split("/")[0] - inner_dscp = random.choice(range(0, 33)) inner_ttl = random.choice(range(3, 65)) inner_ecn = random.choice(range(0,3)) + logging.info("Inner DSCP: {0:06b}, Inner ECN: {1:02b}".format(inner_dscp, inner_ecn)) inner_packet = testutils.simple_ip_packet( ip_src="1.1.1.1", @@ -117,12 +120,12 @@ def build_encapsulated_ip_packet( return packet -@pytest.fixture(scope="function") + def build_non_encapsulated_ip_packet( + dscp, rand_selected_interface, ptfadapter, - rand_selected_dut, - tunnel_traffic_monitor + rand_selected_dut ): """ Build the regular (non encapsulated) packet to be sent from T1 to ToR. @@ -140,9 +143,9 @@ def build_non_encapsulated_ip_packet( if is_ipv4_address(addr.split("/")[0])][0] tor_ipv4_address = tor_ipv4_address.split("/")[0] - dscp = random.choice(range(0, 33)) ttl = random.choice(range(3, 65)) ecn = random.choice(range(0,3)) + logging.info("DSCP: {0:06b}, ECN: {1:02b}".format(dscp, ecn)) packet = testutils.simple_ip_packet( eth_dst=tor.facts["router_mac"], @@ -187,13 +190,14 @@ def build_expected_packet_to_server( return exp_pkt -def get_queue_id_of_received_packet( +def check_received_packet_on_expected_queue( duthosts, rand_one_dut_hostname, - rand_selected_interface + rand_selected_interface, + expected_queue ): """ - Get queue id of the packet received on destination + Check if received expected number of packets on expected queue """ duthost = duthosts[rand_one_dut_hostname] queue_counter = duthost.shell('show queue counters {} | grep "UC"'.format(rand_selected_interface[0]))['stdout'] @@ -204,19 +208,22 @@ def get_queue_id_of_received_packet( ----------------------------------------------------------------------------_--- Port TxQ Counter/pkts Counter/bytes Drop/pkts Drop/bytes ----------- ----- -------------- --------------- ----------- -------------- - Ethernet124 UC1 10 1000 0 0 + Ethernet124 UC1 100 1000 0 0 """ - result = re.search(r'\S+\s+UC\d\s+10+\s+\S+\s+\S+\s+\S+', queue_counter) - - if result is not None: - output = result.group(0) - output_list = output.split() - queue = int(output_list[1][2]) + # In case of other noise packets + DIFF = 0.1 + result = re.findall(r'\S+\s+UC%d\s+(\d+)+\s+\S+\s+\S+\s+\S+' % expected_queue, queue_counter) + + if result: + for number in result: + if int(number) <= PACKET_NUM * (1 + DIFF) and int(number) >= PACKET_NUM: + logging.info("the expected Queue : {} received expected numbers of packet {}".format(expected_queue, number)) + return True + logging.debug("the expected Queue : {} did not receive expected numbers of packet : {}".format(expected_queue, PACKET_NUM)) + return False else: - logging.info("Error occured while fetching queue counters from DUT") - return None - - return queue + logging.debug("Could not find expected queue counter matches.") + return False def verify_ecn_on_received_packet( ptfadapter, @@ -239,10 +246,10 @@ def verify_ecn_on_received_packet( else: logging.info("the expected ECN: {0:02b} matching with received ECN: {0:02b}".format(exp_ecn, rec_ecn)) +@pytest.mark.parametrize("inner_dscp", [3, 4, 2, 6]) #lossless queue is 3 or 4 or 2 or 6. def test_dscp_to_queue_during_decap_on_active( - ptfhost, setup_dualtor_tor_active, - build_encapsulated_ip_packet, request, - rand_selected_interface, ptfadapter, + inner_dscp, ptfhost, setup_dualtor_tor_active, + request, rand_selected_interface, ptfadapter, tbinfo, rand_selected_dut, tunnel_traffic_monitor, duthosts, rand_one_dut_hostname ): @@ -250,7 +257,7 @@ def test_dscp_to_queue_during_decap_on_active( Test if DSCP to Q mapping for inner header is matching with outer header during decap on active """ tor = rand_selected_dut - encapsulated_packet = build_encapsulated_ip_packet + encapsulated_packet = build_encapsulated_ip_packet(inner_dscp, rand_selected_interface, ptfadapter, rand_selected_dut) iface, _ = rand_selected_interface exp_ptf_port_index = get_ptf_server_intf_index(tor, tbinfo, iface) @@ -265,23 +272,23 @@ def test_dscp_to_queue_during_decap_on_active( ptfadapter.dataplane.flush() ptf_t1_intf = random.choice(get_t1_ptf_ports(tor, tbinfo)) logging.info("send encapsulated packet from ptf t1 interface %s", ptf_t1_intf) - testutils.send(ptfadapter, int(ptf_t1_intf.strip("eth")), encapsulated_packet, count=10) + testutils.send(ptfadapter, int(ptf_t1_intf.strip("eth")), encapsulated_packet, count=PACKET_NUM) exp_tos = encapsulated_packet[IP].payload[IP].tos exp_dscp = exp_tos >> 2 - exp_queue = derive_queue_id_from_dscp(exp_dscp) + exp_queue = derive_queue_id_from_dscp(duthost, exp_dscp, False) _, rec_pkt = testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=[exp_ptf_port_index], timeout=10) rec_pkt = Ether(rec_pkt) logging.info("received decap packet:\n%s", dump_scapy_packet_show_output(rec_pkt)) time.sleep(10) - rec_queue = get_queue_id_of_received_packet(duthosts, rand_one_dut_hostname, rand_selected_interface) + check_result = check_received_packet_on_expected_queue(duthosts, rand_one_dut_hostname, rand_selected_interface, exp_queue) - if rec_queue == None or rec_queue != exp_queue: - pytest.fail("the expected Queue : {} not matching with received Queue : {}".format(exp_queue, rec_queue)) + if not check_result: + pytest.fail("the expected Queue : {} did not receive expected numbers of packet : {}".format(exp_queue, PACKET_NUM)) else: - logging.info("the expected Queue : {} matching with received Queue : {}".format(exp_queue, rec_queue)) + logging.info("the expected Queue : {} received expected numbers of packet {}".format(exp_queue, PACKET_NUM)) @pytest.fixture(scope='module') def write_standby(rand_selected_dut): @@ -295,9 +302,10 @@ def runcmd(): except: pytest.skip('file {} not found'.format(file)) +@pytest.mark.parametrize("dscp", [3, 4, 2, 6]) #lossless queue is 3 or 4 or 2 or 6. def test_dscp_to_queue_during_encap_on_standby( + dscp, setup_dualtor_tor_standby, - build_non_encapsulated_ip_packet, rand_selected_interface, ptfadapter, tbinfo, rand_selected_dut, @@ -312,7 +320,7 @@ def test_dscp_to_queue_during_encap_on_standby( write_standby() tor = rand_selected_dut - non_encapsulated_packet = build_non_encapsulated_ip_packet + non_encapsulated_packet = build_non_encapsulated_ip_packet(dscp, rand_selected_interface, ptfadapter, rand_selected_dut) iface, _ = rand_selected_interface exp_ptf_port_index = get_ptf_server_intf_index(tor, tbinfo, iface) @@ -325,20 +333,20 @@ def test_dscp_to_queue_during_encap_on_standby( ptfadapter.dataplane.flush() ptf_t1_intf = random.choice(get_t1_ptf_ports(tor, tbinfo)) logging.info("send IP packet from ptf t1 interface %s", ptf_t1_intf) - with tunnel_traffic_monitor(tor, existing=True): - testutils.send(ptfadapter, int(ptf_t1_intf.strip("eth")), non_encapsulated_packet, count=10) + with tunnel_traffic_monitor(tor, existing=True, packet_count=PACKET_NUM): + testutils.send(ptfadapter, int(ptf_t1_intf.strip("eth")), non_encapsulated_packet, count=PACKET_NUM) +@pytest.mark.parametrize("inner_dscp", [3, 4, 2, 6]) #lossless queue is 3 or 4 or 2 or 6. def test_ecn_during_decap_on_active( - ptfhost, setup_dualtor_tor_active, - build_encapsulated_ip_packet, request, - rand_selected_interface, ptfadapter, + inner_dscp, ptfhost, setup_dualtor_tor_active, + request, rand_selected_interface, ptfadapter, tbinfo, rand_selected_dut, tunnel_traffic_monitor ): """ Test if the ECN stamping on inner header is matching with outer during decap on active """ tor = rand_selected_dut - encapsulated_packet = build_encapsulated_ip_packet + encapsulated_packet = build_encapsulated_ip_packet(inner_dscp, rand_selected_interface, ptfadapter, rand_selected_dut) iface, _ = rand_selected_interface exp_ptf_port_index = get_ptf_server_intf_index(tor, tbinfo, iface) @@ -353,13 +361,14 @@ def test_ecn_during_decap_on_active( tor.shell("portstat -c") tor.shell("show arp") ptfadapter.dataplane.flush() - testutils.send(ptfadapter, int(ptf_t1_intf.strip("eth")), encapsulated_packet, count=10) + testutils.send(ptfadapter, int(ptf_t1_intf.strip("eth")), encapsulated_packet, count=PACKET_NUM) tor.shell("portstat -j") verify_ecn_on_received_packet(ptfadapter, exp_pkt, exp_ptf_port_index, exp_ecn) +@pytest.mark.parametrize("dscp", [3, 4, 2, 6]) #lossless queue is 3 or 4 or 2 or 6. def test_ecn_during_encap_on_standby( + dscp, setup_dualtor_tor_standby, - build_non_encapsulated_ip_packet, rand_selected_interface, ptfadapter, tbinfo, rand_selected_dut, tunnel_traffic_monitor, write_standby @@ -370,12 +379,12 @@ def test_ecn_during_encap_on_standby( write_standby() tor = rand_selected_dut - non_encapsulated_packet = build_non_encapsulated_ip_packet + non_encapsulated_packet = build_non_encapsulated_ip_packet(dscp, rand_selected_interface, ptfadapter, rand_selected_dut) iface, _ = rand_selected_interface exp_ptf_port_index = get_ptf_server_intf_index(tor, tbinfo, iface) ptf_t1_intf = random.choice(get_t1_ptf_ports(tor, tbinfo)) logging.info("send IP packet from ptf t1 interface %s", ptf_t1_intf) - with tunnel_traffic_monitor(tor, existing=True): - testutils.send(ptfadapter, int(ptf_t1_intf.strip("eth")), non_encapsulated_packet, count=10) + with tunnel_traffic_monitor(tor, existing=True, packet_count=PACKET_NUM): + testutils.send(ptfadapter, int(ptf_t1_intf.strip("eth")), non_encapsulated_packet, count=PACKET_NUM)