diff --git a/ansible/roles/test/files/ptftests/inner_hash_test.py b/ansible/roles/test/files/ptftests/inner_hash_test.py index 8e5a75dce8b..6421135e9f8 100644 --- a/ansible/roles/test/files/ptftests/inner_hash_test.py +++ b/ansible/roles/test/files/ptftests/inner_hash_test.py @@ -77,6 +77,7 @@ def setUp(self): self.hash_keys = self.test_params.get('hash_keys', ['src-ip', 'dst-ip', 'src-port', 'dst-port']) self.src_ports = self.test_params['src_ports'] self.vxlan_port = self.test_params['vxlan_port'] + self.outer_encap_formats = self.test_params['outer_encap_formats'] self.symmetric_hashing = self.test_params.get('symmetric_hashing', False) self.outer_dst_ip = self.outer_dst_ip_interval.get_first_ip() @@ -89,13 +90,18 @@ def setUp(self): self.balancing_range = self.test_params.get('balancing_range', self.DEFAULT_BALANCING_RANGE) self.balancing_test_times = self.test_params.get('balancing_test_times', self.BALANCING_TEST_TIMES) + logging.info("balancing_range: {}".format(self.balancing_range)) + logging.info("balancing_test_times: {}".format(self.balancing_test_times)) + logging.info("outer_encap_formats: {}".format(self.outer_encap_formats)) + logging.info("hash_keys: {}".format(self.hash_keys)) + logging.info("symmetric_hashing: {}".format(self.symmetric_hashing)) + def check_hash(self, hash_key): src_port = int(random.choice(self.src_ports)) logging.info("outer_dst_ip={}, src_port={}, exp_port_list={}".format(self.outer_dst_ip, src_port, self.exp_port_list)) - outer_encap_formats = ['vxlan', 'nvgre'] - for outer_encap_format in outer_encap_formats: + for outer_encap_format in self.outer_encap_formats: hit_count_map = {} for _ in range(0, self.balancing_test_times*len(self.exp_port_list)): src_port = int(random.choice(self.src_ports)) @@ -154,16 +160,30 @@ def generate_inner_pkt(self, sport, dport, ip_src, ip_dst, ip_proto): rand_int = random.randint(1, 99) src_mac = '00:12:ab:34:cd:' + str(rand_int) dst_mac = str(rand_int) + ':12:ab:34:cd:00' - pkt = simple_tcp_packet( - eth_dst=dst_mac, - eth_src=src_mac, - ip_src=ip_src, - ip_dst=ip_dst, - tcp_sport=sport, - tcp_dport=dport, - ip_ttl=64) - - pkt['IP'].proto = ip_proto + if ip_network(unicode(ip_src)).version == 4: + pkt = simple_tcp_packet( + eth_dst=dst_mac, + eth_src=src_mac, + ip_dst=ip_dst, + ip_src=ip_src, + tcp_sport=sport, + tcp_dport=dport, + ip_ttl=64 + ) + + pkt["IP"].proto = ip_proto + else: + pkt = simple_tcpv6_packet( + eth_dst=dst_mac, + eth_src=src_mac, + ipv6_dst=ip_dst, + ipv6_src=ip_src, + tcp_sport=sport, + tcp_dport=dport, + ipv6_hlim=64 + ) + + pkt["IPv6"].nh = ip_proto return pkt diff --git a/tests/ecmp/__init__.py b/tests/ecmp/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/ecmp/inner_hashing/__init__.py b/tests/ecmp/inner_hashing/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/ecmp/inner_hashing/conftest.py b/tests/ecmp/inner_hashing/conftest.py new file mode 100644 index 00000000000..bbba04d1cb3 --- /dev/null +++ b/tests/ecmp/inner_hashing/conftest.py @@ -0,0 +1,356 @@ +import time +import json +import logging +import tempfile +import re + +from datetime import datetime + +import pytest + +from tests.common.fixtures.ptfhost_utils import copy_ptftests_directory, change_mac_addresses # lgtm[py/unused-import] + +logger = logging.getLogger(__name__) + + +# Standard HASH_KEYs of 'src-ip', 'dst-ip', 'src-port', 'dst-port', 'ip-proto' varied in the inner packets sent and used to validate hashing +# outer-tuples is also used as a HASH_KEY to validate that varying any outer tuples for encap traffic does not affect inner hashing +HASH_KEYS = ['src-ip', 'dst-ip', 'src-port', 'dst-port', 'ip-proto', 'outer-tuples'] +SRC_IP_RANGE = ['8.0.0.0', '8.255.255.255'] +DST_IP_RANGE = ['9.0.0.0', '9.255.255.255'] +SRC_IPV6_RANGE = ['20D0:A800:0:00::', '20D0:A800:0:00::FFFF:FFFF'] +DST_IPV6_RANGE = ['20D0:A800:0:01::', '20D0:A800:0:01::FFFF:FFFF'] +PTF_QLEN = 2000 + +PTF_TEST_PORT_MAP = '/root/ptf_test_port_map.json' +FIB_INFO_FILE_DST = '/root/fib_info.txt' + +VXLAN_PORT = 13330 +DUT_VXLAN_PORT_JSON_FILE = '/tmp/vxlan.switch.json' + +IP_VERSIONS_LIST = ["ipv4", "ipv6"] +OUTER_ENCAP_FORMATS = ["vxlan", "nvgre"] +TABLE_NAME = "pbh_table" +TABLE_DESCRIPTION = "NVGRE and VXLAN" +HASH_NAME = "inner_hash" +VXLAN_RULE_NAME = "vxlan_{}_{}" +NVGRE_RULE_NAME = "nvgre_{}_{}" +VXLAN_RULE_PRIO = "1" +NVGRE_RULE_PRIO = "2" +ECMP_PACKET_ACTION = "SET_ECMP_HASH" +V4_ETHER_TYPE = "0x0800" +V6_ETHER_TYPE = "0x86dd" +VXLAN_IP_PROTOCOL = "0x11" +NVGRE_IP_PROTOCOL = "0x2f" +VXLAN_L4_DST_PORT = "0x3412" +VXLAN_L4_DST_PORT_OPTION = " --l4-dst-port {}".format(VXLAN_L4_DST_PORT) +ADD_PBH_TABLE_CMD = "sudo config pbh table add '{}' --interface-list '{}' --description '{}'" +DEL_PBH_TABLE_CMD = "sudo config pbh table delete '{}'" +ADD_PBH_RULE_BASE_CMD = "sudo config pbh rule add '{}' '{}' --priority '{}' --ether-type {}" \ + " --inner-ether-type '{}' --hash '{}' --packet-action '{}' --flow-counter 'ENABLED'" +DEL_PBH_RULE_CMD = "sudo config pbh rule delete '{}' '{}'" +ADD_PBH_HASH_CMD = "sudo config pbh hash add '{}' --hash-field-list '{}'" +DEL_PBH_HASH_CMD = "sudo config pbh hash delete '{}'" +ADD_PBH_HASH_FIELD_CMD = "sudo config pbh hash-field add '{}' --hash-field '{}' --sequence-id '{}'" +DEL_PBH_HASH_FIELD_CMD = "sudo config pbh hash-field delete '{}'" + +PBH_HASH_FIELD_LIST = "inner_ip_proto," \ + "inner_l4_dst_port,inner_l4_src_port," \ + "inner_dst_ipv4,inner_src_ipv4," \ + "inner_src_ipv6,inner_dst_ipv6" +HASH_FIELD_CONFIG = { + "inner_ip_proto": {"field": "INNER_IP_PROTOCOL", "sequence": "1"}, + "inner_l4_dst_port": {"field": "INNER_L4_DST_PORT", "sequence": "2"}, + "inner_l4_src_port": {"field": "INNER_L4_SRC_PORT", "sequence": "2"}, + "inner_src_ipv4": {"field": "INNER_SRC_IPV4", "sequence": "3", "mask": "255.255.255.255"}, + "inner_dst_ipv4": {"field": "INNER_DST_IPV4", "sequence": "3", "mask": "255.255.255.255"}, + "inner_src_ipv6": {"field": "INNER_SRC_IPV6", "sequence": "4", "mask": "::ffff:ffff"}, + "inner_dst_ipv6": {"field": "INNER_DST_IPV6", "sequence": "4", "mask": "::ffff:ffff"} +} + + +def pytest_addoption(parser): + parser.addoption('--static_config', action='store_true', default=False, + help="Test configurations done before the test - static config") + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--static_config"): + # --static_config given in cli: skip test with dynamic config + skip_dynamic_config = pytest.mark.skip(reason="need to remove '--static_config'" + " option to run the dynamic config tests") + for item in items: + if "dynamic_config" in item.keywords: + item.add_marker(skip_dynamic_config) + else: + skip_static_config = pytest.mark.skip(reason="need '--static_config'" + " option to run the static config tests") + for item in items: + if "static_config" in item.keywords: + item.add_marker(skip_static_config) + + +@pytest.fixture(scope='module') +def config_facts(duthosts, rand_one_dut_hostname): + duthost = duthosts[rand_one_dut_hostname] + return duthost.config_facts(host=duthost.hostname, source='running')['ansible_facts'] + + +@pytest.fixture(scope='module', autouse=True) +def setup(duthosts, rand_one_dut_hostname): + duthost = duthosts[rand_one_dut_hostname] + + vxlan_switch_config = [{ + "SWITCH_TABLE:switch": { + "vxlan_port": VXLAN_PORT + }, + "OP": "SET" + }] + + logger.info("Copying vxlan.switch.json with data: " + str(vxlan_switch_config)) + + duthost.copy(content=json.dumps(vxlan_switch_config, indent=4), dest=DUT_VXLAN_PORT_JSON_FILE) + duthost.shell("docker cp {} swss:/vxlan.switch.json".format(DUT_VXLAN_PORT_JSON_FILE)) + duthost.shell("docker exec swss sh -c \"swssconfig /vxlan.switch.json\"") + time.sleep(3) + + +@pytest.fixture(scope='module', autouse=True) +def build_fib(duthosts, rand_one_dut_hostname, ptfhost, config_facts, tbinfo): + duthost = duthosts[rand_one_dut_hostname] + + timestamp = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') + + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + + duthost.shell("redis-dump -d 0 -k 'ROUTE*' -y > /tmp/fib.{}.txt".format(timestamp)) + duthost.fetch(src="/tmp/fib.{}.txt".format(timestamp), dest="/tmp/fib") + + po = config_facts.get('PORTCHANNEL', {}) + ports = config_facts.get('PORT', {}) + + tmp_fib_info = tempfile.NamedTemporaryFile() + with open("/tmp/fib/{}/tmp/fib.{}.txt".format(duthost.hostname, timestamp)) as fp: + fib = json.load(fp) + for k, v in fib.items(): + skip = False + prefix = k.split(':', 1)[1] + ifnames = v['value']['ifname'].split(',') + nh = v['value']['nexthop'] + + oports = [] + for ifname in ifnames: + if po.has_key(ifname): + oports.append([str(mg_facts['minigraph_ptf_indices'][x]) for x in po[ifname]['members']]) + else: + if ports.has_key(ifname): + oports.append([str(mg_facts['minigraph_ptf_indices'][ifname])]) + else: + logger.info("Route point to non front panel port {}:{}".format(k, v)) + skip = True + # skip direct attached subnet + if nh == '0.0.0.0' or nh == '::' or nh == "": + skip = True + + if not skip: + tmp_fib_info.write("{}".format(prefix)) + for op in oports: + tmp_fib_info.write(" [{}]".format(" ".join(op))) + tmp_fib_info.write("\n") + else: + tmp_fib_info.write("{} []\n".format(prefix)) + tmp_fib_info.flush() + + ptfhost.copy(src=tmp_fib_info.name, dest=FIB_INFO_FILE_DST) + msg = "Copied FIB info to PTF host '{}': local_path={}, remote_path={}" + logger.info(msg.format(ptfhost.hostname, tmp_fib_info.name, FIB_INFO_FILE_DST)) + + tmp_fib_info.close() + + +@pytest.fixture(scope='module') +def vlan_ptf_ports(config_facts, tbinfo, duthost): + ports = [] + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + for vlan_members in config_facts.get('VLAN_MEMBER', {}).values(): + for intf in vlan_members.keys(): + dut_port_index = mg_facts['minigraph_ptf_indices'][intf] + logging.info("Added " + str(dut_port_index)) + ports.append(dut_port_index) + + return ports + + +@pytest.fixture(scope='module') +def router_mac(duthosts, rand_one_dut_hostname): + duthost = duthosts[rand_one_dut_hostname] + return duthost.facts['router_mac'] + + +@pytest.fixture(scope="module") +def hash_keys(): + hash_keys = HASH_KEYS[:] + return hash_keys + + +@pytest.fixture(scope="module") +def symmetric_hashing(duthosts, rand_one_dut_hostname): + duthost = duthosts[rand_one_dut_hostname] + symmetric_hashing = False + + if duthost.facts['asic_type'] in ["mellanox"]: + symmetric_hashing = True + + return symmetric_hashing + + +@pytest.fixture(scope="module", params=IP_VERSIONS_LIST) +def outer_ipver(request): + return request.param + + +@pytest.fixture(scope="module", params=IP_VERSIONS_LIST) +def inner_ipver(request): + return request.param + + +@pytest.fixture(scope="module") +def config_pbh_table(duthost, vlan_ptf_ports, tbinfo): + test_intfs_str = get_dut_test_intfs_str(duthost, vlan_ptf_ports, tbinfo) + + duthost.command(ADD_PBH_TABLE_CMD.format(TABLE_NAME, + test_intfs_str, + TABLE_DESCRIPTION)) + + yield + + duthost.command(DEL_PBH_TABLE_CMD.format(TABLE_NAME)) + + +def get_dut_test_intfs_str(duthost, vlan_ptf_ports, tbinfo): + test_intfs = [] + # get ports according to chosen ptf ports indices + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + for intf, index in mg_facts['minigraph_ptf_indices'].items(): + if index in vlan_ptf_ports: + test_intfs.append(intf) + return ",".join(test_intfs) + + +@pytest.fixture(scope="module") +def config_hash_fields(duthost): + for hash_field, hash_field_params_dict in HASH_FIELD_CONFIG.items(): + cmd = get_hash_field_add_cmd(hash_field, hash_field_params_dict) + duthost.command(cmd) + + yield + + for hash_field in HASH_FIELD_CONFIG.keys(): + duthost.command(DEL_PBH_HASH_FIELD_CMD.format(hash_field)) + + +def get_hash_field_add_cmd(hash_field_name, hash_field_params_dict): + cmd = ADD_PBH_HASH_FIELD_CMD.format(hash_field_name, + hash_field_params_dict["field"], + hash_field_params_dict["sequence"]) + if "mask" in hash_field_params_dict: + cmd += " --ip-mask '{}'".format(hash_field_params_dict["mask"]) + return cmd + + +@pytest.fixture(scope="module") +def config_hash(duthost): + duthost.command(ADD_PBH_HASH_CMD.format(HASH_NAME, PBH_HASH_FIELD_LIST)) + + yield + + duthost.command(DEL_PBH_HASH_CMD.format(HASH_NAME)) + + +@pytest.fixture(scope="module") +def config_rules(duthost): + for inner_ipver in IP_VERSIONS_LIST: + config_ipv4_rules(duthost, inner_ipver) + config_ipv6_rules(duthost, inner_ipver) + + yield + + for inner_ipver in IP_VERSIONS_LIST: + delete_ipv4_rules(duthost, inner_ipver) + delete_ipv6_rules(duthost, inner_ipver) + + +def config_ipv4_rules(duthost, inner_ipver): + config_vxlan_rule(duthost, " --ip-protocol {}", V4_ETHER_TYPE, "ipv4", inner_ipver) + config_nvgre_rule(duthost, " --ip-protocol {}", V4_ETHER_TYPE, "ipv4", inner_ipver) + + +def config_ipv6_rules(duthost, inner_ipver): + config_vxlan_rule(duthost, " --ipv6-next-header {}", V6_ETHER_TYPE, "ipv6", inner_ipver) + config_nvgre_rule(duthost, " --ipv6-next-header {}", V6_ETHER_TYPE, "ipv6", inner_ipver) + + +def config_vxlan_rule(duthost, ip_ver_option, ether_type, outer_ipver, inner_ipver): + inner_ether_type = V4_ETHER_TYPE if inner_ipver == "ipv4" else V6_ETHER_TYPE + duthost.command((ADD_PBH_RULE_BASE_CMD + ip_ver_option + VXLAN_L4_DST_PORT_OPTION) + .format(TABLE_NAME, + VXLAN_RULE_NAME.format(outer_ipver, inner_ipver), + VXLAN_RULE_PRIO, + ether_type, + inner_ether_type, + HASH_NAME, + ECMP_PACKET_ACTION, + VXLAN_IP_PROTOCOL, + VXLAN_L4_DST_PORT)) + + +def config_nvgre_rule(duthost, ip_ver_option, ether_type, outer_ipver, inner_ipver): + inner_ether_type = V4_ETHER_TYPE if inner_ipver == "ipv4" else V6_ETHER_TYPE + duthost.command((ADD_PBH_RULE_BASE_CMD + ip_ver_option) + .format(TABLE_NAME, + NVGRE_RULE_NAME.format(outer_ipver, inner_ipver), + NVGRE_RULE_PRIO, + ether_type, + inner_ether_type, + HASH_NAME, + ECMP_PACKET_ACTION, + NVGRE_IP_PROTOCOL)) + + +def delete_ipv4_rules(duthost, inner_ipver): + delete_vxlan_nvgre_rules(duthost, "ipv4", inner_ipver) + + +def delete_ipv6_rules(duthost, inner_ipver): + delete_vxlan_nvgre_rules(duthost, "ipv6", inner_ipver) + + +def delete_vxlan_nvgre_rules(duthost, outer_ipver, inner_ipver): + duthost.command(DEL_PBH_RULE_CMD.format(TABLE_NAME, VXLAN_RULE_NAME.format(outer_ipver, inner_ipver))) + duthost.command(DEL_PBH_RULE_CMD.format(TABLE_NAME, NVGRE_RULE_NAME.format(outer_ipver, inner_ipver))) + + +def get_src_dst_ip_range(ipver): + if ipver == "ipv4": + src_ip_range = SRC_IP_RANGE + dst_ip_range = DST_IP_RANGE + else: + src_ip_range = SRC_IPV6_RANGE + dst_ip_range = DST_IPV6_RANGE + return src_ip_range, dst_ip_range + + +def check_pbh_counters(duthost, outer_ipver, inner_ipver, balancing_test_times, symmetric_hashing, hash_keys): + symmetric_multiplier = 2 if symmetric_hashing else 1 + exp_port_multiplier = 4 # num of POs in t0 topology + hash_keys_multiplier = len(hash_keys) + # for hash key "ip-proto", the traffic sends always in one way + exp_count = str((balancing_test_times * symmetric_multiplier * exp_port_multiplier * (hash_keys_multiplier-1)) + + (balancing_test_times * exp_port_multiplier)) + pbh_statistic_output = duthost.shell("show pbh statistic")['stdout'] + for outer_encap_format in OUTER_ENCAP_FORMATS: + regex = r'{}\s+{}_{}_{}\s+(\d+)\s+\d+'.format(TABLE_NAME, outer_encap_format, outer_ipver, inner_ipver) + current_count = re.search(regex, pbh_statistic_output).group(1) + assert current_count == exp_count,\ + "PBH counters are different from expected for {}, outer ipver {}, inner ipver {}. Expected: {}, " \ + "Current: {}".format(outer_encap_format, outer_ipver, inner_ipver, exp_count, current_count) diff --git a/tests/ecmp/inner_hashing/test_inner_hashing.py b/tests/ecmp/inner_hashing/test_inner_hashing.py new file mode 100644 index 00000000000..4458c1e64c3 --- /dev/null +++ b/tests/ecmp/inner_hashing/test_inner_hashing.py @@ -0,0 +1,102 @@ +# Summary: Inner packet hashing test +# How to run this test: sudo ./run_tests.sh -n -i -u -m group -e --skip_sanity -l info -c ecmp/test_inner_hashing.py --static_config +# parameter "--static_config" used when already exists hashing configurations and will be executed suitable test + +import logging +import pytest + +from datetime import datetime +from retry.api import retry_call +from tests.ptf_runner import ptf_runner +from tests.ecmp.inner_hashing.conftest import get_src_dst_ip_range, FIB_INFO_FILE_DST,\ + VXLAN_PORT, PTF_QLEN, check_pbh_counters, OUTER_ENCAP_FORMATS + +logger = logging.getLogger(__name__) + +pytestmark = [ + pytest.mark.topology('t0'), + pytest.mark.asic('mellanox') +] + + +@pytest.mark.dynamic_config +class TestDynamicInnerHashing(): + + @pytest.fixture(scope="class", autouse=True) + def setup_dynamic_pbh(self, request): + request.getfixturevalue("config_pbh_table") + request.getfixturevalue("config_hash_fields") + request.getfixturevalue("config_hash") + request.getfixturevalue("config_rules") + + def test_inner_hashing(self, hash_keys, ptfhost, outer_ipver, inner_ipver, router_mac, vlan_ptf_ports, symmetric_hashing, duthost): + logging.info("Executing dynamic inner hash test for outer {} and inner {} with symmetric_hashing set to {}" + .format(outer_ipver, inner_ipver, str(symmetric_hashing))) + timestamp = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') + log_file = "/tmp/inner_hash_test.DynamicInnerHashTest.{}.{}.{}.log".format(outer_ipver, inner_ipver, timestamp) + logging.info("PTF log file: %s" % log_file) + + outer_src_ip_range, outer_dst_ip_range = get_src_dst_ip_range(outer_ipver) + inner_src_ip_range, inner_dst_ip_range = get_src_dst_ip_range(inner_ipver) + + balancing_test_times = 150 + balancing_range = 0.3 + + ptf_runner(ptfhost, + "ptftests", + "inner_hash_test.InnerHashTest", + platform_dir="ptftests", + params={"fib_info": FIB_INFO_FILE_DST, + "router_mac": router_mac, + "src_ports": vlan_ptf_ports, + "hash_keys": hash_keys, + "vxlan_port": VXLAN_PORT, + "inner_src_ip_range": ",".join(inner_src_ip_range), + "inner_dst_ip_range": ",".join(inner_dst_ip_range), + "outer_src_ip_range": ",".join(outer_src_ip_range), + "outer_dst_ip_range": ",".join(outer_dst_ip_range), + "balancing_test_times": balancing_test_times, + "balancing_range": balancing_range, + "outer_encap_formats": OUTER_ENCAP_FORMATS, + "symmetric_hashing": symmetric_hashing}, + log_file=log_file, + qlen=PTF_QLEN, + socket_recv_size=16384) + + retry_call(check_pbh_counters, + fargs=[duthost, outer_ipver, inner_ipver, balancing_test_times, symmetric_hashing, hash_keys], + tries=5, + delay=5) + + +@pytest.mark.static_config +class TestStaticInnerHashing(): + + def test_inner_hashing(self, hash_keys, ptfhost, outer_ipver, inner_ipver, router_mac, vlan_ptf_ports, symmetric_hashing): + logging.info("Executing static inner hash test for outer {} and inner {} with symmetric_hashing set to {}" + .format(outer_ipver, inner_ipver, str(symmetric_hashing))) + timestamp = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') + log_file = "/tmp/inner_hash_test.StaticInnerHashTest.{}.{}.{}.log".format(outer_ipver, inner_ipver, timestamp) + logging.info("PTF log file: %s" % log_file) + + outer_src_ip_range, outer_dst_ip_range = get_src_dst_ip_range(outer_ipver) + inner_src_ip_range, inner_dst_ip_range = get_src_dst_ip_range(inner_ipver) + + ptf_runner(ptfhost, + "ptftests", + "inner_hash_test.InnerHashTest", + platform_dir="ptftests", + params={"fib_info": FIB_INFO_FILE_DST, + "router_mac": router_mac, + "src_ports": vlan_ptf_ports, + "hash_keys": hash_keys, + "vxlan_port": VXLAN_PORT, + "inner_src_ip_range": ",".join(inner_src_ip_range), + "inner_dst_ip_range": ",".join(inner_dst_ip_range), + "outer_src_ip_range": ",".join(outer_src_ip_range), + "outer_dst_ip_range": ",".join(outer_dst_ip_range), + "outer_encap_formats": OUTER_ENCAP_FORMATS, + "symmetric_hashing": symmetric_hashing}, + log_file=log_file, + qlen=PTF_QLEN, + socket_recv_size=16384) diff --git a/tests/ecmp/inner_hashing/test_wr_inner_hashing.py b/tests/ecmp/inner_hashing/test_wr_inner_hashing.py new file mode 100644 index 00000000000..090fbc50275 --- /dev/null +++ b/tests/ecmp/inner_hashing/test_wr_inner_hashing.py @@ -0,0 +1,110 @@ +import logging +import threading +import pytest +import random + +from datetime import datetime +from tests.common import reboot +from tests.ecmp.inner_hashing.conftest import get_src_dst_ip_range, FIB_INFO_FILE_DST, VXLAN_PORT,\ + PTF_QLEN, OUTER_ENCAP_FORMATS +from tests.ptf_runner import ptf_runner + +logger = logging.getLogger(__name__) + +pytestmark = [ + pytest.mark.topology('t0'), + pytest.mark.asic('mellanox') +] + +@pytest.mark.dynamic_config +class TestDynamicInnerHashing(): + + @pytest.fixture(scope="class", autouse=True) + def setup_dynamic_pbh(self, request): + request.getfixturevalue("config_pbh_table") + request.getfixturevalue("config_hash_fields") + request.getfixturevalue("config_hash") + request.getfixturevalue("config_rules") + + def test_inner_hashing(self, duthost, hash_keys, ptfhost, outer_ipver, inner_ipver, router_mac, + vlan_ptf_ports, symmetric_hashing, localhost): + logging.info("Executing warm boot dynamic inner hash test for outer {} and inner {} with symmetric_hashing" + " set to {}".format(outer_ipver, inner_ipver, str(symmetric_hashing))) + timestamp = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') + log_file = "/tmp/wr_inner_hash_test.DynamicInnerHashTest.{}.{}.{}.log".format(outer_ipver, inner_ipver, timestamp) + logging.info("PTF log file: %s" % log_file) + + # to reduce test run time, check one of encapsulation formats + outer_encap_format = random.choice(OUTER_ENCAP_FORMATS).split() + logging.info("Tested encapsulation format: {}".format(outer_encap_format[0])) + + outer_src_ip_range, outer_dst_ip_range = get_src_dst_ip_range(outer_ipver) + inner_src_ip_range, inner_dst_ip_range = get_src_dst_ip_range(inner_ipver) + + balancing_test_times = 200 + balancing_range = 0.3 + + duthost.command('sudo config save -y') + reboot_thr = threading.Thread(target=reboot, args=(duthost, localhost, 'warm',)) + reboot_thr.start() + + ptf_runner(ptfhost, + "ptftests", + "inner_hash_test.InnerHashTest", + platform_dir="ptftests", + params={"fib_info": FIB_INFO_FILE_DST, + "router_mac": router_mac, + "src_ports": vlan_ptf_ports, + "hash_keys": hash_keys, + "vxlan_port": VXLAN_PORT, + "inner_src_ip_range": ",".join(inner_src_ip_range), + "inner_dst_ip_range": ",".join(inner_dst_ip_range), + "outer_src_ip_range": ",".join(outer_src_ip_range), + "outer_dst_ip_range": ",".join(outer_dst_ip_range), + "balancing_test_times": balancing_test_times, + "balancing_range": balancing_range, + "outer_encap_formats": outer_encap_format, + "symmetric_hashing": symmetric_hashing}, + log_file=log_file, + qlen=PTF_QLEN, + socket_recv_size=16384) + reboot_thr.join() + + +@pytest.mark.static_config +class TestStaticInnerHashing(): + + def test_inner_hashing(self, duthost, hash_keys, ptfhost, outer_ipver, inner_ipver, router_mac, + vlan_ptf_ports, symmetric_hashing, localhost): + logging.info("Executing static inner hash test for outer {} and inner {} with symmetric_hashing set to {}" + .format(outer_ipver, inner_ipver, str(symmetric_hashing))) + timestamp = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') + log_file = "/tmp/wr_inner_hash_test.StaticInnerHashTest.{}.{}.{}.log".format(outer_ipver, inner_ipver, timestamp) + logging.info("PTF log file: %s" % log_file) + + outer_src_ip_range, outer_dst_ip_range = get_src_dst_ip_range(outer_ipver) + inner_src_ip_range, inner_dst_ip_range = get_src_dst_ip_range(inner_ipver) + + duthost.command('sudo config save -y') + reboot_thr = threading.Thread(target=reboot, args=(duthost, localhost, 'warm',)) + reboot_thr.start() + + ptf_runner(ptfhost, + "ptftests", + "inner_hash_test.InnerHashTest", + platform_dir="ptftests", + params={"fib_info": FIB_INFO_FILE_DST, + "router_mac": router_mac, + "src_ports": vlan_ptf_ports, + "hash_keys": hash_keys, + "vxlan_port": VXLAN_PORT, + "inner_src_ip_range": ",".join(inner_src_ip_range), + "inner_dst_ip_range": ",".join(inner_dst_ip_range), + "outer_src_ip_range": ",".join(outer_src_ip_range), + "outer_dst_ip_range": ",".join(outer_dst_ip_range), + "outer_encap_formats": OUTER_ENCAP_FORMATS, + "symmetric_hashing": symmetric_hashing}, + log_file=log_file, + qlen=PTF_QLEN, + socket_recv_size=16384) + reboot_thr.join() diff --git a/tests/ecmp/test_inner_hashing.py b/tests/ecmp/test_inner_hashing.py deleted file mode 100644 index edda9b590f1..00000000000 --- a/tests/ecmp/test_inner_hashing.py +++ /dev/null @@ -1,189 +0,0 @@ -# Summary: Inner packet hashing test -# How to run this test: sudo ./run_tests.sh -n -i -u -m group -e --skip_sanity -l info -c ecmp/test_inner_hashing.py - -import time -import json -import logging -import tempfile - -from datetime import datetime - -import pytest - -from tests.common.fixtures.ptfhost_utils import copy_ptftests_directory, change_mac_addresses # lgtm[py/unused-import] -from tests.ptf_runner import ptf_runner - -logger = logging.getLogger(__name__) - -pytestmark = [ - pytest.mark.topology('t0'), - pytest.mark.asic('mellanox') -] - -# Standard HASH_KEYs of 'src-ip', 'dst-ip', 'src-port', 'dst-port', 'ip-proto' varied in the inner packets sent and used to validate hashing -# outer-tuples is also used as a HASH_KEY to validate that varying any outer tuples for encap traffic does not affect inner hashing -HASH_KEYS = ['src-ip', 'dst-ip', 'src-port', 'dst-port', 'ip-proto', 'outer-tuples'] -SRC_IP_RANGE = ['8.0.0.0', '8.255.255.255'] -DST_IP_RANGE = ['9.0.0.0', '9.255.255.255'] -SRC_IPV6_RANGE = ['20D0:A800:0:00::', '20D0:A800:0:00::FFFF'] -DST_IPV6_RANGE = ['20D0:A800:0:01::', '20D0:A800:0:01::FFFF'] -PTF_QLEN = 2000 - -PTF_TEST_PORT_MAP = '/root/ptf_test_port_map.json' -FIB_INFO_FILE_DST = '/root/fib_info.txt' - -VXLAN_PORT = 13330 -DUT_VXLAN_PORT_JSON_FILE = '/tmp/vxlan.switch.json' - -@pytest.fixture(scope='module') -def config_facts(duthosts, rand_one_dut_hostname): - duthost = duthosts[rand_one_dut_hostname] - return duthost.config_facts(host=duthost.hostname, source='running')['ansible_facts'] - -@pytest.fixture(scope='module') -def setup(duthosts, rand_one_dut_hostname): - duthost = duthosts[rand_one_dut_hostname] - - vxlan_switch_config = [{ - "SWITCH_TABLE:switch": { - "vxlan_port": VXLAN_PORT - }, - "OP": "SET" - }] - - logger.info("Copying vxlan.switch.json with data: " + str(vxlan_switch_config)) - - duthost.copy(content=json.dumps(vxlan_switch_config, indent=4), dest=DUT_VXLAN_PORT_JSON_FILE) - duthost.shell("docker cp {} swss:/vxlan.switch.json".format(DUT_VXLAN_PORT_JSON_FILE)) - duthost.shell("docker exec swss sh -c \"swssconfig /vxlan.switch.json\"") - time.sleep(3) - - -@pytest.fixture(scope='module') -def build_fib(duthosts, rand_one_dut_hostname, ptfhost, config_facts, tbinfo): - duthost = duthosts[rand_one_dut_hostname] - - timestamp = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - - mg_facts = duthost.get_extended_minigraph_facts(tbinfo) - - duthost.shell("redis-dump -d 0 -k 'ROUTE*' -y > /tmp/fib.{}.txt".format(timestamp)) - duthost.fetch(src="/tmp/fib.{}.txt".format(timestamp), dest="/tmp/fib") - - po = config_facts.get('PORTCHANNEL', {}) - ports = config_facts.get('PORT', {}) - - tmp_fib_info = tempfile.NamedTemporaryFile() - with open("/tmp/fib/{}/tmp/fib.{}.txt".format(duthost.hostname, timestamp)) as fp: - fib = json.load(fp) - for k, v in fib.items(): - skip = False - prefix = k.split(':', 1)[1] - ifnames = v['value']['ifname'].split(',') - nh = v['value']['nexthop'] - - oports = [] - for ifname in ifnames: - if po.has_key(ifname): - oports.append([str(mg_facts['minigraph_ptf_indices'][x]) for x in po[ifname]['members']]) - else: - if ports.has_key(ifname): - oports.append([str(mg_facts['minigraph_ptf_indices'][ifname])]) - else: - logger.info("Route point to non front panel port {}:{}".format(k, v)) - skip = True - # skip direct attached subnet - if nh == '0.0.0.0' or nh == '::' or nh == "": - skip = True - - if not skip: - tmp_fib_info.write("{}".format(prefix)) - for op in oports: - tmp_fib_info.write(" [{}]".format(" ".join(op))) - tmp_fib_info.write("\n") - else: - tmp_fib_info.write("{} []\n".format(prefix)) - tmp_fib_info.flush() - - ptfhost.copy(src=tmp_fib_info.name, dest=FIB_INFO_FILE_DST) - msg = "Copied FIB info to PTF host '{}': local_path={}, remote_path={}" - logger.info(msg.format(ptfhost.hostname, tmp_fib_info.name, FIB_INFO_FILE_DST)) - - tmp_fib_info.close() - - -@pytest.fixture(scope='module') -def vlan_ptf_ports(config_facts, tbinfo): - ports = [] - for vlan_members in config_facts.get('VLAN_MEMBER', {}).values(): - for intf in vlan_members.keys(): - dut_port_index = config_facts.get('port_index_map', {})[intf] - logging.info("Added " + str(dut_port_index)) - ports.append(dut_port_index) - - return ports - - -@pytest.fixture(scope='module') -def router_mac(duthosts, rand_one_dut_hostname): - duthost = duthosts[rand_one_dut_hostname] - return duthost.facts['router_mac'] - - -@pytest.fixture(scope="module") -def hash_keys(): - hash_keys = HASH_KEYS[:] - return hash_keys - - -@pytest.fixture(scope="module") -def symmetric_hashing(duthosts, rand_one_dut_hostname): - duthost = duthosts[rand_one_dut_hostname] - symmetric_hashing = False - - if duthost.facts['asic_type'] in ["mellanox"]: - symmetric_hashing = True - - return symmetric_hashing - - -@pytest.fixture(params=["ipv4", "ipv6"]) -def ipver(request): - return request.param - -# The test case is expected to fail since some setup is missing. -# Please remove the xfail marker when the issue is fixed. -@pytest.mark.xfail -def test_inner_hashing(hash_keys, ptfhost, ipver, router_mac, vlan_ptf_ports, symmetric_hashing, build_fib, setup): - logging.info("Executing inner hash test for " + ipver + " with symmetric_hashing set to " + str(symmetric_hashing)) - timestamp = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') - log_file = "/tmp/inner_hash_test.InnerHashTest.{}.{}.log".format(ipver, timestamp) - logging.info("PTF log file: %s" % log_file) - - inner_src_ip_range = SRC_IP_RANGE - inner_dst_ip_range = DST_IP_RANGE - - if ipver == "ipv4": - outer_src_ip_range = SRC_IP_RANGE - outer_dst_ip_range = DST_IP_RANGE - else: - outer_src_ip_range = SRC_IPV6_RANGE - outer_dst_ip_range = DST_IPV6_RANGE - - ptf_runner(ptfhost, - "ptftests", - "inner_hash_test.InnerHashTest", - platform_dir="ptftests", - params={"fib_info": FIB_INFO_FILE_DST, - "router_mac": router_mac, - "src_ports": vlan_ptf_ports, - "hash_keys": hash_keys, - "vxlan_port": VXLAN_PORT, - "inner_src_ip_range": ",".join(inner_src_ip_range), - "inner_dst_ip_range": ",".join(inner_dst_ip_range), - "outer_src_ip_range": ",".join(outer_src_ip_range), - "outer_dst_ip_range": ",".join(outer_dst_ip_range), - "symmetric_hashing": symmetric_hashing}, - log_file=log_file, - qlen=PTF_QLEN, - socket_recv_size=16384)