From 599c5514948d7be5f2999be02a57ca35585ea2c6 Mon Sep 17 00:00:00 2001 From: Nana He Date: Fri, 24 Jun 2022 22:34:32 +0800 Subject: [PATCH] Add loopback action test cases Change-Id: I2f666a0ea91b3de80909362196452681e82680bc --- tests/common/devices/sonic.py | 50 +- tests/common/fixtures/duthost_utils.py | 51 +- .../common/plugins/allure_wrapper/__init__.py | 0 .../allure_wrapper/allure_step_wrapper.py | 8 + tests/iface_loopback_action/__init__.py | 0 tests/iface_loopback_action/conftest.py | 220 +++++++ .../iface_loopback_action_helper.py | 607 ++++++++++++++++++ .../test_iface_loopback_action.py | 116 ++++ 8 files changed, 1027 insertions(+), 25 deletions(-) create mode 100644 tests/common/plugins/allure_wrapper/__init__.py create mode 100644 tests/common/plugins/allure_wrapper/allure_step_wrapper.py create mode 100644 tests/iface_loopback_action/__init__.py create mode 100644 tests/iface_loopback_action/conftest.py create mode 100644 tests/iface_loopback_action/iface_loopback_action_helper.py create mode 100644 tests/iface_loopback_action/test_iface_loopback_action.py diff --git a/tests/common/devices/sonic.py b/tests/common/devices/sonic.py index 148cc9825c5..2ed9ea922f3 100644 --- a/tests/common/devices/sonic.py +++ b/tests/common/devices/sonic.py @@ -32,6 +32,7 @@ class SonicHost(AnsibleHostBase): This type of host contains information about the SONiC device (device info, services, etc.), and also provides the ability to run Ansible modules on the SONiC device. """ + DEFAULT_ASIC_SERVICES = ["bgp", "database", "lldp", "swss", "syncd", "teamd"] def __init__(self, ansible_adhoc, hostname, shell_user=None, shell_passwd=None, @@ -1062,8 +1063,13 @@ def get_ip_route_info(self, dstip, ns=""): @param dstip: destination. either ip_address or ip_network Please beware: if dstip is an ip network, you will receive all ECMP nexthops +<<<<<<< HEAD But if dstip is an ip address, only one nexthop will be returned, the one which is going to be used to send a packet to the destination. +======= + But if dstip is an ip address, only one nexthop will be returned, the one which is going to be used to + send a packet to the destination. +>>>>>>> 090bc7a72 (Add loopback action test cases) Exanples: ---------------- @@ -1076,9 +1082,14 @@ def get_ip_route_info(self, dstip, ns=""): ---------------- get_ip_route_info(ipaddress.ip_network(unicode("192.168.8.0/25"))) returns {'set_src': IPv4Address(u'10.1.0.32'), 'nexthops': [(IPv4Address(u'10.0.0.1'), u'PortChannel0001'), +<<<<<<< HEAD (IPv4Address(u'10.0.0.5'), u'PortChannel0002'), (IPv4Address(u'10.0.0.9'), u'PortChannel0003'), (IPv4Address(u'10.0.0.13'), u'PortChannel0004')]} +======= +(IPv4Address(u'10.0.0.5'), u'PortChannel0002'), (IPv4Address(u'10.0.0.9'), u'PortChannel0003'), +(IPv4Address(u'10.0.0.13'), u'PortChannel0004')]} +>>>>>>> 090bc7a72 (Add loopback action test cases) raw data 192.168.8.0/25 proto 186 src 10.1.0.32 metric 20 @@ -1102,9 +1113,14 @@ def get_ip_route_info(self, dstip, ns=""): ---------------- get_ip_route_info(ipaddress.ip_network(unicode("20c0:a818::/64"))) returns {'set_src': IPv6Address(u'fc00:1::32'), 'nexthops': [(IPv6Address(u'fc00::2'), u'PortChannel0001'), +<<<<<<< HEAD (IPv6Address(u'fc00::a'), u'PortChannel0002'), (IPv6Address(u'fc00::12'), u'PortChannel0003'), (IPv6Address(u'fc00::1a'), u'PortChannel0004')]} +======= +(IPv6Address(u'fc00::a'), u'PortChannel0002'), (IPv6Address(u'fc00::12'), u'PortChannel0003'), +(IPv6Address(u'fc00::1a'), u'PortChannel0004')]} +>>>>>>> 090bc7a72 (Add loopback action test cases) raw data 20c0:a818::/64 via fc00::2 dev PortChannel0001 proto 186 src fc00:1::32 metric 20 pref medium @@ -1121,9 +1137,14 @@ def get_ip_route_info(self, dstip, ns=""): ---------------- get_ip_route_info(ipaddress.ip_network(unicode("0.0.0.0/0"))) returns {'set_src': IPv4Address(u'10.1.0.32'), 'nexthops': [(IPv4Address(u'10.0.0.1'), u'PortChannel0001'), +<<<<<<< HEAD (IPv4Address(u'10.0.0.5'), u'PortChannel0002'), (IPv4Address(u'10.0.0.9'), u'PortChannel0003'), (IPv4Address(u'10.0.0.13'), u'PortChannel0004')]} +======= +(IPv4Address(u'10.0.0.5'), u'PortChannel0002'), (IPv4Address(u'10.0.0.9'), u'PortChannel0003'), +(IPv4Address(u'10.0.0.13'), u'PortChannel0004')]} +>>>>>>> 090bc7a72 (Add loopback action test cases) raw data default proto 186 src 10.1.0.32 metric 20 @@ -1140,10 +1161,18 @@ def get_ip_route_info(self, dstip, ns=""): nexthop via 10.0.0.63 dev PortChannel0004 weight 1 ---------------- get_ip_route_info(ipaddress.ip_network(unicode("::/0"))) +<<<<<<< HEAD returns {'set_src': IPv6Address(u'fc00:1::32'), 'nexthops': [(IPv6Address(u'fc00::2'), u'PortChannel0001'), (IPv6Address(u'fc00::a'), u'PortChannel0002'), (IPv6Address(u'fc00::12'), u'PortChannel0003'), (IPv6Address(u'fc00::1a'), u'PortChannel0004')]} +======= +returns {'set_src': IPv6Address(u'fc00:1::32'), +'nexthops': [(IPv6Address(u'fc00::2'), u'PortChannel0001'), +(IPv6Address(u'fc00::a'), u'PortChannel0002'), +(IPv6Address(u'fc00::12'), u'PortChannel0003'), + (IPv6Address(u'fc00::1a'), u'PortChannel0004')]} +>>>>>>> 090bc7a72 (Add loopback action test cases) raw data default via fc00::2 dev PortChannel0001 proto 186 src fc00:1::32 metric 20 pref medium @@ -1385,8 +1414,8 @@ def _parse_column_positions(self, sep_line, sep_char='-'): sep_char: The character used in separation line. Defaults to '-'. Returns: - Returns a list. Each item is a tuple with two elements. The first element is start position of a column. The - second element is the end position of the column. + Returns a list. Each item is a tuple with two elements. The first element is start position of a column. + The second element is the end position of the column. """ prev = ' ', positions = [] @@ -1401,7 +1430,7 @@ def _parse_column_positions(self, sep_line, sep_char='-'): prev = char return positions - def _parse_show(self, output_lines): + def _parse_show(self, output_lines, header_len=1): result = [] @@ -1410,7 +1439,7 @@ def _parse_show(self, output_lines): for idx, line in enumerate(output_lines): if sep_line_pattern.match(line): sep_line_found = True - header_line = output_lines[idx-1] + header_lines = output_lines[idx-header_len:idx] sep_line = output_lines[idx] content_lines = output_lines[idx+1:] break @@ -1427,7 +1456,8 @@ def _parse_show(self, output_lines): headers = [] for (left, right) in positions: - headers.append(header_line[left:right].strip().lower()) + header = " ".join([header_line[left:right].strip().lower() for header_line in header_lines]).strip() + headers.append(header) for content_line in content_lines: # When an empty line is encountered while parsing the tabulate content, it is highly possible that the @@ -1443,7 +1473,7 @@ def _parse_show(self, output_lines): return result - def show_and_parse(self, show_cmd, **kwargs): + def show_and_parse(self, show_cmd, header_len=1, **kwargs): """Run a show command and parse the output using a generic pattern. This method can adapt to the column changes as long as the output format follows the pattern of @@ -1481,7 +1511,11 @@ def show_and_parse(self, show_cmd, **kwargs): "lanes": "4,5,6,7", "fec": "N/A", "asym pfc": "off", - "admin": "up", "type": "QSFP+ or later", "vlan": "PortChannel0002", "mtu": "9100", "alias": "etp2", + "admin": "up", + "type": "QSFP+ or later", + "vlan": "PortChannel0002", + "mtu": "9100", + "alias": "etp2", "interface": "Ethernet4", "speed": "40G" }, @@ -1516,7 +1550,7 @@ def show_and_parse(self, show_cmd, **kwargs): output = output[start_line_index:] else: output = output[start_line_index:end_line_index] - return self._parse_show(output) + return self._parse_show(output, header_len) @cached(name='mg_facts') def get_extended_minigraph_facts(self, tbinfo, namespace=DEFAULT_NAMESPACE): diff --git a/tests/common/fixtures/duthost_utils.py b/tests/common/fixtures/duthost_utils.py index 89d48ca4f72..597a0c3e3bc 100644 --- a/tests/common/fixtures/duthost_utils.py +++ b/tests/common/fixtures/duthost_utils.py @@ -66,6 +66,14 @@ def backup_and_restore_config_db_module(duthosts, rand_one_dut_hostname): for func in _backup_and_restore_config_db(duthost, "module"): yield func + +@pytest.fixture(scope="package") +def backup_and_restore_config_db_package(duthosts): + + for func in _backup_and_restore_config_db(duthosts, "package"): + yield func + + @pytest.fixture(scope="session") def backup_and_restore_config_db_session(duthosts): @@ -151,21 +159,20 @@ def disable_fdb_aging(duthost): duthost.shell_cmds(cmds=cmds) duthost.file(path=TMP_SWITCH_CONFIG_FILE, state="absent") + @pytest.fixture(scope="module") def ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tbinfo): duthost = duthosts[rand_one_dut_hostname] cfg_facts = duthost.config_facts(host=duthost.hostname, source="persistent")['ansible_facts'] mg_facts = rand_selected_dut.get_extended_minigraph_facts(tbinfo) - config_ports = {k: v for k,v in cfg_facts['PORT'].items() if v.get('admin_status', 'down') == 'up'} + config_ports = {k: v for k, v in cfg_facts['PORT'].items() if v.get('admin_status', 'down') == 'up'} config_port_indices = {k: v for k, v in mg_facts['minigraph_ptf_indices'].items() if k in config_ports} ptf_ports_available_in_topo = {port_index: 'eth{}'.format(port_index) for port_index in config_port_indices.values()} config_portchannels = cfg_facts.get('PORTCHANNEL', {}) config_port_channel_members = [port_channel['members'] for port_channel in config_portchannels.values()] config_port_channel_member_ports = list(itertools.chain.from_iterable(config_port_channel_members)) - ports = [port for port in config_ports - if config_port_indices[port] in ptf_ports_available_in_topo - and config_ports[port].get('admin_status', 'down') == 'up' - and port not in config_port_channel_member_ports] + ports = [port for port in config_ports if config_port_indices[port] in ptf_ports_available_in_topo and + config_ports[port].get('admin_status', 'down') == 'up' and port not in config_port_channel_member_ports] return ports @@ -182,7 +189,7 @@ def check_orch_cpu_utilization(dut, orch_cpu_threshold): orch_cpu = dut.shell("COLUMNS=512 show processes cpu | grep orchagent | awk '{print $9}'")["stdout_lines"] for line in orch_cpu: if int(float(line)) > orch_cpu_threshold: - return False + return False time.sleep(1) return True @@ -191,12 +198,15 @@ def check_ebgp_routes(num_v4_routes, num_v6_routes, duthost): MAX_DIFF = 5 sumv4, sumv6 = duthost.get_ip_route_summary() rtn_val = True - if 'ebgp' in sumv4 and 'routes' in sumv4['ebgp'] and abs(int(float(sumv4['ebgp']['routes'])) - int(float(num_v4_routes))) >= MAX_DIFF: + if 'ebgp' in sumv4 and 'routes' in sumv4['ebgp'] and \ + abs(int(float(sumv4['ebgp']['routes'])) - int(float(num_v4_routes))) >= MAX_DIFF: rtn_val = False - if 'ebgp' in sumv6 and 'routes' in sumv6['ebgp'] and abs(int(float(sumv6['ebgp']['routes'])) - int(float(num_v6_routes))) >= MAX_DIFF: + if 'ebgp' in sumv6 and 'routes' in sumv6['ebgp'] and \ + abs(int(float(sumv6['ebgp']['routes'])) - int(float(num_v6_routes))) >= MAX_DIFF: rtn_val = False return rtn_val + @pytest.fixture(scope="module") def shutdown_ebgp(duthosts): # To store the original number of eBGP v4 and v6 routes. @@ -229,11 +239,14 @@ def shutdown_ebgp(duthosts): orig_v4_ebgp = v4ebgps[duthost.hostname] orig_v6_ebgp = v6ebgps[duthost.hostname] pytest_assert(wait_until(120, 10, 10, check_ebgp_routes, orig_v4_ebgp, orig_v6_ebgp, duthost), - "eBGP v4 routes are {}, and v6 route are {}, and not what they were originally after enabling all neighbors on {}".format(orig_v4_ebgp, orig_v6_ebgp, duthost)) + "eBGP v4 routes are {}, and v6 route are {}, and not what they were originally after enabling " + "all neighbors on {}".format(orig_v4_ebgp, orig_v6_ebgp, duthost)) pytest_assert(wait_until(60, 2, 0, check_orch_cpu_utilization, duthost, orch_cpu_threshold), "Orch CPU utilization {} > orch cpu threshold {} after startup all eBGP" .format(duthost.shell("show processes cpu | grep orchagent | awk '{print $9}'")["stdout"], orch_cpu_threshold)) + + @pytest.fixture(scope="module") def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tbinfo, ports_list): """ @@ -243,7 +256,7 @@ def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tb cfg_facts = duthost.config_facts(host=duthost.hostname, source="persistent")['ansible_facts'] mg_facts = rand_selected_dut.get_extended_minigraph_facts(tbinfo) vlan_ports_list = [] - config_ports = {k: v for k,v in cfg_facts['PORT'].items() if v.get('admin_status', 'down') == 'up'} + config_ports = {k: v for k, v in cfg_facts['PORT'].items() if v.get('admin_status', 'down') == 'up'} config_portchannels = cfg_facts.get('PORTCHANNEL', {}) config_port_indices = {k: v for k, v in mg_facts['minigraph_ptf_indices'].items() if k in config_ports} config_ports_vlan = collections.defaultdict(list) @@ -264,14 +277,14 @@ def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tb if 'tagging_mode' not in vlan_members[k][port]: continue mode = vlan_members[k][port]['tagging_mode'] - config_ports_vlan[port].append({'vlanid':int(vlanid), 'ip':ip, 'tagging_mode':mode}) + config_ports_vlan[port].append({'vlanid':int(vlanid), 'ip': ip, 'tagging_mode': mode}) if config_portchannels: for po in config_portchannels: vlan_port = { - 'dev' : po, - 'port_index' : [config_port_indices[member] for member in config_portchannels[po]['members']], - 'permit_vlanid' : [] + 'dev': po, + 'port_index': [config_port_indices[member] for member in config_portchannels[po]['members']], + 'permit_vlanid': [] } if po in config_ports_vlan: vlan_port['pvid'] = 0 @@ -286,9 +299,9 @@ def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tb for i, port in enumerate(ports_list): vlan_port = { - 'dev' : port, - 'port_index' : [config_port_indices[port]], - 'permit_vlanid' : [] + 'dev': port, + 'port_index': [config_port_indices[port]], + 'permit_vlanid': [] } if port in config_ports_vlan: vlan_port['pvid'] = 0 @@ -303,11 +316,13 @@ def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tb return vlan_ports_list + def compare_network(src_ipprefix, dst_ipprefix): src_network = ipaddress.IPv4Interface(src_ipprefix).network dst_network = ipaddress.IPv4Interface(dst_ipprefix).network return src_network.overlaps(dst_network) + @pytest.fixture(scope="module") def utils_vlan_intfs_dict_orig(duthosts, rand_one_dut_hostname, tbinfo): '''A module level fixture to record duthost's original vlan info @@ -343,6 +358,7 @@ def utils_vlan_intfs_dict_orig(duthosts, rand_one_dut_hostname, tbinfo): vlan_intfs_dict[int(vlanid)] = {'ip': ip, 'orig': True} return vlan_intfs_dict + def utils_vlan_intfs_dict_add(vlan_intfs_dict, add_cnt): '''Utilities function to add add_cnt of new VLAN @@ -377,6 +393,7 @@ def utils_vlan_intfs_dict_add(vlan_intfs_dict, add_cnt): assert vlan_cnt == add_cnt return vlan_intfs_dict + def utils_create_test_vlans(duthost, cfg_facts, vlan_ports_list, vlan_intfs_dict, delete_untagged_vlan): '''Utilities function to create vlans for test diff --git a/tests/common/plugins/allure_wrapper/__init__.py b/tests/common/plugins/allure_wrapper/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/common/plugins/allure_wrapper/allure_step_wrapper.py b/tests/common/plugins/allure_wrapper/allure_step_wrapper.py new file mode 100644 index 00000000000..01fc256ecd2 --- /dev/null +++ b/tests/common/plugins/allure_wrapper/allure_step_wrapper.py @@ -0,0 +1,8 @@ +import logging +from allure_commons._allure import step as raw_allure_step +logger = logging.getLogger(__name__) + + +def step(title): + logger.info("Allure step: {}".format(title)) + return raw_allure_step(title) diff --git a/tests/iface_loopback_action/__init__.py b/tests/iface_loopback_action/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/iface_loopback_action/conftest.py b/tests/iface_loopback_action/conftest.py new file mode 100644 index 00000000000..9a93d1d2ca1 --- /dev/null +++ b/tests/iface_loopback_action/conftest.py @@ -0,0 +1,220 @@ +import pytest +from tests.common.plugins.ptfadapter import get_ifaces, get_ifaces_map +from tests.common import constants +from .iface_loopback_action_helper import get_tested_up_ports, remove_orig_dut_port_config, \ + get_portchannel_peer_port_map, recover_config, apply_config +from .iface_loopback_action_helper import ETHERNET_RIF, VLAN_RIF, PO_RIF, SUB_PORT_RIF, PO_SUB_PORT_RIF +from tests.common.fixtures.duthost_utils import \ + backup_and_restore_config_db_package # lgtm[py/unused-import] # noqa: F401 + +PORT_COUNT = 10 + + +def pytest_addoption(parser): + """ + Adds options to pytest that are used by the rif loopback action tests. + """ + + parser.addoption( + "--rif_loppback_reboot_type", + action="store", + type=str, + default="cold", + help="reboot type such as reload, cold, fast, warm, random" + ) + + +@pytest.fixture(scope="package") +def orig_ports_configuration(request, duthost, ptfhost, tbinfo): + """ + Get the ports used to do test, return the dict of the port's original vlan, portchannel, infos. + :param duthost: DUT host object + :param ptfhost: PTF host object + :return: Dictionary of original port configuration + For example: + { + 1: { + 'port': 'Ethernet0', + 'vlan': None, + 'portchannel': None, + 'ip_addr': '10.0.0.5', + 'ptf_port': 'eth0' + }, + 2: { + 'port': 'Ethernet4', + 'vlan': 'Vlan1000', + 'portchannel': None, + 'ip_addr': None, + 'ptf_port': 'eth1' + }, + 3: { + 'port': 'Ethernet8', + 'vlan': None, + 'portchannel': 'PortChannel102', + 'ip_addr': None, + 'ptf_port': 'eth2' + } + } + """ + if 'backend' in tbinfo['topo']['name']: + ptf_port_mapping_mode = getattr(request.module, "PTF_PORT_MAPPING_MODE", + constants.PTF_PORT_MAPPING_MODE_DEFAULT) + else: + ptf_port_mapping_mode = 'use_orig_interface' + res = ptfhost.command('cat /proc/net/dev') + ptf_ifaces = get_ifaces(res['stdout']) + ptf_ifaces_map = get_ifaces_map(ptf_ifaces, ptf_port_mapping_mode) + port_dict = get_tested_up_ports(duthost, ptf_ifaces_map, count=PORT_COUNT) + yield port_dict + + +@pytest.fixture(scope="package") +def ports_configuration(orig_ports_configuration): + """ + Define the ports parameters + :param orig_ports_configuration: original config of the ports. + :return: Dictionary of port parameters for configuration DUT and PTF host + For example: + { + 'Ethernet4': { + 'type': 'ethernet', + 'port_index': '2', + 'port': 'Ethernet4', + 'ptf_port': 'eth1', + 'ip_addr': '11.0.0.1', + 'ptf_ip_addr': '11.0.0.10' + }, + 'Vlan11': { + 'type': 'vlan', + 'vlan_id': '11', + 'port_index': '3', + 'port': 'Ethernet8', + 'ptf_port': 'eth2', + 'ip_addr': '11.1.0.1', + 'ptf_ip_addr': '11.1.0.10' + }, + 'PortChannel222': { + 'type': 'po', + 'po_id': '222', + 'port_index': '4', + 'port': 'Ethernet12', + 'ptf_port': 'eth3', + 'ip_addr': '11.2.0.1', + 'ptf_ip_addr': '11.2.0.10' + }, + 'Ethernet8.33': { + 'type': 'sub_port', + 'vlan_id': '33', + 'port_index': '5', + 'port': 'Ethernet16', + 'ptf_port': 'eth4', + 'ip_addr': '11.3.0.1', + 'ptf_ip_addr': '11.3.0.10' + }, + 'Po444.44': { + 'type': 'po_sub_port', + 'po_id': '444', + 'vlan_id': '44', + 'port_index': '6', + 'port': 'Ethernet20', + 'ptf_port': 'eth5', + 'ip_addr': '11.4.0.1', + 'ptf_ip_addr': '11.4.0.10' + }, + } + """ + groups_of_ports = 5 + dut_ip_list, ptf_ip_list = generate_ip_list() + ports_configuration = {} + index = 0 + for port_index, port_dict in orig_ports_configuration.items(): + if index % groups_of_ports == 0: + rif_port_name = port_dict['port'] + ports_configuration[rif_port_name] = {} + ports_configuration[rif_port_name]['type'] = ETHERNET_RIF + + elif index % groups_of_ports == 1: + vlan_id = 50 + index + rif_port_name = "Vlan{}".format(vlan_id) + ports_configuration[rif_port_name] = {} + ports_configuration[rif_port_name]['type'] = VLAN_RIF + ports_configuration[rif_port_name]['vlan_id'] = vlan_id + + elif index % groups_of_ports == 2: + po_id = 50 + index + rif_port_name = "PortChannel{}".format(po_id) + ports_configuration[rif_port_name] = {} + ports_configuration[rif_port_name]['type'] = PO_RIF + ports_configuration[rif_port_name]['po_id'] = po_id + + elif index % groups_of_ports == 3: + vlan_id = 50 + index + rif_port_name = "{}.{}".format(port_dict['port'], vlan_id) + ports_configuration[rif_port_name] = {} + ports_configuration[rif_port_name]['type'] = SUB_PORT_RIF + ports_configuration[rif_port_name]['vlan_id'] = vlan_id + + elif index % groups_of_ports == 4: + po_id = 50 + index + vlan_id = 50 + index + rif_port_name = "Po{}.{}".format(po_id, vlan_id) + ports_configuration[rif_port_name] = {} + ports_configuration[rif_port_name]['type'] = PO_SUB_PORT_RIF + ports_configuration[rif_port_name]['po_id'] = po_id + ports_configuration[rif_port_name]['vlan_id'] = vlan_id + + ports_configuration[rif_port_name]['port'] = port_dict['port'] + ports_configuration[rif_port_name]['ptf_port'] = port_dict['ptf_port'] + ports_configuration[rif_port_name]['port_index'] = port_index + ports_configuration[rif_port_name]['ip_addr'] = dut_ip_list[index] + ports_configuration[rif_port_name]['ptf_ip_addr'] = ptf_ip_list[index] + index += 1 + yield ports_configuration + + +def generate_ip_list(): + dut_ip_list = [] + ptf_ip_list = [] + for i in range(PORT_COUNT): + dut_ip_list.append('11.{}.0.1/24'.format(i)) + ptf_ip_list.append('11.{}.0.10/24'.format(i)) + return dut_ip_list, ptf_ip_list + + +@pytest.fixture(scope="package", autouse=True) +def setup(duthost, ptfhost, orig_ports_configuration, ports_configuration, + backup_and_restore_config_db_package, nbrhosts, tbinfo): # noqa: F811 + """ + Config: Cleanup the original port configuration and add new configurations before test + Cleanup: restore the config on the VMs + :param duthost: DUT host object + :param ptfhost: PTF host object + :param orig_ports_configuration: original ports configuration parameters + :param ports_configuration: ports configuration parameters + :param backup_and_restore_config_db_package: backup and restore config db package fixture. + :param nbrhosts: nbrhosts fixture. + :param tbinfo: Testbed object + """ + peer_shutdown_ports = get_portchannel_peer_port_map(duthost, orig_ports_configuration, tbinfo, nbrhosts) + remove_orig_dut_port_config(duthost, orig_ports_configuration) + for vm_host, peer_ports in peer_shutdown_ports.items(): + for peer_port in peer_ports: + vm_host.shutdown(peer_port) + apply_config(duthost, ptfhost, ports_configuration) + + yield + for vm_host, peer_ports in peer_shutdown_ports.items(): + for peer_port in peer_ports: + vm_host.no_shutdown(peer_port) + + +@pytest.fixture(scope="package", autouse=True) +def recover(duthost, ptfhost, ports_configuration): + """ + restore the original configurations + :param duthost: DUT host object + :param ptfhost: PTF host object + :param ports_configuration: ports configuration parameters + """ + yield + recover_config(duthost, ptfhost, ports_configuration) diff --git a/tests/iface_loopback_action/iface_loopback_action_helper.py b/tests/iface_loopback_action/iface_loopback_action_helper.py new file mode 100644 index 00000000000..d7a10866d7a --- /dev/null +++ b/tests/iface_loopback_action/iface_loopback_action_helper.py @@ -0,0 +1,607 @@ +import ptf.testutils as testutils +import ptf.mask as mask +import ptf.packet as packet +import time +import re +import logging +from tests.common.helpers.assertions import pytest_assert +from tests.common.utilities import wait_until +from tests.common.config_reload import config_reload + +ETHERNET_RIF = 'ethernet' +VLAN_RIF = 'vlan' +PO_RIF = 'po' +SUB_PORT_RIF = "sub_port" +PO_SUB_PORT_RIF = "po_sub_port" +CONIFIG_LOOPBACK_ACTION_REG = "config interface ip loopback-action {} {}" +ACTION_FORWARD = "forward" +ACTION_DROP = "drop" +NUM_OF_TOTAL_PACKETS = 10 +logger = logging.getLogger(__name__) + + +def generate_and_verify_traffic(duthost, ptfadapter, rif_interface, src_port_index, ip_src='', ip_dst='', + pkt_action=None): + """ + Send packet from PTF to DUT and and verify packet on PTF host + :param duthost: DUT host object + :param ptfadapter: PTF adapter + :param rif_interface: rif interface on dut + :param src_port_index: Source port index from which pkt will be sent + :param ip_src: Source IP address of the pkt + :param ip_dst: Destination IP address of the pkt + :param pkt_action: Packet action (forward or drop) + :return: None + """ + + vlan_vid = None + dl_vlan_enable = False + # Get VLAN ID from name of rif interface + if '.' in rif_interface: + # this should be the sub-port interface + vlan_vid = int(rif_interface.split('.')[1]) + dl_vlan_enable = True + elif rif_interface.startswith("Vlan"): + vlan_vid = int(rif_interface[4:]) + dl_vlan_enable = True + + ip_dst = ip_dst.split('/')[0] + eth_dst = duthost.facts["router_mac"] + eth_src = ptfadapter.dataplane.get_mac(0, src_port_index) + duthost.shell("sudo ip neigh replace {} lladdr {} dev {}".format(ip_dst, eth_src, rif_interface)) + logger.info("Traffic info is: eth_dst- {}, eth_src- {}, ip_src- {}, ip_dst- {}, vlan_vid- {}".format( + eth_dst, eth_src, ip_src, ip_dst, vlan_vid)) + pkt = testutils.simple_ip_packet( + eth_dst=eth_dst, + eth_src=eth_src, + ip_src=ip_src, + ip_dst=ip_dst, + vlan_vid=vlan_vid, + dl_vlan_enable=dl_vlan_enable, + ip_ttl=121 + ) + exp_pkt = pkt.copy() + exp_pkt.payload.ttl = 120 + exp_pkt = mask.Mask(exp_pkt) + exp_pkt.set_do_not_care_scapy(packet.IP, "chksum") + exp_pkt.set_do_not_care_scapy(packet.Ether, 'dst') + exp_pkt.set_do_not_care_scapy(packet.Ether, 'src') + + ptfadapter.dataplane.flush() + time.sleep(1) + logger.info("Verify the traffic for {}".format(rif_interface)) + testutils.send_packet(ptfadapter, src_port_index, pkt, count=NUM_OF_TOTAL_PACKETS) + if pkt_action == ACTION_DROP: + testutils.verify_no_packet(ptfadapter, exp_pkt, src_port_index) + else: + testutils.verify_packet(ptfadapter, exp_pkt, src_port_index) + + +def get_tested_up_ports(duthost, ptf_ifaces_map, count=10): + """ + Get the specified number of up ports + :param duthost: DUT host object + :param ptfhost: PTF host object + :param count: The number of ports + :return: The dictionary of the up ports. + Examples: + { + '1': { + 'vlan': 100, + 'ip_addresses': {} + 'portchannel': None + 'ptf_port': 'eth0' + }, + ... + } + """ + config_facts = duthost.config_facts(host=duthost.hostname, source="running")['ansible_facts'] + up_ports = get_all_up_ports(config_facts) + logger.info("Ports infos: {}".format(config_facts['PORT'])) + port_index_map = config_facts['port_index_map'] + port_configuration = {} + + index = 0 + for port in up_ports: + if index >= count: + break + port_dict = {'port': port, 'vlan': None, 'portchannel': None, 'ip_addr': None, 'ptf_port': None} + port_index = port_index_map[port] + + ip_addresses = config_facts.get('INTERFACE', {}).get(port, {}) + port_dict['ip_addr'] = ip_addresses + port_dict['portchannel'] = get_portchannel_of_port(config_facts, port) + port_dict['vlan'] = get_vlan_of_port(config_facts, port) + + ptf_port = ptf_ifaces_map[port_index] + port_dict['ptf_port'] = ptf_port + + port_configuration[port_index] = port_dict + index += 1 + return port_configuration + + +def get_all_up_ports(config_facts): + """ + Get all ports which are up + :param config_facts: DUT running config facts + :return: List of ports which is up + """ + split_port_alias_pattern = r"etp\d+[a-z]" + split_up_ports = [p for p, v in config_facts['PORT'].items() if v.get('admin_status', None) == 'up' and + not re.match(split_port_alias_pattern, v['alias'])] + non_split_up_ports = [p for p, v in config_facts['PORT'].items() if v.get('admin_status', None) == 'up' and + re.match(split_port_alias_pattern, v['alias'])] + return split_up_ports + non_split_up_ports + + +def get_portchannel_of_port(config_facts, port): + """ + Check if the port is a member of port channel, if it is then return the portchannel, else return Noe + :param config_facts: DUT running config facts + :param port: the port which need to check + :return: portchannel or None + """ + portchannels = config_facts['PORTCHANNEL'].keys() if 'PORTCHANNEL' in config_facts else [] + for portchannel in portchannels: + portchannel_members = config_facts['PORTCHANNEL'][portchannel].get('members') + if port in portchannel_members: + return portchannel + + +def get_vlan_of_port(config_facts, port): + """ + Check if the port is a member of vlan, if it is then return the vlan, else return None + :param config_facts: DUT running config facts + :param port: the port which need to check + :return: vlan or None + """ + vlan_dict = config_facts['VLAN'].items() if 'VLAN' in config_facts else {} + for vlan_name, vlan in vlan_dict: + if port in config_facts['VLAN_MEMBER'][vlan_name].keys(): + return vlan['vlanid'] + return None + + +def remove_orig_dut_port_config(duthost, orig_ports_configuration): + """ + Remove the original port configurations for DUT + :param duthost: DUT host object + :param orig_ports_configuration: original ports configuration parameters + """ + for _, port_dict in orig_ports_configuration.items(): + port = port_dict['port'] + if port_dict['vlan']: + remove_dut_vlan_member(duthost, port, port_dict['vlan']) + elif port_dict['portchannel']: + remove_dut_portchannel_member(duthost, port, port_dict['portchannel']) + elif port_dict['ip_addr']: + for ip in port_dict['ip_addr']: + remove_dut_ip_from_port(duthost, port, ip) + remove_acl_tables(duthost) + + +def get_portchannel_peer_port_map(duthost, orig_ports_configuration, tbinfo, nbrhosts): + """ + Get the portchannel peer port map. + :param duthost: DUT host object + :param orig_ports_configuration: original ports configuration parameters + :param tbinfo: Testbed object + :param nbrhosts: nbrhosts fixture. + :return: The dictionary of vm/ports mapping. + """ + peer_ports_map = {} + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + vm_neighbors = mg_facts['minigraph_neighbors'] + for _, port_dict in orig_ports_configuration.items(): + port = port_dict['port'] + if port_dict['portchannel']: + vm_host, peer_port = get_peer_port_info(nbrhosts, vm_neighbors, port) + if vm_host not in peer_ports_map: + peer_ports_map[vm_host] = [] + peer_ports_map[vm_host].append(peer_port) + return peer_ports_map + + +def get_peer_port_info(nbrhosts, vm_neighbors, intf): + """ + Get the peer port info + :param nbrhosts: nbrhosts fixture. + :param vm_neighbors: vm neighbors infos + :param intf: the intf on the dut + :return: Return the vm host connect to the intf, and the peer port of intf + """ + peer_device = vm_neighbors[intf]['name'] + vm_host = nbrhosts[peer_device]['host'] + peer_port = vm_neighbors[intf]['port'] + return vm_host, peer_port + + +def remove_acl_tables(duthost): + """ + Remove all the acl tables + :param duthost: DUT host object + :return: None + """ + acl_table_list = duthost.show_and_parse('show acl table') + for acl_table in acl_table_list: + if acl_table["name"]: + logger.info("Removing ACL table {}".format(acl_table["name"])) + duthost.shell("config acl remove table {}".format(acl_table["name"])) + + +def apply_config(duthost, ptfhost, ports_configuration): + """ + Apply the configuration on the DUT and PTF host + :param duthost: DUT host object + :param ptfhost: PTF host object + :param ports_configuration: ports configuration parameters + """ + apply_ptf_config(ptfhost, ports_configuration) + apply_dut_config(duthost, ports_configuration) + + +def recover_config(duthost, ptfhost, ports_configuration): + """ + Remove the configuration on the DUT and PTF host + :param duthost: DUT host object + :param ptfhost: PTF host object + :param ports_configuration: ports configuration parameters + """ + config_reload(duthost, safe_reload=True, check_intf_up_ports=True) + remove_ptf_config(ptfhost, ports_configuration) + + +def apply_dut_config(duthost, ports_configuration): + """ + Apply the configuration on the DUT host + :param duthost: DUT host object + :param ports_configuration: ports configuration parameters + """ + for _, port_conf in ports_configuration.items(): + port = port_conf['port'] + port_type = port_conf['type'] + ip_addr = port_conf['ip_addr'] + if port_type == ETHERNET_RIF: + add_ip_dut_port(duthost, port, ip_addr) + elif port_type == VLAN_RIF: + add_dut_vlan(duthost, port, port_conf['vlan_id'], ip_addr) + elif port_type == PO_RIF: + add_dut_portchannel(duthost, port, port_conf['po_id'], ip_addr) + elif port_type == SUB_PORT_RIF: + add_dut_sub_port(duthost, port, port_conf['vlan_id'], ip_addr, sub_port_type="eth") + elif port_type == PO_SUB_PORT_RIF: + add_dut_po_sub_port(duthost, port, port_conf['po_id'], port_conf['vlan_id'], ip_addr) + + +def apply_ptf_config(ptfhost, ports_configuration): + """ + Apply the configuration on the PTF host + :param ptfhost: PTF host object + :param ports_configuration: ports configuration parameters + """ + for _, port_conf in ports_configuration.items(): + port_type = port_conf['type'] + ptf_port = port_conf['ptf_port'] + + if port_type == PO_SUB_PORT_RIF or port_type == PO_RIF: + add_ptf_bond(ptfhost, ptf_port, port_conf['po_id'], port_conf['ptf_ip_addr']) + + ptfhost.shell("supervisorctl restart ptf_nn_agent") + time.sleep(5) + + +def remove_ptf_config(ptfhost, ports_configuration): + """ + Remove the configuration on the PTF host + :param ptfhost: PTF host object + :param ports_configuration: ports configuration parameters + """ + for _, port_conf in ports_configuration.items(): + port_type = port_conf['type'] + ptf_port = port_conf['ptf_port'] + if port_type == PO_SUB_PORT_RIF or port_type == PO_RIF: + remove_ptf_bond(ptfhost, ptf_port, port_conf['po_id'], port_conf['ptf_ip_addr']) + + ptfhost.shell("supervisorctl restart ptf_nn_agent") + time.sleep(5) + + +def add_ip_dut_port(duthost, port, ip_addr): + """ + Add ip address for the port on DUT host + :param duthost: DUT host object + :param port: port name + :param ip_addr: ip address + """ + duthost.shell('config interface ip add {} {}'.format(port, ip_addr)) + + +def remove_dut_ip_from_port(duthost, port, ip_addr): + """ + Remove ip address from the port on DUT host + :param duthost: DUT host object + :param port: port name + :param ip_addr: ip address + """ + duthost.shell('config interface ip remove {} {}'.format(port, ip_addr)) + + +def add_dut_vlan(duthost, port, vlan_id, ip_addr): + """ + Create vlan and add port to vlan member and config the ip address on the vlan + :param duthost: DUT host object + :param port: Port which will be added to the vlan + :param vlan_id: Vlan id + :param ip_addr: ip address + """ + duthost.shell('config vlan add {}'.format(vlan_id)) + duthost.shell('config vlan member add {} {}'.format(vlan_id, port)) + add_ip_dut_port(duthost, "Vlan{}".format(vlan_id), ip_addr) + + +def add_dut_portchannel(duthost, port, po_id, ip_addr): + """ + Create port channel on the dut, add port to the port channel member and config ip address for the portchannel + :param duthost: DUT host object + :param port: Port which will be added to the portchannel + :param po_id: portchannel id + :param ip_addr: ip address + """ + lag_port = "PortChannel{}".format(po_id) + duthost.shell('config portchannel add {}'.format(lag_port)) + duthost.shell('config portchannel member add {} {}'.format(lag_port, port)) + add_ip_dut_port(duthost, lag_port, ip_addr) + + +def add_dut_sub_port(duthost, port, vlan_id, ip_addr, sub_port_type="po"): + """ + Add sub port and configure the ip address on the sub port + :param duthost: DUT host object + :param port: Ethernet or PortChannel port + :param vlan_id: Vlan id of the sub port + :param ip_addr: Ip address + :param sub_port_type: sub port type, can be eth, po + """ + if sub_port_type != "eth": + port = port.replace("PortChannel", "Po") + subport = '{}.{}'.format(port, vlan_id) + duthost.shell('config subinterface add {} {}'.format(subport, vlan_id)) + add_ip_dut_port(duthost, subport, ip_addr) + + +def add_dut_po_sub_port(duthost, port, po_id, vlan_id, ip_addr): + """ + Add port channel sub port interface and config the ip address for it + :param duthost: DUT host object + :param port: port which will be add to the port channel + :param po_id: port channel id + :param vlan_id: vlan id + :param ip_addr: ip address + """ + lag_port = "PortChannel{}".format(po_id) + duthost.shell('config portchannel add {}'.format(lag_port)) + duthost.shell('config portchannel member add {} {}'.format(lag_port, port)) + add_dut_sub_port(duthost, lag_port, vlan_id, ip_addr) + + +def add_dut_portchannel_member(duthost, port, lag_port): + """ + Add the port to the portchannel member + :param duthost: DUT host object + :param port: port which will be added to the port channel. + :param lag_port: port channel + """ + duthost.shell('config portchannel member add {} {}'.format(lag_port, port)) + + +def remove_dut_portchannel_member(duthost, port, lag_port): + """ + Remove the port from the portchannel member + :param duthost: DUT host object + :param port: port which will be removed from the port channel. + :param lag_port: port channel + """ + duthost.shell('config portchannel member del {} {}'.format(lag_port, port)) + + +def add_dut_vlan_member(duthost, port, vlan_id): + """ + Add the port to the vlan member + :param duthost: DUT host object + :param port: port which will be added to the vlan. + :param vlan_id: vlan id + """ + duthost.shell('config vlan member add {} {}'.format(vlan_id, port)) + + +def remove_dut_vlan_member(duthost, port, vlan_id): + """ + Remove the port from the vlan member + :param duthost: DUT host object + :param port: port which will be removed from the vlan. + :param vlan_id: vlan id + """ + duthost.shell('config vlan member del {} {}'.format(vlan_id, port)) + + +def add_ptf_bond(ptfhost, port, bond_id, ip_addr): + """ + Add bond on the ptf host + :param ptfhost: PTF host object + :param port: the ptf port which will be added to the bond + :param bond_id: bond id + :param ip_addr: ip address + """ + try: + bond_port = 'bond{}'.format(bond_id) + ptfhost.shell("ip link add {} type bond".format(bond_port)) + ptfhost.shell("ip link set {} type bond miimon 100 mode 802.3ad".format(bond_port)) + ptfhost.shell("ip link set {} down".format(port)) + ptfhost.shell("ip link set {} master {}".format(port, bond_port)) + ptfhost.shell("ip link set dev {} up".format(bond_port)) + ptfhost.shell("ifconfig {} mtu 9216 up".format(bond_port)) + except Exception as e: + logger.error("Err when add bond on ptf host: {}".format(e)) + + +def remove_ptf_bond(ptfhost, port, bond_id, ip_addr): + """ + Remove bond on the ptf host + :param ptfhost: PTF host object + :param port: the ptf port which will be removed from the bond + :param bond_id: bond id + """ + try: + ptfhost.shell("ip link set bond{} nomaster".format(bond_id)) + ptfhost.shell("ip link set {} nomaster".format(port)) + ptfhost.shell("ip link set {} up".format(port)) + ptfhost.shell("ip link del bond{}".format(bond_id)) + except Exception as e: + logger.error("Err when remove bond on ptf host: {}".format(e)) + + +def verify_traffic(duthost, ptfadapter, rif_interfaces, ports_configuration, action_list): + """ + Verify traffic can be forwarded or dropped as expect + :param duthost: DUT host object + :param ptfadapter: PTF adapter object + :param rif_interfaces: List of rif interfaces + :param ports_configuration: ports configuration parameters + :param action_list: List of actions will be configure on the rif interface, the value can be forward, drop + """ + ip_src = "11.11.11.11" + for rif_interface, pkt_action in zip(rif_interfaces, action_list): + port_conf = ports_configuration[rif_interface] + src_port = port_conf['ptf_port'] + ip_dst = port_conf['ptf_ip_addr'] + port_index = port_conf['port_index'] + logger.info("Sending traffic from {}".format(src_port)) + generate_and_verify_traffic(duthost, ptfadapter, rif_interface, port_index, ip_src, ip_dst, + pkt_action=pkt_action) + + +def config_loopback_action(duthost, rif_interfaces, action_list, ignore_err=False): + """ + Config the loopback action for the rif interfaces + :param duthost: DUT host object + :param rif_interfaces: List of rif interfaces + :param action_list: List of actions will be configure on the rif interface + :param ignore_err: ignore the ansible err or not + """ + for rif_interface, action in zip(rif_interfaces, action_list): + duthost.shell(CONIFIG_LOOPBACK_ACTION_REG.format(rif_interface, action), module_ignore_errors=ignore_err) + + +def clear_rif_counter(duthost): + """ + Clear the rif counters + :param duthost: DUT host object + """ + duthost.shell("sonic-clear rifcounters") + + +def show_loopback_action(duthost): + """ + Get the loopback action for every rif interface + :param duthost: DUT host object + :return: loopback action for every rif interface + Example: + { + "Ethernet0": "drop", + "Vlan11": "drop", + "PortChannel11": "drop" + } + """ + res = duthost.shell("show ip interfaces loopback-action") + interfaces_loopback_actions = res['stdout'].splitlines()[2:] + interface_loopback_action_map = {} + for interface_loopback_action in interfaces_loopback_actions: + interface = interface_loopback_action.split(" ")[0].strip() + action = interface_loopback_action.split(" ")[-1].strip() + interface_loopback_action_map[interface] = action + return interface_loopback_action_map + + +def verify_interface_loopback_action(duthost, rif_interfaces, expected_actions): + """ + Verify the loopback action on the rif interfaces is configured as expected value + :param duthost: DUT host object + :param rif_interfaces: List of rif interface + :param expected_actions: drop or forward + """ + interface_loopback_action_map = show_loopback_action(duthost) + for rif_interface, expected_action in zip(rif_interfaces, expected_actions): + loopback_action = interface_loopback_action_map[rif_interface] + pytest_assert(loopback_action == expected_action, + "The loopback action on {} is {}, expected action is {}".format(rif_interface, loopback_action, + expected_action)) + + +def get_rif_tx_err_count(duthost): + """ + Get the TX ERR count for every rif interface + :param duthost: DUT host object + :return: rx err count for every rif interface + Example: + { + "Ethernet0": 0, + "Vlan11": 10, + "PortChannel11": 10 + } + """ + rif_counter_list = duthost.show_and_parse('show interfaces counters rif') + rif_tx_err_map = {counter["iface"]: counter["tx_err"] for counter in rif_counter_list} + return rif_tx_err_map + + +def verify_rif_tx_err_count(duthost, rif_interfaces, expect_counts): + """ + Verify the TX ERR count on the rif interfaces is increased as expected + :param duthost: DUT host object + :param rif_interfaces: List of rif interface + :param expect_counts: expected TX ERR for for every rif interface + """ + rif_tx_err_map = get_rif_tx_err_count(duthost) + for rif_interface, expected_count in zip(rif_interfaces, expect_counts): + tx_err_count = int(rif_tx_err_map[rif_interface]) + pytest_assert(tx_err_count == expected_count, + "The TX ERR count on {} is {}, expect TX ERR count is {}".format(rif_interface, tx_err_count, + expected_count)) + + +def shutdown_rif_interfaces(duthost, rif_interfaces): + """ + Shutdown interfaces on the DUT + :param duthost: DUT host object + :param rif_interfaces: rif interfaces list + """ + duthost.shutdown_multiple(rif_interfaces) + pytest_assert(wait_until(60, 1, 0, check_interface_state, duthost, rif_interfaces, 'down'), + "DUT's port {} didn't go down as expected".format(rif_interfaces)) + + +def startup_rif_interfaces(duthost, rif_interfaces): + """ + Start up interfaces on the DUT + :param duthost: DUT host object + :param rif_interfaces: rif interfaces list + """ + duthost.no_shutdown_multiple(rif_interfaces) + pytest_assert(wait_until(60, 1, 0, check_interface_state, duthost, rif_interfaces), + "DUT's port {} didn't go up as expected".format(rif_interfaces)) + + +def check_interface_state(duthost, rif_interfaces, state='up'): + """ + Check interface status + + :param duthost: DUT host object + :param rif_interfaces: rif interfaces list + :return: Bool value which confirm ports state + """ + ports_down = duthost.interface_facts(up_ports=rif_interfaces)['ansible_facts']['ansible_interface_link_down_ports'] + if 'down' in state: + return all(interface in ports_down for interface in rif_interfaces) + + return all(interface not in ports_down for interface in rif_interfaces) diff --git a/tests/iface_loopback_action/test_iface_loopback_action.py b/tests/iface_loopback_action/test_iface_loopback_action.py new file mode 100644 index 00000000000..96ca27ec390 --- /dev/null +++ b/tests/iface_loopback_action/test_iface_loopback_action.py @@ -0,0 +1,116 @@ +import pytest +import logging +import random +from tests.common.reboot import reboot +from tests.common.config_reload import config_reload +from tests.common.helpers.assertions import pytest_assert +from tests.common.utilities import wait_until +from tests.common.plugins.allure_wrapper import allure_step_wrapper as allure +from .iface_loopback_action_helper import ACTION_FORWARD, ACTION_DROP, NUM_OF_TOTAL_PACKETS +from .iface_loopback_action_helper import verify_traffic +from .iface_loopback_action_helper import config_loopback_action +from .iface_loopback_action_helper import clear_rif_counter +from .iface_loopback_action_helper import verify_interface_loopback_action +from .iface_loopback_action_helper import verify_rif_tx_err_count +from .iface_loopback_action_helper import shutdown_rif_interfaces, startup_rif_interfaces +from tests.common.platform.interface_utils import check_interface_status_of_up_ports + + +pytestmark = [ + pytest.mark.topology('any'), + pytest.mark.skip_check_dut_health +] + +logger = logging.getLogger(__name__) +allure.logger = logger + + +def test_loopback_action_basic(duthost, ptfadapter, ports_configuration): + rif_interfaces = list(ports_configuration.keys()) + intf_count = len(rif_interfaces) + with allure.step("Verify the rif loopback action default action: loopback traffic will be forwarded"): + verify_traffic(duthost, ptfadapter, rif_interfaces, ports_configuration, [ACTION_FORWARD] * intf_count) + with allure.step("Configure the loopback action to {}".format(ACTION_DROP)): + config_loopback_action(duthost, rif_interfaces, [ACTION_DROP] * intf_count) + with allure.step("Verify the loopback action is configured to drop"): + with allure.step("Check the looback action is configured correctly with cli command"): + verify_interface_loopback_action(duthost, rif_interfaces, [ACTION_DROP] * intf_count) + with allure.step("Check the loopback traffic should be dropped"): + with allure.step("Clear the rif counter"): + clear_rif_counter(duthost) + with allure.step("Check the traffic can not be received on the destination"): + verify_traffic(duthost, ptfadapter, rif_interfaces, ports_configuration, [ACTION_DROP] * intf_count) + with allure.step("Check the TX_ERR in rif counter statistic will increase"): + verify_rif_tx_err_count(duthost, rif_interfaces, [NUM_OF_TOTAL_PACKETS]*intf_count) + with allure.step("Configure the loopback action to forward"): + config_loopback_action(duthost, rif_interfaces, [ACTION_FORWARD] * intf_count) + with allure.step("Verify the loopback action is configured to forward"): + with allure.step("Check the looback action is configured correctly with cli command"): + verify_interface_loopback_action(duthost, rif_interfaces, [ACTION_FORWARD] * intf_count) + with allure.step("Check the loopback traffic should be forwarded"): + with allure.step("Clear the rif counter"): + clear_rif_counter(duthost) + with allure.step("Check the traffic can be received on the destination"): + verify_traffic(duthost, ptfadapter, rif_interfaces, ports_configuration, [ACTION_FORWARD] * intf_count) + with allure.step("Check the TX_ERR in rif counter statistic will not increase"): + verify_rif_tx_err_count(duthost, rif_interfaces, [0] * intf_count) + + +def test_loopback_action_port_flap(duthost, ptfadapter, ports_configuration): + rif_interfaces = list(ports_configuration.keys()) + # Remove the vlan interface since vlan interface can not be shutdown or startup + rif_interfaces = [iface for iface in rif_interfaces if not iface.startswith("Vlan")] + intf_count = len(rif_interfaces) + action_list = [random.choice([ACTION_FORWARD, ACTION_DROP]) for i in range(intf_count)] + count_list = [NUM_OF_TOTAL_PACKETS if action == ACTION_DROP else 0 for action in action_list] + with allure.step("Configure the loopback action for {} to {}".format(rif_interfaces, action_list)): + config_loopback_action(duthost, rif_interfaces, action_list) + with allure.step("Shutdown the interfaces"): + shutdown_rif_interfaces(duthost, rif_interfaces) + with allure.step("Startup the interfaces"): + startup_rif_interfaces(duthost, rif_interfaces) + with allure.step("Verify the loopback action is correct of port flap"): + with allure.step("Check the looback action is configured correctly with cli command"): + verify_interface_loopback_action(duthost, rif_interfaces, action_list) + with allure.step("Check the loopback traffic"): + with allure.step("Clear the rif counter"): + clear_rif_counter(duthost) + with allure.step("Check the traffic can be received or dropped as expected"): + verify_traffic(duthost, ptfadapter, rif_interfaces, ports_configuration, action_list) + with allure.step("Check the TX_ERR in rif counter statistic will increase or not as expected"): + verify_rif_tx_err_count(duthost, rif_interfaces, count_list) + + +def test_loopback_action_reload(request, duthost, localhost, ptfadapter, ports_configuration): + rif_interfaces = list(ports_configuration.keys()) + intf_count = len(rif_interfaces) + action_list = [random.choice([ACTION_FORWARD, ACTION_DROP]) for i in range(intf_count)] + count_list = [NUM_OF_TOTAL_PACKETS if action == ACTION_DROP else 0 for action in action_list] + with allure.step("Configure the loopback action for {} to {}".format(rif_interfaces, action_list)): + config_loopback_action(duthost, rif_interfaces, action_list) + with allure.step("Save configuration"): + duthost.shell("config save -y") + with allure.step("System reload"): + + reboot_type = request.config.getoption("--rif_loppback_reboot_type") + if reboot_type == "random": + reload_types = ["reload", "cold", "fast", "warm"] + reboot_type = random.choice(reload_types) + if reboot_type == "reload": + config_reload(duthost, safe_reload=True, check_intf_up_ports=True) + else: + reboot(duthost, localhost, reboot_type) + pytest_assert(wait_until(300, 20, 0, duthost.critical_services_fully_started), + "All critical services should be fully started!") + pytest_assert(wait_until(300, 20, 0, check_interface_status_of_up_ports, duthost), + "Not all ports that are admin up on are operationally up") + with allure.step("Verify the loopback action is correct after config reload"): + with allure.step("Check the looback action is configured correctly with cli command"): + verify_interface_loopback_action(duthost, rif_interfaces, action_list) + with allure.step("Check the loopback traffic"): + with allure.step("Clear the rif counter"): + clear_rif_counter(duthost) + with allure.step("Check the traffic can be received or dropped as expected"): + verify_traffic(duthost, ptfadapter, rif_interfaces, ports_configuration, action_list) + with allure.step("Check the TX_ERR in rif counter statistic will increase or not as expected"): + verify_rif_tx_err_count(duthost, rif_interfaces, count_list)