diff --git a/tests/bgp/bgp_helpers.py b/tests/bgp/bgp_helpers.py index 9d5c27b114c..ea6d2095703 100644 --- a/tests/bgp/bgp_helpers.py +++ b/tests/bgp/bgp_helpers.py @@ -4,6 +4,7 @@ import json import pytest import yaml +import random import logging import requests from natsort import natsorted @@ -45,6 +46,13 @@ ALLOW_LIST_PREFIX_JSON_FILE = '/tmp/allow_list.json' DROP_COMMUNITY = '' DEFAULT_ACTION = '' +ANNOUNCE = 'announce' +DEFAULT = "default" +IP_VER = 4 +QUEUED = "queued" +ACTION_IN = "in" +ACTION_NOT_IN = "not" +ACTION_STOP = "stop" def apply_bgp_config(duthost, template_name): @@ -514,3 +522,210 @@ def get_default_action(): Since the value of this constant has been changed in the helper, it cannot be directly imported """ return DEFAULT_ACTION + + +def restart_bgp_session(duthost): + """ + Restart bgp session + """ + logging.info("Restart all BGP sessions") + duthost.shell('vtysh -c "clear bgp *"') + + +def get_ptf_recv_port(duthost, vm_name, tbinfo): + """ + Get ptf receive port + """ + port = duthost.shell("show lldp table | grep -w {} | awk '{{print $1}}'".format(vm_name))['stdout'] + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + return mg_facts['minigraph_ptf_indices'][port] + + +def get_eth_port(duthost, tbinfo): + """ + Get ethernet port that connects to T0 VM + """ + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + t0_vm = [vm_name for vm_name in mg_facts['minigraph_devices'].keys() if vm_name.endswith('T0')][0] + port = duthost.shell("show ip interface | grep -w {} | awk '{{print $1}}'".format(t0_vm))['stdout'] + return port + + +def get_vm_offset(duthost, nbrhosts, tbinfo): + """ + Get port offset of exabgp and ptf receive port + """ + logging.info("get_vm_offset ---------") + vm_name = random.choice([vm_name for vm_name in nbrhosts.keys() if vm_name.endswith('T0')]) + ptf_recv_port = get_ptf_recv_port(duthost, vm_name, tbinfo) + port_offset = tbinfo['topo']['properties']['topology']['VMs'][vm_name]['vm_offset'] + logging.info("vm_offset of {} is: {}".format(vm_name, port_offset)) + return port_offset, ptf_recv_port + + +def get_exabgp_port(duthost, nbrhosts, tbinfo, exabgp_base_port): + """ + Get exabgp port and ptf receive port + """ + port_offset, ptf_recv_port = get_vm_offset(duthost, nbrhosts, tbinfo) + return exabgp_base_port + port_offset, ptf_recv_port + + +def get_vm_name(tbinfo, vm_level='T2'): + """ + Get vm name, default return value would be T2 VM name + """ + for vm in tbinfo['topo']['properties']['topology']['VMs'].keys(): + if vm[-2:] == vm_level: + return vm + + +def get_t2_ptf_intfs(mg_facts): + """ + Get ptf interface list that connect with T2 VMs + """ + t2_ethernets = [] + for k, v in mg_facts["minigraph_neighbors"].items(): + if v['name'][-2:] == 'T2': + t2_ethernets.append(k) + + ptf_interfaces = [] + for port in t2_ethernets: + ptf_interfaces.append(mg_facts['minigraph_ptf_indices'][port]) + return ptf_interfaces + + +def get_bgp_neighbor_ip(duthost, vm_name, vrf=DEFAULT): + """ + Get ipv4 and ipv6 bgp neighbor ip addresses + """ + if vrf == DEFAULT: + cmd_v4 = "show ip interface | grep -w {} | awk '{{print $5}}'" + cmd_v6 = "show ipv6 interface | grep -w {} | awk '{{print $5}}'" + bgp_neighbor_ip = duthost.shell(cmd_v4.format(vm_name))['stdout'] + bgp_neighbor_ipv6 = duthost.shell(cmd_v6.format(vm_name))['stdout'] + else: + cmd_v4 = "show ip interface | grep -w {} | awk '{{print $7}}' | sed 's/)//g'" + cmd_v6 = "show ipv6 interface | grep -w {} | awk '{{print $7}}' | sed 's/)//g'" + bgp_neighbor_ip = duthost.shell(cmd_v4.format(vm_name))['stdout'][1:-1] + bgp_neighbor_ipv6 = duthost.shell(cmd_v6.format(vm_name))['stdout'][1:-1] + logging.info("BGP neighbor of {} is {}".format(vm_name, bgp_neighbor_ip)) + logging.info("IPv6 BGP neighbor of {} is {}".format(vm_name, bgp_neighbor_ipv6)) + + return bgp_neighbor_ip, bgp_neighbor_ipv6 + + +def get_vrf_route_json(duthost, route, vrf=DEFAULT, ip_ver=IP_VER): + """ + Get output of 'show ip route vrf xxx xxx json' or 'show ipv6 route vrf xxx xxx json' + """ + if ip_ver == IP_VER: + logging.info('Execute command - vtysh -c "show ip route vrf {} {} json"'.format(vrf, route)) + out = json.loads(duthost.shell('vtysh -c "show ip route vrf {} {} json"'. + format(vrf, route), verbose=False)['stdout']) + else: + logging.info('Execute command - vtysh -c "show ipv6 route vrf {} {} json"'.format(vrf, route)) + out = json.loads(duthost.shell('vtysh -c "show ipv6 route vrf {} {} json"'. + format(vrf, route), verbose=False)['stdout']) + + logging.info('Command output:\n {}'.format(out)) + return out + + +def check_route_status(duthost, route, check_field, vrf=DEFAULT, ip_ver=IP_VER, expect_status=True): + """ + Get 'offloaded' or 'queu' value of specific route + """ + out = get_vrf_route_json(duthost, route, vrf, ip_ver) + check_field_status = out[route][0].get(check_field, None) + if check_field_status: + logging.info("Route:{} - {} status:{} - expect status:{}" + .format(route, check_field, check_field_status, expect_status)) + return True is expect_status + else: + logging.info("No {} value found in route:{}".format(check_field, out)) + return False is expect_status + + +def check_route_install_status(duthost, route, vrf=DEFAULT, ip_ver=IP_VER, check_point=QUEUED, action=ACTION_IN): + """ + Verify route install status + """ + if check_point == QUEUED: + if action == ACTION_IN: + pytest_assert(wait_until(60, 2, 0, check_route_status, duthost, route, check_point, vrf, ip_ver), + "Vrf:{} - route:{} is not in {} state".format(vrf, route, check_point)) + else: + pytest_assert(wait_until(60, 2, 0, check_route_status, duthost, route, check_point, vrf, ip_ver, False), + "Vrf:{} - route:{} is in {} state".format(vrf, route, check_point)) + else: + if action == ACTION_IN: + pytest_assert(wait_until(60, 2, 0, check_route_status, duthost, route, check_point, vrf, ip_ver), + "Vrf:{} - route:{} is not installed into FIB".format(vrf, route)) + else: + pytest_assert(wait_until(60, 2, 0, check_route_status, duthost, route, check_point, vrf, ip_ver, False), + "Vrf:{} - route:{} is installed into FIB".format(vrf, route)) + + +def check_propagate_route(duthost, route, bgp_neighbor, vrf=DEFAULT, ip_ver=IP_VER, action=ACTION_IN): + """ + Check whether ipv4 or ipv6 route is advertised to T2 VM + """ + if ip_ver == IP_VER: + logging.info('Execute command - vtysh -c "show ip bgp vrf {} neighbors {} advertised-routes"' + .format(vrf, bgp_neighbor)) + out = duthost.shell('vtysh -c "show ip bgp vrf {} neighbors {} advertised-routes"'.format(vrf, bgp_neighbor), + verbose=False)['stdout'] + else: + logging.info('Execute command - vtysh -c "show ip bgp vrf {} ipv6 neighbors {} advertised-routes"' + .format(vrf, bgp_neighbor)) + out = duthost.shell('vtysh -c "show ip bgp vrf {} ipv6 neighbors {} advertised-routes"'. + format(vrf, bgp_neighbor), verbose=False)['stdout'] + logging.debug('Command output:\n {}'.format(out)) + + if action == ACTION_IN: + if route in out: + logging.info("Route:{} found - action:{}".format(route, action)) + return True + else: + logging.info("Route:{} not found - action:{}".format(route, action)) + return False + else: + if route in out: + logging.info("Route:{} found - action:{}".format(route, action)) + return False + else: + logging.info("Route:{} not found - action:{}".format(route, action)) + return True + + +def validate_route_propagate_status(duthost, route, bgp_neighbor, vrf=DEFAULT, ip_ver=IP_VER, exist=True): + """ + Verify ipv4 or ipv6 route propagate status + :param duthost: duthost fixture + :param route: ipv4 or ipv6 route + :param bgp_neighbor: ipv4 or ipv6 bgp neighbor address + :param vrf: vrf name + :param ip_ver: ip version number + :param exist: route expected status + """ + if exist: + pytest_assert(wait_until(30, 2, 0, check_propagate_route, duthost, route, bgp_neighbor, vrf, ip_ver), + "Vrf:{} - route:{} is not propagated to {}".format(vrf, route, bgp_neighbor)) + else: + pytest_assert(wait_until(30, 2, 0, check_propagate_route, duthost, route, bgp_neighbor, vrf, ip_ver, + ACTION_NOT_IN), + "Vrf:{} - route:{} is propagated to {}".format(vrf, route, bgp_neighbor)) + + +def operate_orchagent(duthost, action=ACTION_STOP): + """ + Stop or Continue orchagent process + """ + if action == ACTION_STOP: + logging.info('Suspend orchagent process to simulate a delay') + cmd = 'sudo kill -SIGSTOP $(pidof orchagent)' + else: + logging.info('Recover orchagent process') + cmd = 'sudo kill -SIGCONT $(pidof orchagent)' + duthost.shell(cmd) diff --git a/tests/bgp/conftest.py b/tests/bgp/conftest.py index 62888920202..6110e319e47 100644 --- a/tests/bgp/conftest.py +++ b/tests/bgp/conftest.py @@ -596,3 +596,52 @@ def bgpmon_setup_teardown(ptfhost, duthosts, enum_rand_one_per_hwsku_frontend_ho # Remove the route to DUT loopback IP and the interface router mac ptfhost.shell("ip route del %s" % dut_lo_addr + "/32") ptfhost.shell("ip neigh flush to %s nud permanent" % dut_lo_addr) + + +def pytest_addoption(parser): + """ + Adds options to pytest that are used by bgp suppress fib pending test + """ + + parser.addoption( + "--bgp_suppress_fib_pending", + action="store_true", + dest="bgp_suppress_fib_pending", + default=False, + help="enable bgp suppress fib pending function, by default it will not enable bgp suppress fib pending function" + ) + parser.addoption( + "--bgp_suppress_fib_reboot_type", + action="store", + dest="bgp_suppress_fib_reboot_type", + type=str, + choices=["reload", "fast", "warm", "cold", "random"], + default="reload", + help="reboot type such as reload, fast, warm, cold, random" + ) + + +@pytest.fixture(scope="module", autouse=True) +def config_bgp_suppress_fib(duthosts, rand_one_dut_hostname, request): + """ + Enable or disable bgp suppress-fib-pending function + """ + duthost = duthosts[rand_one_dut_hostname] + config = request.config.getoption("--bgp_suppress_fib_pending") + logger.info("--bgp_suppress_fib_pending:{}".format(config)) + + if config: + logger.info("Check if bgp suppress fib pending is supported") + res = duthost.command("show suppress-fib-pending", module_ignore_errors=True) + if res['rc'] != 0: + pytest.skip('BGP suppress fib pending function is not supported') + logger.info('Enable BGP suppress fib pending function') + duthost.shell('sudo config suppress-fib-pending enabled') + duthost.shell('sudo config save -y') + + yield + + if config: + logger.info('Disable BGP suppress fib pending function') + duthost.shell('sudo config suppress-fib-pending disabled') + duthost.shell('sudo config save -y') diff --git a/tests/bgp/test_bgp_suppress_fib.py b/tests/bgp/test_bgp_suppress_fib.py new file mode 100644 index 00000000000..8822c33c96a --- /dev/null +++ b/tests/bgp/test_bgp_suppress_fib.py @@ -0,0 +1,604 @@ +import requests +import json +import logging +import random +import ipaddress +import pytest +import allure +import ptf.testutils as testutils +import ptf.packet as scapy +from copy import deepcopy + +from ptf.mask import Mask +from natsort import natsorted +from tests.common.reboot import reboot +from tests.common.utilities import wait_until +from tests.common.config_reload import config_reload +from tests.common.helpers.assertions import pytest_assert +from tests.common.platform.interface_utils import check_interface_status_of_up_ports +from bgp_helpers import restart_bgp_session, get_eth_port, get_exabgp_port, get_vm_name, get_bgp_neighbor_ip, \ + check_route_install_status, validate_route_propagate_status, operate_orchagent, get_t2_ptf_intfs + +pytestmark = [ + pytest.mark.topology("t1"), + pytest.mark.skip_check_dut_health +] + +logger = logging.getLogger(__name__) + +EXABGP_BASE_PORT = 5000 +EXABGP_BASE_PORT_V6 = 6000 +WITHDRAW = 'withdraw' +ANNOUNCE = 'announce' +ACTION_STOP = "stop" +ACTION_CONTINUE = "continue" +FORWARD = "FORWARD" +DROP = "DROP" +ACTION_IN = "in" +ACTION_NOT_IN = "not" +QUEUED = "queued" +OFFLOADED = "offloaded" +IP_VER = 4 +IPV6_VER = 6 +SRC_IP = { + 4: "192.168.0.2", + 6: "fc02:1000::2" +} +USER_DEFINED_VRF = "Vrf1" +DEFAULT = "default" +VRF_TYPES = [DEFAULT, USER_DEFINED_VRF] + +STATIC_ROUTE_PREFIX = "1.1.1.0/24" +# ipv4 route injection from T0 +IP_ROUTE_LIST = [ + '91.0.1.0/24', + '91.0.2.0/24' +] + +# ipv6 route injection from T0 +IPV6_ROUTE_LIST = [ + '1000:1001::1/128', + '1000:1001::2/128' +] + +TRAFFIC_DATA_FORWARD = [ + # src_ip, expected_result + ("91.0.1.1", FORWARD), + ("91.0.2.1", FORWARD), + ("1000:1001::1", FORWARD), + ("1000:1001::2", FORWARD) +] + +TRAFFIC_DATA_DROP = [ + # src_ip, expected_result + ("91.0.1.1", DROP), + ("91.0.2.1", DROP), + ("1000:1001::1", DROP), + ("1000:1001::2", DROP), +] + + +@pytest.fixture(scope="function") +def restore_bgp_suppress_fib(duthost): + """ + Restore bgp suppress fib pending function + """ + yield + + config_bgp_suppress_fib(duthost, False) + logger.info("Save configuration") + duthost.shell('sudo config save -y') + + +@pytest.fixture(scope="module") +def get_exabgp_ptf_ports(duthost, nbrhosts, tbinfo): + """ + Get ipv4 and ipv6 Exabgp port and ptf receive port + """ + exabgp_port, ptf_recv_port = get_exabgp_port(duthost, nbrhosts, tbinfo, EXABGP_BASE_PORT) + exabgp_port_v6, ptf_recv_port_v6 = get_exabgp_port(duthost, nbrhosts, tbinfo, EXABGP_BASE_PORT_V6) + return exabgp_port, ptf_recv_port, exabgp_port_v6, ptf_recv_port_v6 + + +def is_orchagent_stopped(duthost): + """ + Check if process 'orchagent' is stopped + """ + out = duthost.shell('cat /proc/$(pidof orchagent)/status | grep State')['stdout'] + logger.info('Orchagent process - {}'.format(out)) + return ACTION_STOP in out + + +@pytest.fixture(scope="function", autouse=True) +def withdraw_bgp_routes_and_restore_orchagent(duthost, tbinfo, nbrhosts, get_exabgp_ptf_ports): + """ + Fixture to withdraw ipv4 and ipv6 routes and restore process 'orchagent' in case of unexpected failures in case + """ + yield + + ptf_ip = tbinfo['ptf_ip'] + exabgp_port, _, exabgp_port_v6, _ = get_exabgp_ptf_ports + announce_ipv4_ipv6_routes(ptf_ip, exabgp_port, exabgp_port_v6, action=WITHDRAW) + if is_orchagent_stopped(duthost): + logger.info('Orchagent process stopped, will restore it') + operate_orchagent(duthost, action=ACTION_CONTINUE) + + +def get_cfg_facts(duthost): + """ + Get config port indices + """ + cfg_facts = json.loads(duthost.shell("sonic-cfggen -d --print-data")['stdout']) + + port_name_list_sorted = natsorted(cfg_facts['PORT'].keys()) + port_index_map = {} + for idx, val in enumerate(port_name_list_sorted): + port_index_map[val] = idx + + cfg_facts['config_port_indices'] = port_index_map + + return cfg_facts + + +def get_port_connected_with_t0_vm(duthost, nbrhosts): + """ + Get ports that connects with T0 VM + """ + port_list = [] + t0_vm_list = [vm_name for vm_name in nbrhosts.keys() if vm_name.endswith('T0')] + for t0_vm in t0_vm_list: + port = duthost.shell("show ip interface | grep -w {} | awk '{{print $1}}'".format(t0_vm))['stdout'] + port_list.append(port) + logger.info("Ports connected with T0 VMs: {}".format(port_list)) + return port_list + + +def setup_vrf_cfg(duthost, cfg_facts, nbrhosts, tbinfo): + """ + Config vrf based configuration + """ + cfg_t1 = deepcopy(cfg_facts) + cfg_t1.pop('config_port_indices', None) + port_list = get_port_connected_with_t0_vm(duthost, nbrhosts) + vm_list = nbrhosts.keys() + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + port_channel_list = mg_facts['minigraph_portchannels'].keys() + extra_vars = {'cfg_t1': cfg_t1, 'port_list': port_list, 'vm_list': vm_list, 'pc_list': port_channel_list} + + duthost.host.options['variable_manager'].extra_vars.update(extra_vars) + + duthost.template(src="bgp/vrf_config_db.j2", dest="/tmp/config_db_vrf.json") + duthost.shell("cp -f /tmp/config_db_vrf.json /etc/sonic/config_db.json") + + config_reload(duthost) + + +def setup_vrf(duthost, nbrhosts, tbinfo): + """ + Prepare vrf based environment + """ + logger.info("Back up original config_db.json") + duthost.shell("mv /etc/sonic/config_db.json /etc/sonic/config_db.json.bak") + + cfg_t1 = get_cfg_facts(duthost) + setup_vrf_cfg(duthost, cfg_t1, nbrhosts, tbinfo) + + +def install_route_from_exabgp(operation, ptfip, route, port): + """ + Install or withdraw ipv4 or ipv6 route by exabgp + """ + url = "http://{}:{}".format(ptfip, port) + data = {"command": "{} route {} next-hop self".format(operation, route)} + logging.info("url: {}".format(url)) + logging.info("data: {}".format(data)) + r = requests.post(url, data=data) + assert r.status_code == 200 + + +def announce_route(ptfip, route, port, action=ANNOUNCE): + """ + Announce or withdraw ipv4 or ipv6 route + """ + logging.info("\n========================== announce_route -- {} ==========================".format(action)) + logging.info(" action:{}\n ptfip:{}\n route:{}\n port:{}".format(action, ptfip, route, port)) + install_route_from_exabgp(action, ptfip, route, port) + logging.info("\n--------------------------------------------------------------------------------") + + +def generate_packet(src_ip, dst_ip, dst_mac): + """ + Build ipv4 and ipv6 packets/expected_packets for testing + """ + if ipaddress.ip_network(src_ip.encode().decode(), False).version == 4: + pkt = testutils.simple_ip_packet(eth_dst=dst_mac, ip_src=src_ip, ip_dst=dst_ip) + exp_pkt = Mask(pkt) + exp_pkt.set_do_not_care_scapy(scapy.IP, "ttl") + exp_pkt.set_do_not_care_scapy(scapy.IP, "chksum") + else: + pkt = testutils.simple_tcpv6_packet(eth_dst=dst_mac, ipv6_src=src_ip, ipv6_dst=dst_ip) + exp_pkt = Mask(pkt) + exp_pkt.set_do_not_care_scapy(scapy.IPv6, "hlim") + + exp_pkt.set_do_not_care_scapy(scapy.Ether, "dst") + exp_pkt.set_do_not_care_scapy(scapy.Ether, "src") + + return pkt, exp_pkt + + +def send_and_verify_packet(ptfadapter, pkt, exp_pkt, tx_port, rx_port, expected_action): + """ + Send packet with ptfadapter and verify if packet is forwarded or dropped as expected + """ + ptfadapter.dataplane.flush() + testutils.send(ptfadapter, pkt=pkt, port_id=tx_port) + if expected_action == FORWARD: + testutils.verify_packet(ptfadapter, pkt=exp_pkt, port_id=rx_port, timeout=5) + else: + testutils.verify_no_packet(ptfadapter, pkt=exp_pkt, port_id=rx_port, timeout=5) + + +def send_and_verify_loopback_packets(ptfadapter, pkt, exp_pkt, tx_port, rx_ports, expected_action): + """ + Send packet with ptfadapter and verify if packet is forwarded back or dropped as expected + """ + ptfadapter.dataplane.flush() + testutils.send(ptfadapter, pkt=pkt, port_id=tx_port) + if expected_action == FORWARD: + testutils.verify_packets_any(ptfadapter, pkt=exp_pkt, ports=rx_ports) + else: + testutils.verify_no_packet_any(ptfadapter, pkt=exp_pkt, ports=rx_ports) + + +def validate_traffic(ptfadapter, traffic_data, router_mac, ptf_interfaces, recv_port, loop_back=False): + """ + Verify traffic is forwarded/forwarded back/drop as expected + """ + for test_item in traffic_data: + dst_ip = test_item[0] + expected_result = test_item[1] + ip_ver = ipaddress.ip_network(dst_ip.encode().decode(), False).version + logger.info("Testing with dst_ip = {} expected_result = {}" + .format(dst_ip, expected_result)) + pkt, exp_pkt = generate_packet(SRC_IP[ip_ver], dst_ip, router_mac) + tx_port = random.choice(ptf_interfaces) + if ptf_interfaces is recv_port: + if loop_back: + logger.info("Expected packet:\n dst_mac:{} - src_ip:{} - dst_ip:{} - ptf tx_port:{} - ptf rx_port:{}". + format(router_mac, SRC_IP[ip_ver], dst_ip, tx_port, ptf_interfaces)) + send_and_verify_loopback_packets(ptfadapter, pkt, exp_pkt, tx_port, ptf_interfaces, expected_result) + else: + logger.info("Loopback traffic - expected packet:\n dst_mac:{} - src_ip:{} - dst_ip:{} - ptf tx_port:{}\ + - ptf rx_port:{}".format(router_mac, SRC_IP[ip_ver], dst_ip, tx_port, tx_port)) + send_and_verify_packet(ptfadapter, pkt, exp_pkt, tx_port, tx_port, expected_result) + else: + logger.info("Expected packet:\n dst_mac:{} - src_ip:{} - dst_ip:{} - ptf tx_port:{} - ptf rx_port:{}". + format(router_mac, SRC_IP[ip_ver], dst_ip, tx_port, recv_port[ip_ver])) + send_and_verify_packet(ptfadapter, pkt, exp_pkt, tx_port, recv_port[ip_ver], expected_result) + + +def announce_ipv4_ipv6_routes(ptf_ip, exabgp_port, exabgp_port_v6, action=ANNOUNCE): + """ + Announce or withdraw ipv4 and ipv6 routes by exabgp + """ + for route in IP_ROUTE_LIST: + announce_route(ptf_ip, route, exabgp_port, action) + + for route in IPV6_ROUTE_LIST: + announce_route(ptf_ip, route, exabgp_port_v6, action) + + +def config_bgp_suppress_fib(duthost, enable=True): + """ + Enable or disable bgp suppress-fib-pending function + """ + if enable: + logger.info('Enable BGP suppress fib pending function') + cmd = 'sudo config suppress-fib-pending enabled' + else: + logger.info('Disable BGP suppress fib pending function') + cmd = 'sudo config suppress-fib-pending disabled' + duthost.shell(cmd) + + +def do_and_wait_reboot(duthost, localhost, reboot_type): + """ + Do reboot and wait critical services and ports up + """ + with allure.step("Do {}".format(reboot_type)): + reboot(duthost, localhost, reboot_type=reboot_type, reboot_helper=None, reboot_kwargs=None, + wait_warmboot_finalizer=True) + 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") + + +def param_reboot(request, duthost, localhost): + """ + Read reboot_type from option bgp_suppress_fib_reboot_type + If reboot_type is reload, do config reload + If reboot_type is random, randomly choose one action from reload/cold/warm/fast reboot + Else do a reboot directly as bgp_suppress_fib_reboot_type assigned + """ + reboot_type = request.config.getoption("--bgp_suppress_fib_reboot_type") + reboot_type_list = ["reload", "cold", "warm", "fast"] + if reboot_type == "random": + reboot_type = random.choice(reboot_type_list) + logger.info("Randomly choose {} from reload, cold, warm, fast".format(reboot_type)) + + if reboot_type == "reload": + config_reload(duthost, safe_reload=True, check_intf_up_ports=True) + else: + do_and_wait_reboot(duthost, localhost, reboot_type) + + +def validate_route_states(duthost, vrf=DEFAULT, check_point=QUEUED, action=ACTION_IN): + """ + Verify ipv4 and ipv6 routes install status + """ + for route in IP_ROUTE_LIST: + check_route_install_status(duthost, route, vrf, IP_VER, check_point, action) + for route in IPV6_ROUTE_LIST: + check_route_install_status(duthost, route, vrf, IPV6_VER, check_point, action) + + +def validate_route_propagate(duthost, tbinfo, vrf=DEFAULT, exist=True): + """ + Verify ipv4 and ipv6 route propagate status + """ + t2_vm = get_vm_name(tbinfo) + bgp_neighbor_v4, bgp_neighbor_v6 = get_bgp_neighbor_ip(duthost, t2_vm, vrf) + for route in IP_ROUTE_LIST: + validate_route_propagate_status(duthost, route, bgp_neighbor_v4, vrf, exist=exist) + for route in IPV6_ROUTE_LIST: + validate_route_propagate_status(duthost, route, bgp_neighbor_v6, vrf, ip_ver=IPV6_VER, exist=exist) + + +def redistribute_static_route_to_bgp(duthost, redistribute=True): + """ + Enable or disable redistribute static route to BGP + """ + vtysh_cmd = "sudo vtysh" + config_terminal = " -c 'config'" + enter_bgp_mode = " -c 'router bgp'" + enter_address_family_ipv4 = " -c 'address-family ipv4'" + redistribute_static = " -c 'redistribute static'" + no_redistribute_static = " -c 'no redistribute static'" + if redistribute: + duthost.shell(vtysh_cmd + config_terminal + enter_bgp_mode + enter_address_family_ipv4 + redistribute_static) + else: + duthost.shell(vtysh_cmd + config_terminal + enter_bgp_mode + enter_address_family_ipv4 + no_redistribute_static) + + +def remove_static_route_and_redistribute(duthost): + """ + Remove static route and stop redistribute it to BGP + """ + out = duthost.shell("show ip route {}".format(STATIC_ROUTE_PREFIX), verbose=False)['stdout'] + if STATIC_ROUTE_PREFIX in out: + duthost.shell("sudo config route del prefix {}".format(STATIC_ROUTE_PREFIX)) + redistribute_static_route_to_bgp(duthost, redistribute=False) + + +@pytest.mark.parametrize("vrf_type", VRF_TYPES) +def test_bgp_route_with_suppress(duthost, tbinfo, nbrhosts, ptfadapter, localhost, restore_bgp_suppress_fib, + get_exabgp_ptf_ports, vrf_type, request): + try: + if vrf_type == USER_DEFINED_VRF: + with allure.step("Configure user defined vrf"): + setup_vrf(duthost, nbrhosts, tbinfo) + + with allure.step("Prepare needed parameters"): + router_mac = duthost.facts["router_mac"] + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + ptf_ip = tbinfo['ptf_ip'] + exabgp_port, ptf_recv_port, exabgp_port_v6, ptf_recv_port_v6 = get_exabgp_ptf_ports + recv_port = { + 4: ptf_recv_port, + 6: ptf_recv_port_v6 + } + + with allure.step("Config bgp suppress-fib-pending function"): + config_bgp_suppress_fib(duthost) + + with allure.step("Save configuration"): + logger.info("Save configuration") + duthost.shell('sudo config save -y') + + with allure.step("Do reload"): + param_reboot(request, duthost, localhost) + + with allure.step("Suspend orchagent process to simulate a route install delay"): + operate_orchagent(duthost) + + with allure.step("Announce BGP ipv4 and ipv6 routes to DUT from T0 VM by ExaBGP"): + announce_ipv4_ipv6_routes(ptf_ip, exabgp_port, exabgp_port_v6) + + with allure.step("Validate announced BGP ipv4 and ipv6 routes are in {} state".format(QUEUED)): + validate_route_states(duthost, vrf_type) + + with allure.step("Validate BGP ipv4 and ipv6 routes are not announced to T2 VM peer"): + validate_route_propagate(duthost, tbinfo, vrf_type, exist=False) + + with allure.step("Validate traffic could not be forwarded to T0 VM"): + ptf_interfaces = get_t2_ptf_intfs(mg_facts) + validate_traffic(ptfadapter, TRAFFIC_DATA_DROP, router_mac, ptf_interfaces, recv_port) + + with allure.step("Restore orchagent process"): + operate_orchagent(duthost, action=ACTION_CONTINUE) + + with allure.step("Validate announced BGP ipv4 and ipv6 routes are not in {} state".format(QUEUED)): + validate_route_states(duthost, vrf_type, check_point=QUEUED, action=ACTION_NOT_IN) + + with allure.step("Validate announced BGP ipv4 and ipv6 routes are in {} state".format(OFFLOADED)): + validate_route_states(duthost, vrf_type, check_point=OFFLOADED) + + with allure.step("Validate BGP ipv4 and ipv6 routes are announced to T2 VM peer"): + validate_route_propagate(duthost, tbinfo, vrf_type) + + with allure.step("Validate traffic would be forwarded to T0 VM"): + validate_traffic(ptfadapter, TRAFFIC_DATA_FORWARD, router_mac, ptf_interfaces, recv_port) + + finally: + if vrf_type == USER_DEFINED_VRF: + with allure.step("Clean user defined vrf"): + duthost.shell("cp -f /etc/sonic/config_db.json.bak /etc/sonic/config_db.json") + config_reload(duthost) + + +def test_bgp_route_without_suppress(duthost, tbinfo, nbrhosts, ptfadapter, get_exabgp_ptf_ports): + with allure.step("Prepare needed parameters"): + router_mac = duthost.facts["router_mac"] + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + ptf_ip = tbinfo['ptf_ip'] + exabgp_port, ptf_recv_port, exabgp_port_v6, ptf_recv_port_v6 = get_exabgp_ptf_ports + recv_port = { + 4: ptf_recv_port, + 6: ptf_recv_port_v6 + } + + with allure.step("Suspend orchagent process to simulate a route install delay"): + operate_orchagent(duthost) + + with allure.step("Announce BGP ipv4 and ipv6 routes to DUT from T0 VM by ExaBGP"): + announce_ipv4_ipv6_routes(ptf_ip, exabgp_port, exabgp_port_v6) + + with allure.step("Validate announced BGP ipv4 and ipv6 routes are not in {} state".format(QUEUED)): + validate_route_states(duthost, check_point=QUEUED, action=ACTION_NOT_IN) + + with allure.step("Validate BGP ipv4 and ipv6 routes are announced to T2 VM peer"): + validate_route_propagate(duthost, tbinfo) + + with allure.step("Restore orchagent process"): + operate_orchagent(duthost, action=ACTION_CONTINUE) + + with allure.step("Validate announced BGP ipv4 and ipv6 routes are in {} state".format(OFFLOADED)): + validate_route_states(duthost, check_point=OFFLOADED) + + with allure.step("Validate traffic would be forwarded to T0 VM"): + ptf_interfaces = get_t2_ptf_intfs(mg_facts) + validate_traffic(ptfadapter, TRAFFIC_DATA_FORWARD, router_mac, ptf_interfaces, recv_port) + + +def test_bgp_route_with_suppress_negative_operation(duthost, tbinfo, nbrhosts, ptfadapter, localhost, + restore_bgp_suppress_fib, get_exabgp_ptf_ports): + try: + with allure.step("Prepare needed parameters"): + router_mac = duthost.facts["router_mac"] + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + ptf_ip = tbinfo['ptf_ip'] + exabgp_port, ptf_recv_port, exabgp_port_v6, ptf_recv_port_v6 = get_exabgp_ptf_ports + recv_port = { + 4: ptf_recv_port, + 6: ptf_recv_port_v6 + } + + with allure.step("Config bgp suppress-fib-pending function"): + config_bgp_suppress_fib(duthost) + + with allure.step("Suspend orchagent process to simulate a route install delay"): + operate_orchagent(duthost) + + with allure.step("Announce BGP ipv4 and ipv6 routes to DUT from T0 VM by ExaBGP"): + announce_ipv4_ipv6_routes(ptf_ip, exabgp_port, exabgp_port_v6) + + with allure.step("Execute bgp sessions restart"): + restart_bgp_session(duthost) + + with allure.step("Validate bgp neighbor are established"): + config_facts = duthost.config_facts(host=duthost.hostname, source="running")['ansible_facts'] + bgp_neighbors = config_facts.get('BGP_NEIGHBOR', {}) + pytest_assert( + wait_until(300, 10, 0, duthost.check_bgp_session_state, bgp_neighbors), + "graceful restarted bgp sessions {} are not coming back".format(bgp_neighbors) + ) + + with allure.step("Validate announced BGP ipv4 and ipv6 routes are in {} state".format(QUEUED)): + validate_route_states(duthost) + + with allure.step("Validate BGP ipv4 and ipv6 routes are not announced to T2 VM peer"): + validate_route_propagate(duthost, tbinfo, exist=False) + + with allure.step("Config static route and redistribute to BGP"): + port = get_eth_port(duthost, tbinfo) + logger.info("Config static route - sudo config route add prefix {} nexthop dev {}". + format(STATIC_ROUTE_PREFIX, port)) + duthost.shell("sudo config route add prefix {} nexthop dev {}".format(STATIC_ROUTE_PREFIX, port)) + redistribute_static_route_to_bgp(duthost) + + with allure.step("Validate redistributed static route is propagate to T2 VM peer"): + t2_vm = get_vm_name(tbinfo) + bgp_neighbor_v4, _ = get_bgp_neighbor_ip(duthost, t2_vm) + validate_route_propagate_status(duthost, STATIC_ROUTE_PREFIX, bgp_neighbor_v4) + + with allure.step("Validate traffic could not be forwarded to T0 VM"): + ptf_interfaces = get_t2_ptf_intfs(mg_facts) + validate_traffic(ptfadapter, TRAFFIC_DATA_DROP, router_mac, ptf_interfaces, recv_port) + + with allure.step("Restore orchagent process"): + operate_orchagent(duthost, action=ACTION_CONTINUE) + + with allure.step("Validate announced BGP ipv4 and ipv6 routes are not in {} state".format(QUEUED)): + validate_route_states(duthost, check_point=QUEUED, action=ACTION_NOT_IN) + + with allure.step("Validate announced BGP ipv4 and ipv6 routes are in {} state".format(OFFLOADED)): + validate_route_states(duthost, check_point=OFFLOADED) + + with allure.step("Validate BGP ipv4 and ipv6 routes are announced to T2 VM peer"): + validate_route_propagate(duthost, tbinfo) + + with allure.step("Validate traffic would be forwarded to T0 VM"): + validate_traffic(ptfadapter, TRAFFIC_DATA_FORWARD, router_mac, ptf_interfaces, recv_port) + + finally: + with allure.step("Delete static route and remove redistribute to BGP"): + remove_static_route_and_redistribute(duthost) + + +def test_credit_loop(duthost, tbinfo, nbrhosts, ptfadapter, get_exabgp_ptf_ports, restore_bgp_suppress_fib): + """ + The problem with BGP programming occurs after the T1 switch is rebooted: + + First, the T1 FRR learns a default route from at least 1 T2 + The T0 advertises its prefixes to T1 + FRR advertises the prefixes to T2 without waiting for them to be programmed in the ASIC + T2 starts forwarding traffic for prefixes not yet programmed, according to T1 routing table, + T1 sends it back to a default route - same T2 + When the traffic is bounced back on lossless queue, buffers on both sides are overflown, credit loop happens + """ + with allure.step("Prepare needed parameters"): + router_mac = duthost.facts["router_mac"] + mg_facts = duthost.get_extended_minigraph_facts(tbinfo) + ptf_ip = tbinfo['ptf_ip'] + exabgp_port, ptf_recv_port, exabgp_port_v6, ptf_recv_port_v6 = get_exabgp_ptf_ports + recv_port = { + 4: ptf_recv_port, + 6: ptf_recv_port_v6 + } + + with allure.step("Suspend orchagent process to simulate a route install delay"): + operate_orchagent(duthost) + + with allure.step("Announce BGP ipv4 and ipv6 routes to DUT from T0 VM by ExaBGP"): + announce_ipv4_ipv6_routes(ptf_ip, exabgp_port, exabgp_port_v6) + + with allure.step("Validate the BGP routes are propagated to T2 VM"): + validate_route_propagate(duthost, tbinfo) + + with allure.step("Validate traffic is forwarded back to T2 VM"): + ptf_interfaces = get_t2_ptf_intfs(mg_facts) + validate_traffic(ptfadapter, TRAFFIC_DATA_FORWARD, router_mac, ptf_interfaces, ptf_interfaces, loop_back=True) + + with allure.step("Config bgp suppress-fib-pending function"): + config_bgp_suppress_fib(duthost) + + with allure.step("Restore orchagent process"): + operate_orchagent(duthost, action=ACTION_CONTINUE) + + with allure.step("Validate announced BGP ipv4 and ipv6 routes are in {} state".format(OFFLOADED)): + validate_route_states(duthost, check_point=OFFLOADED) + + with allure.step("Validate traffic would be forwarded to T0 VM"): + ptf_interfaces = get_t2_ptf_intfs(mg_facts) + validate_traffic(ptfadapter, TRAFFIC_DATA_FORWARD, router_mac, ptf_interfaces, recv_port) diff --git a/tests/bgp/vrf_config_db.j2 b/tests/bgp/vrf_config_db.j2 new file mode 100644 index 00000000000..b63845f835f --- /dev/null +++ b/tests/bgp/vrf_config_db.j2 @@ -0,0 +1,52 @@ +{ +{% for k in cfg_t1 %} +{% if k == 'BGP_NEIGHBOR' %} + "BGP_NEIGHBOR": { +{% for neigh in cfg_t1['BGP_NEIGHBOR'] | sort %} +{# to detect number of pcs, used multiplier 2, because each neigh have ipv4 and ipv6 key #} +{% if cfg_t1['BGP_NEIGHBOR'] | length == 48 %} +{% if cfg_t1['BGP_NEIGHBOR'][neigh]['name'] in vm_list %} + "Vrf1|{{ neigh }}": {{ cfg_t1['BGP_NEIGHBOR'][neigh] | to_nice_json | indent(width=8) }} +{%- else %} + "{{ neigh }}": {{ cfg_t1['BGP_NEIGHBOR'][neigh] | to_nice_json | indent(width=8) }} +{%- endif %} +{%- else %} + "{{ neigh }}": {{ cfg_t1['BGP_NEIGHBOR'][neigh] | to_nice_json | indent(width=8) }} +{%- endif %} +{%- if not loop.last %},{%endif %} + +{% endfor %} + }, +{% elif k == 'PORTCHANNEL_INTERFACE' %} + "PORTCHANNEL_INTERFACE": { +{% for pc in cfg_t1['PORTCHANNEL_INTERFACE'] | sort %} +{# each pc have 3 keys: pc, pc|ipv4 and pc|ipv6 #} +{% if pc in pc_list %} + "{{ pc }}": {"vrf_name": "Vrf1"} +{%- else %} + "{{ pc }}": {{ cfg_t1['PORTCHANNEL_INTERFACE'][pc] | to_nice_json | indent(width=8) }} +{%- endif %} +{%- if not loop.last %},{% endif %} + +{% endfor %} + }, +{% elif k == 'INTERFACE' %} + "INTERFACE": { +{% for eth in cfg_t1['INTERFACE'] | sort %} +{% if eth in port_list %} + "{{ eth }}": {"vrf_name": "Vrf1"} +{%- else %} + "{{ eth }}": {{ cfg_t1['INTERFACE'][eth] | to_nice_json | indent }} +{%- endif %} +{%- if not loop.last %},{% endif %} + +{% endfor %} + }, +{% else %} + "{{ k }}": {{ cfg_t1[k] | to_nice_json | indent}}, +{% endif %} +{% endfor %} + "VRF": { + "Vrf1": {} + } +}