#!/usr/bin/env python
# SPDX-License-Identifier: ISC

#
# test_bgp_features.py
# Part of NetDEF Topology Tests
#
# Copyright (c) 2020 by
# Network Device Education Foundation, Inc. ("NetDEF")
#

"""
test_bgp_features.py: Test various BGP features.
"""

import json
import functools
import os
import sys
import pytest
import time

# Save the Current Working Directory to find configuration files.
CWD = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(CWD, "../"))

# pylint: disable=C0413
# Import topogen and topotest helpers
from lib import topotest
from lib.topogen import Topogen, TopoRouter, get_topogen
from lib.topolog import logger

# Required to instantiate the topology builder class.

pytestmark = [pytest.mark.bgpd, pytest.mark.ospf6d, pytest.mark.ospfd]

#####################################################
#
#   Network Topology Definition
#
#####################################################


def build_topo(tgen):
    for rtrNum in range(1, 6):
        tgen.add_router("r{}".format(rtrNum))

    # create ExaBGP peers
    for peer_num in range(1, 5):
        tgen.add_exabgp_peer(
            "peer{}".format(peer_num),
            ip="192.168.101.{}".format(peer_num + 2),
            defaultRoute="via 192.168.101.1",
        )

    # Setup Switches and connections
    for swNum in range(1, 11):
        tgen.add_switch("sw{}".format(swNum))

    # Add connections to stub switches
    tgen.gears["r1"].add_link(tgen.gears["sw6"])
    tgen.gears["r2"].add_link(tgen.gears["sw7"])
    tgen.gears["r3"].add_link(tgen.gears["sw8"])
    tgen.gears["r4"].add_link(tgen.gears["sw9"])
    tgen.gears["r5"].add_link(tgen.gears["sw10"])

    # Add connections to R1-R2-R3 core
    tgen.gears["r1"].add_link(tgen.gears["sw1"])
    tgen.gears["r1"].add_link(tgen.gears["sw3"])
    tgen.gears["r2"].add_link(tgen.gears["sw1"])
    tgen.gears["r2"].add_link(tgen.gears["sw2"])
    tgen.gears["r3"].add_link(tgen.gears["sw2"])
    tgen.gears["r3"].add_link(tgen.gears["sw3"])

    # Add connections to external R4/R5 Routers
    tgen.gears["r1"].add_link(tgen.gears["sw4"])
    tgen.gears["r4"].add_link(tgen.gears["sw4"])
    tgen.gears["r2"].add_link(tgen.gears["sw5"])
    tgen.gears["r5"].add_link(tgen.gears["sw5"])

    # Add ExaBGP peers to sw4
    tgen.gears["peer1"].add_link(tgen.gears["sw4"])
    tgen.gears["peer2"].add_link(tgen.gears["sw4"])
    tgen.gears["peer3"].add_link(tgen.gears["sw4"])
    tgen.gears["peer4"].add_link(tgen.gears["sw4"])


#####################################################
#
#   Tests starting
#
#####################################################


def setup_module(module):
    tgen = Topogen(build_topo, module.__name__)
    tgen.start_topology()

    # Starting Routers
    router_list = tgen.routers()
    for rname, router in router_list.items():
        router.load_config(
            TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
        )
        if os.path.exists(os.path.join(CWD, "{}/bgpd.conf".format(rname))):
            logger.info("{} uses BGPd".format(rname))
            router.load_config(
                TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
            )
        if os.path.exists(os.path.join(CWD, "{}/ospfd.conf".format(rname))):
            logger.info("{} uses OSPFd".format(rname))
            router.load_config(
                TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname))
            )
        if os.path.exists(os.path.join(CWD, "{}/ospf6d.conf".format(rname))):
            logger.info("{} uses OSPF6d".format(rname))
            router.load_config(
                TopoRouter.RD_OSPF6, os.path.join(CWD, "{}/ospf6d.conf".format(rname))
            )
        router.start()


def teardown_module(module):
    tgen = get_topogen()
    tgen.stop_topology()


def test_ospf_convergence():
    "Test for OSPFv2 topology convergence"
    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    # Check Router r1, r2 & r3 OSPF
    for rtrNum in range(1, 4):
        logger.info("Checking OSPFv2 convergence on router r{}".format(rtrNum))

        router = tgen.gears["r{}".format(rtrNum)]
        reffile = os.path.join(CWD, "r{}/ospf_neighbor.json".format(rtrNum))
        expected = json.loads(open(reffile).read())

        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip ospf neighbor json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
        assertmsg = "OSPF router R{} did not converge".format(rtrNum)
        assert res is None, assertmsg


def test_bgp_convergence():
    "Test for BGP topology convergence"
    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    # Check Router r1 & r2 BGP
    for rtrNum in [1, 2, 4, 5]:
        logger.info("Checking BGP IPv4 convergence on router r{}".format(rtrNum))

        router = tgen.gears["r{}".format(rtrNum)]
        reffile = os.path.join(CWD, "r{}/bgp_summary.json".format(rtrNum))
        expected = json.loads(open(reffile).read())

        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
        assertmsg = "BGP router R{} did not converge".format(rtrNum)
        assert res is None, assertmsg

    # tgen.mininet_cli()


def test_bgp_neighbors_brief():
    "Test 'show bgp neighbors [<ip>] json brief' (brief is only valid with json)"
    tgen = get_topogen()
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    r1 = tgen.gears["r1"]
    neighbor_ip = "192.168.0.2"
    neighbor_ip2 = "192.168.101.2"

    # Text output remains the full neighbor display (no 'brief' keyword).
    logger.info(f"Checking 'show bgp neighbors {neighbor_ip}' text output")
    out = r1.vtysh_cmd(f"show bgp neighbors {neighbor_ip}")
    assert neighbor_ip in out, "full text output missing neighbor address"
    assert "BGP neighbor" in out, "full text output missing BGP neighbor header"

    def _check_brief_json_nbr(nbr):
        assert "hostname" in nbr, "brief json missing hostname"
        assert "remoteAs" in nbr, "brief json missing remoteAs"
        assert "localAs" in nbr, "brief json missing localAs"
        assert "messageStats" in nbr, "brief json missing messageStats"
        assert "totalSent" in nbr["messageStats"], "brief json missing totalSent"
        assert "totalRecv" in nbr["messageStats"], "brief json missing totalRecv"
        assert "addressFamilyInfo" in nbr, "brief json missing addressFamilyInfo"
        afi_info = nbr["addressFamilyInfo"]
        assert (
            isinstance(afi_info, dict) and len(afi_info) >= 1
        ), "brief json addressFamilyInfo should have at least one entry"
        for afi_obj in afi_info.values():
            assert (
                "acceptedPrefixCounter" in afi_obj
            ), "brief json AF entry missing acceptedPrefixCounter"
            assert (
                "sentPrefixCounter" in afi_obj
            ), "brief json AF entry missing sentPrefixCounter"

    # 1. Single neighbor JSON: show bgp neighbors <ip> json brief
    logger.info(f"Checking 'show bgp neighbors {neighbor_ip} json brief' structure")
    out_json = r1.vtysh_cmd(f"show bgp neighbors {neighbor_ip} json brief", isjson=True)
    assert neighbor_ip in out_json, "brief json missing neighbor key"
    _check_brief_json_nbr(out_json[neighbor_ip])

    # 2. All neighbors JSON: show bgp neighbors json brief
    logger.info("Checking 'show bgp neighbors json brief' structure (all neighbors)")
    out_json_all = r1.vtysh_cmd("show bgp neighbors json brief", isjson=True)
    assert (
        neighbor_ip in out_json_all and neighbor_ip2 in out_json_all
    ), "brief json (all) missing expected neighbor keys"
    _check_brief_json_nbr(out_json_all[neighbor_ip])
    _check_brief_json_nbr(out_json_all[neighbor_ip2])


def test_bgp_neighbors_established_failed():
    "Test 'show bgp neighbors json brief established' and 'json brief failed'"
    tgen = get_topogen()
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    r1 = tgen.gears["r1"]
    neighbor_est = "192.168.0.2"
    neighbor_fail = "192.168.101.2"

    def _check_brief_json_nbr(nbr):
        assert "hostname" in nbr, "brief json missing hostname"
        assert "remoteAs" in nbr, "brief json missing remoteAs"
        assert "localAs" in nbr, "brief json missing localAs"
        assert "messageStats" in nbr, "brief json missing messageStats"
        assert "addressFamilyInfo" in nbr, "brief json missing addressFamilyInfo"

    # --- All peers up: established = both, failed = none ---
    logger.info("Checking 'show bgp neighbors json brief established' (all peers up)")
    json_est = r1.vtysh_cmd("show bgp neighbors json brief established", isjson=True)
    assert (
        neighbor_est in json_est and neighbor_fail in json_est
    ), "established json should contain both neighbors when all up"
    _check_brief_json_nbr(json_est[neighbor_est])
    _check_brief_json_nbr(json_est[neighbor_fail])

    logger.info(
        "Checking 'show bgp neighbors json brief failed' (all peers up -> empty)"
    )
    json_fail = r1.vtysh_cmd("show bgp neighbors json brief failed", isjson=True)
    assert (
        neighbor_est not in json_fail and neighbor_fail not in json_fail
    ), "failed json should have no neighbors when all are established"

    # --- Shut one peer so we have one established, one failed ---
    logger.info(
        f"Shutting down neighbor {neighbor_fail} on r1 to test established/failed"
    )
    tgen.net["r1"].cmd(
        f'vtysh -c "conf t" -c "router bgp 65000" '
        f'-c "neighbor {neighbor_fail} shutdown"'
    )
    tgen.net["r4"].cmd(
        'vtysh -c "conf t" -c "router bgp 65100" '
        '-c "neighbor 192.168.101.1 shutdown"'
    )

    # Wait for peer to leave Established
    for _ in range(30):
        out_sum = r1.vtysh_cmd("show ip bgp summary json", isjson=True)
        peers = {}
        for key in out_sum:
            if isinstance(out_sum[key], dict) and "peers" in out_sum[key]:
                peers.update(out_sum[key]["peers"])
        if neighbor_fail in peers:
            state = peers[neighbor_fail].get("state", "") or peers[neighbor_fail].get(
                "stateStr", ""
            )
            if str(state).lower() != "established":
                break
        time.sleep(1)
    else:
        assert (
            False
        ), f"neighbor {neighbor_fail} did not leave Established state after shutdown"

    # --- established: only 192.168.0.2; failed: only 192.168.101.2 ---
    logger.info("Checking 'show bgp neighbors json brief established' (one peer down)")
    json_est = r1.vtysh_cmd("show bgp neighbors json brief established", isjson=True)
    assert neighbor_est in json_est, f"established json should contain {neighbor_est}"
    assert (
        neighbor_fail not in json_est
    ), "established json should not contain shut peer"
    _check_brief_json_nbr(json_est[neighbor_est])

    logger.info("Checking 'show bgp neighbors json brief failed' (one peer down)")
    json_fail = r1.vtysh_cmd("show bgp neighbors json brief failed", isjson=True)
    assert neighbor_fail in json_fail, "failed json should contain shut peer"
    assert (
        neighbor_est not in json_fail
    ), "failed json should not contain established peer"
    _check_brief_json_nbr(json_fail[neighbor_fail])

    # --- Restore neighbor ---
    logger.info(f"Restoring neighbor {neighbor_fail} on r1")
    tgen.net["r1"].cmd(
        f'vtysh -c "conf t" -c "router bgp 65000" '
        f'-c "no neighbor {neighbor_fail} shutdown"'
    )
    tgen.net["r4"].cmd(
        'vtysh -c "conf t" -c "router bgp 65100" '
        '-c "no neighbor 192.168.101.1 shutdown"'
    )

    # Wait for reconvergence
    test_func = functools.partial(
        topotest.router_json_cmp,
        r1,
        "show ip bgp summary json",
        json.loads(open(os.path.join(CWD, "r1/bgp_summary.json")).read()),
    )
    topotest.run_and_expect(test_func, None, count=60, wait=2)


def get_shut_msg_count(tgen):
    shuts = {}
    for rtrNum in [2, 4]:
        shutmsg = tgen.net["r{}".format(rtrNum)].cmd_nostatus(
            'grep -c "NOTIFICATION.*Cease/Administrative Shutdown" bgpd.log', warn=False
        )
        try:
            shuts[rtrNum] = int(shutmsg.strip())
        except ValueError:
            shuts[rtrNum] = 0
    return shuts


def test_bgp_shutdown():
    "Test BGP instance shutdown"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    shuts_before = get_shut_msg_count(tgen)

    tgen.net["r1"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "bgp shutdown message ABCDabcd"'
    )

    # Check BGP Summary on local and remote routers
    for rtrNum in [1, 2, 4]:
        logger.info(
            "Checking BGP Summary after shutdown of R1 BGP on router r{}".format(rtrNum)
        )

        router = tgen.gears["r{}".format(rtrNum)]
        reffile = os.path.join(CWD, "r{}/bgp_shutdown_summary.json".format(rtrNum))
        expected = json.loads(open(reffile).read())

        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
        assertmsg = "BGP sessions on router R{} are in incorrect state (not down as expected?)".format(
            rtrNum
        )
        assert res is None, assertmsg

    shuts_after = get_shut_msg_count(tgen)

    for k in shuts_before:
        assert shuts_before[k] + 1 == shuts_after[k]


def test_bgp_shutdown_message():
    "Test BGP Peer Shutdown Message"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    for rtrNum in [2, 4]:
        logger.info("Checking BGP shutdown received on router r{}".format(rtrNum))

        shut_message = tgen.net["r{}".format(rtrNum)].cmd(
            'grep -e "NOTIFICATION.*Cease/Administrative Shutdown.*ABCDabcd" bgpd.log'
        )
        assertmsg = "BGP shutdown message not received on router R{}".format(rtrNum)
        assert shut_message != "", assertmsg


def test_bgp_no_shutdown():
    "Test BGP instance no shutdown"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    tgen.net["r1"].cmd('vtysh -c "conf t" -c "router bgp 65000" -c "no bgp shutdown"')

    # Check BGP Summary on local and remote routers
    for rtrNum in [1, 2, 4]:
        logger.info(
            "Checking BGP Summary after removing bgp shutdown on router r1 on router r{}".format(
                rtrNum
            )
        )

        router = tgen.gears["r{}".format(rtrNum)]
        reffile = os.path.join(CWD, "r{}/bgp_summary.json".format(rtrNum))
        expected = json.loads(open(reffile).read())

        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
        assertmsg = "BGP sessions on router R{} are in incorrect state (not down as expected?)".format(
            rtrNum
        )
        assert res is None, assertmsg


def test_bgp_metric_config():
    "Test BGP Changing metric values in route-maps"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    logger.info("Configuring bgp route-maps on router r1 and r2 to update metric")

    # # Adding the following configuration to r1:
    # router bgp 65000
    #  address-family ipv4 unicast
    #   neighbor 192.168.0.2 route-map addmetric-in in
    #   neighbor 192.168.0.2 route-map addmetric-out out
    #   neighbor 192.168.101.2 route-map setmetric-in in
    #   neighbor 192.168.101.2 route-map setmetric-out out
    #  exit-address-family
    # !
    # ip prefix-list net1 seq 10 permit 192.168.101.0/24
    # ip prefix-list net2 seq 20 permit 192.168.1.0/24
    # !
    # route-map setmetric-in permit 10
    #  match ip address prefix-list net1
    #  set metric 111
    # !
    # route-map setmetric-in permit 20
    # !
    # route-map setmetric-out permit 10
    #  match ip address prefix-list net2
    #  set metric 1011
    # !
    # route-map setmetric-out permit 20
    # !
    # route-map addmetric-in permit 10
    #  set metric +11
    # !
    # route-map addmetric-out permit 10
    #  set metric +12
    # !

    tgen.net["r1"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" '
        + '-c "address-family ipv4 unicast" '
        + '-c "neighbor 192.168.0.2 route-map addmetric-in in" '
        + '-c "neighbor 192.168.0.2 route-map addmetric-out out" '
        + '-c "neighbor 192.168.101.2 route-map setmetric-in in" '
        + '-c "neighbor 192.168.101.2 route-map setmetric-out out" '
    )
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" '
        + '-c "ip prefix-list net1 seq 10 permit 192.168.101.0/24" '
        + '-c "ip prefix-list net2 seq 20 permit 192.168.1.0/24"'
    )
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" '
        + '-c "route-map setmetric-in permit 10" '
        + '-c "match ip address prefix-list net1" '
        + '-c "set metric 111" '
        + '-c "route-map setmetric-in permit 20"'
    )
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" '
        + '-c "route-map setmetric-out permit 10" '
        + '-c "match ip address prefix-list net2" '
        + '-c "set metric 1011" '
        + '-c "route-map setmetric-out permit 20"'
    )
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" '
        + '-c "route-map addmetric-in permit 10" '
        + '-c "set metric +11"'
    )
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" '
        + '-c "route-map addmetric-out permit 10" '
        + '-c "set metric +12"'
    )

    # # Adding the following configuration to r2:
    # router bgp 65000
    #  address-family ipv4 unicast
    # neighbor 192.168.0.1 route-map subtractmetric-in in
    # neighbor 192.168.0.1 route-map subtractmetric-out out
    # neighbor 192.168.201.2 route-map setmetric-in in
    # neighbor 192.168.201.2 route-map setmetric-out out
    #  exit-address-family
    # !
    # ip prefix-list net1 seq 10 permit 192.168.201.0/24
    # ip prefix-list net2 seq 20 permit 192.168.2.0/24
    # !
    # route-map setmetric-in permit 10
    #  match ip address prefix-list net1
    #  set metric 222
    # !
    # route-map setmetric-in permit 20
    # !
    # route-map setmetric-out permit 10
    #  match ip address prefix-list net2
    #  set metric 2022
    # !
    # route-map setmetric-out permit 20
    # !
    # route-map subtractmetric-in permit 10
    #  set metric -22
    # !
    # route-map subtractmetric-out permit 10
    #  set metric -23
    # !

    tgen.net["r2"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" '
        + '-c "address-family ipv4 unicast" '
        + '-c "neighbor 192.168.0.1 route-map subtractmetric-in in" '
        + '-c "neighbor 192.168.0.1 route-map subtractmetric-out out" '
        + '-c "neighbor 192.168.201.2 route-map setmetric-in in" '
        + '-c "neighbor 192.168.201.2 route-map setmetric-out out" '
    )
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" '
        + '-c "ip prefix-list net1 seq 10 permit 192.168.201.0/24" '
        + '-c "ip prefix-list net2 seq 20 permit 192.168.2.0/24" '
    )
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" '
        + '-c "route-map setmetric-in permit 10" '
        + '-c "match ip address prefix-list net1" '
        + '-c "set metric 222" '
        + '-c "route-map setmetric-in permit 20"'
    )
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" '
        + '-c "route-map setmetric-out permit 10" '
        + '-c "match ip address prefix-list net2" '
        + '-c "set metric 2022" '
        + '-c "route-map setmetric-out permit 20"'
    )
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" '
        + '-c "route-map subtractmetric-in permit 10" '
        + '-c "set metric -22"'
    )
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" '
        + '-c "route-map subtractmetric-out permit 10" '
        + '-c "set metric -23"'
    )

    # Clear IN the bgp neighbors to make sure the route-maps are applied
    tgen.net["r1"].cmd(
        'vtysh -c "clear ip bgp 192.168.0.2 in" ' + '-c "clear ip bgp 192.168.101.2 in"'
    )
    tgen.net["r2"].cmd(
        'vtysh -c "clear ip bgp 192.168.0.1 in" ' + '-c "clear ip bgp 192.168.201.2 in"'
    )

    # tgen.mininet_cli()

    # Checking BGP config - should show the bgp metric settings in the route-maps
    logger.info("Checking BGP configuration for correct 'set metric' values")

    setmetric111 = (
        tgen.net["r1"].cmd('vtysh -c "show running" | grep "^ set metric 111"').rstrip()
    )
    assertmsg = (
        "'set metric 111' configuration applied to R1, but not visible in configuration"
    )
    assert setmetric111 == " set metric 111", assertmsg

    setmetric222 = (
        tgen.net["r2"].cmd('vtysh -c "show running" | grep "^ set metric 222"').rstrip()
    )
    assertmsg = (
        "'set metric 222' configuration applied to R2, but not visible in configuration"
    )
    assert setmetric222 == " set metric 222", assertmsg


def test_bgp_metric_add_config():
    "Test BGP Changing metric values in route-maps"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    logger.info("Checking BGP configuration for correct 'set metric' ADD value")

    setmetricP11 = (
        tgen.net["r1"].cmd('vtysh -c "show running" | grep "^ set metric +11"').rstrip()
    )
    assertmsg = (
        "'set metric +11' configuration applied to R1, but not visible in configuration"
    )
    assert setmetricP11 == " set metric +11", assertmsg


def test_bgp_metric_subtract_config():
    "Test BGP Changing metric values in route-maps"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    logger.info("Checking BGP configuration for correct 'set metric' SUBTRACT value")

    setmetricM22 = (
        tgen.net["r2"].cmd('vtysh -c "show running" | grep "^ set metric -22"').rstrip()
    )
    assertmsg = (
        "'set metric -22' configuration applied to R2, but not visible in configuration"
    )
    assert setmetricM22 == " set metric -22", assertmsg


def test_bgp_set_metric():
    "Test setting metrics"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    logger.info("Test absolute metric")

    # Check BGP Summary on local and remote routers
    for rtrNum in [1, 2, 4, 5]:
        logger.info("Checking metrics of BGP router on r{}".format(rtrNum))

        router = tgen.gears["r{}".format(rtrNum)]
        reffile = os.path.join(CWD, "r{}/show_bgp_metric_test.json".format(rtrNum))
        expected = json.loads(open(reffile).read())

        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
        assertmsg = "BGP metrics on router r{} wrong".format(rtrNum)
        assert res is None, assertmsg


def test_bgp_remove_metric_rmaps():
    "Test removing route-maps with metric changes again"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    logger.info("Test absolute metric")

    # Remove metric route-maps and relevant comfiguration

    tgen.net["r1"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" '
        + '-c "address-family ipv4 unicast" '
        + '-c "no neighbor 192.168.0.2 route-map addmetric-in in" '
        + '-c "no neighbor 192.168.0.2 route-map addmetric-out out" '
        + '-c "no neighbor 192.168.101.2 route-map setmetric-in in" '
        + '-c "no neighbor 192.168.101.2 route-map setmetric-out out" '
    )
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" '
        + '-c "no ip prefix-list net1" '
        + '-c "no ip prefix-list net2"'
    )
    tgen.net["r1"].cmd('vtysh -c "conf t" ' + '-c "no route-map setmetric-in" ')
    tgen.net["r1"].cmd('vtysh -c "conf t" ' + '-c "no route-map setmetric-out" ')
    tgen.net["r1"].cmd('vtysh -c "conf t" ' + '-c "no route-map addmetric-in" ')
    tgen.net["r1"].cmd('vtysh -c "conf t" ' + '-c "no route-map addmetric-out" ')

    tgen.net["r2"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" '
        + '-c "address-family ipv4 unicast" '
        + '-c "no neighbor 192.168.0.1 route-map subtractmetric-in in" '
        + '-c "no neighbor 192.168.0.1 route-map subtractmetric-out out" '
        + '-c "no neighbor 192.168.201.2 route-map setmetric-in in" '
        + '-c "no neighbor 192.168.201.2 route-map setmetric-out out" '
    )
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" '
        + '-c "no ip prefix-list net1" '
        + '-c "no ip prefix-list net2" '
    )
    tgen.net["r2"].cmd('vtysh -c "conf t" ' + '-c "no route-map setmetric-in" ')
    tgen.net["r2"].cmd('vtysh -c "conf t" ' + '-c "no route-map setmetric-out" ')
    tgen.net["r2"].cmd('vtysh -c "conf t" ' + '-c "no route-map addmetric-in" ')
    tgen.net["r2"].cmd('vtysh -c "conf t" ' + '-c "no route-map addmetric-out" ')

    # Clear IN the bgp neighbors to make sure the route-maps are applied
    tgen.net["r1"].cmd(
        'vtysh -c "clear ip bgp 192.168.0.2 in" ' + '-c "clear ip bgp 192.168.101.2 in"'
    )
    tgen.net["r2"].cmd(
        'vtysh -c "clear ip bgp 192.168.0.1 in" ' + '-c "clear ip bgp 192.168.201.2 in"'
    )

    # tgen.mininet_cli()

    # Check BGP Summary on local and remote routers
    for rtrNum in [1, 2]:
        logger.info("Checking metrics of BGP router on r{}".format(rtrNum))

        router = tgen.gears["r{}".format(rtrNum)]
        reffile = os.path.join(CWD, "r{}/show_bgp.json".format(rtrNum))
        expected = json.loads(open(reffile).read())

        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
        assertmsg = "BGP routes on router r{} are wrong after removing metric route-maps".format(
            rtrNum
        )
        assert res is None, assertmsg


def test_bgp_norib():
    "Test BGP disable RIB (Zebra) Route Install"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    logger.info("Configuring 'bgp no-rib' on router r1")

    tgen.net["r1"].cmd('vtysh -c "conf t" -c "bgp no-rib"')

    # Checking BGP config - should show the "bgp no-rib" under the router bgp section
    logger.info("Checking BGP configuration for 'bgp no-rib'")

    norib_cfg = (
        tgen.net["r1"].cmd('vtysh -c "show running" | grep "^bgp no-rib"').rstrip()
    )

    assertmsg = "'bgp no-rib' configuration applied, but not visible in configuration"
    assert norib_cfg == "bgp no-rib", assertmsg


def test_bgp_norib_routes():
    "Test Routes in Zebra and BGP with the 'bgp-norib' configuration"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    # Checking local BGP routes - they need to be gone from Zebra
    logger.info("Checking Zebra routes after removing bgp shutdown on router r1")

    router = tgen.gears["r1"]
    reffile = os.path.join(CWD, "r1/ip_route_norib.json")
    expected = json.loads(open(reffile).read())

    test_func = functools.partial(
        topotest.router_json_cmp, router, "show ip route json", expected
    )
    _, res = topotest.run_and_expect(test_func, None, count=30, wait=2)
    assertmsg = "Zebra IPv4 Routes after configuring 'bgp no-rib' (There should be no BGP routes in Zebra anymore)"
    assert res is None, assertmsg

    # Check BGP Summary on local and remote routers
    for rtrNum in [1, 2, 4]:
        logger.info(
            "Checking BGP Summary after 'bgp no-rib' on router r1 on router r{}".format(
                rtrNum
            )
        )

        router = tgen.gears["r{}".format(rtrNum)]
        reffile = os.path.join(CWD, "r{}/bgp_summary.json".format(rtrNum))
        expected = json.loads(open(reffile).read())

        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=30, wait=2)
        assertmsg = "BGP sessions on router R{} has incorrect routes after adding 'bgp no-rib on r1'".format(
            rtrNum
        )
        assert res is None, assertmsg

    # tgen.mininet_cli()


def test_bgp_disable_norib():
    "Test BGP disabling the no-RIB (Zebra) Route Install"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    logger.info("Configuring 'no bgp no-rib' on router r1")

    tgen.net["r1"].cmd('vtysh -c "conf t" -c "no bgp no-rib"')

    # Checking BGP config - should show the "bgp no-rib" under the router bgp section
    logger.info("Checking BGP configuration for 'bgp no-rib'")

    norib_cfg = (
        tgen.net["r1"].cmd('vtysh -c "show running" | grep "^ bgp no-rib"').rstrip()
    )

    assertmsg = (
        "'no bgp no-rib'configuration applied, but still visible in configuration"
    )
    assert norib_cfg == "", assertmsg


def test_bgp_disable_norib_routes():
    "Test Routes in Zebra and BGP with the 'bgp-norib' configuration"

    tgen = get_topogen()

    # Skip if previous fatal error condition is raised
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    # Checking local BGP routes - they need to be gone from Zebra
    logger.info("Checking Zebra routes after removing bgp shutdown on router r1")

    router = tgen.gears["r1"]
    reffile = os.path.join(CWD, "r1/ip_route.json")
    expected = json.loads(open(reffile).read())

    test_func = functools.partial(
        topotest.router_json_cmp, router, "show ip route json", expected
    )
    _, res = topotest.run_and_expect(test_func, None, count=30, wait=2)
    assertmsg = "Zebra IPv4 Routes wrong after removing the 'bgp no-rib'"
    assert res is None, assertmsg

    # Check BGP Summary on local and remote routers
    for rtrNum in [1, 2, 4]:
        logger.info(
            "Checking BGP Summary after removing the 'bgp no-rib' on router r1 on router r{}".format(
                rtrNum
            )
        )

        router = tgen.gears["r{}".format(rtrNum)]
        reffile = os.path.join(CWD, "r{}/bgp_summary.json".format(rtrNum))
        expected = json.loads(open(reffile).read())

        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=30, wait=2)
        assertmsg = "BGP sessions on router R{} has incorrect routes after removing 'bgp no-rib on r1'".format(
            rtrNum
        )
        assert res is None, assertmsg

    # tgen.mininet_cli()


def test_bgp_delayopen_without():
    "Optional test of BGP functionality and behaviour without DelayOpenTimer enabled to establish a reference for following tests"
    tgen = get_topogen()
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    # part 1: no delay r1 <=> no delay r4
    logger.info(
        "Starting optional test of BGP functionality without DelayOpenTimer enabled to establish a reference for following tests"
    )

    # 1.1 enable peering shutdown
    logger.info("Enable shutdown of peering between r1 and r4")
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.101.2 shutdown"'
    )
    tgen.net["r4"].cmd(
        'vtysh -c "conf t" -c "router bgp 65100" -c "neighbor 192.168.101.1 shutdown"'
    )

    # 1.2 wait for peers to shut down (poll output)
    for router_num in [1, 4]:
        logger.info(
            "Checking BGP summary after enabling shutdown of peering on r{}".format(
                router_num
            )
        )
        router = tgen.gears["r{}".format(router_num)]
        reffile = os.path.join(
            CWD, "r{}/bgp_delayopen_summary_shutdown.json".format(router_num)
        )
        expected = json.loads(open(reffile).read())
        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=5, wait=1)
        assertmsg = "BGP session on r{} did not shut down peer".format(router_num)
        assert res is None, assertmsg

    # 1.3 disable peering shutdown
    logger.info("Disable shutdown of peering between r1 and r4")
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.101.2 shutdown"'
    )
    tgen.net["r4"].cmd(
        'vtysh -c "conf t" -c "router bgp 65100" -c "no neighbor 192.168.101.1 shutdown"'
    )

    # 1.4 wait for peers to establish connection (poll output)
    for router_num in [1, 4]:
        logger.info(
            "Checking BGP summary after disabling shutdown of peering on r{}".format(
                router_num
            )
        )
        router = tgen.gears["r{}".format(router_num)]
        reffile = os.path.join(
            CWD, "r{}/bgp_delayopen_summary_established.json".format(router_num)
        )
        expected = json.loads(open(reffile).read())
        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=5, wait=1)
        assertmsg = (
            "BGP session on r{} did not establish a connection with peer".format(
                router_num
            )
        )
        assert res is None, assertmsg

    # tgen.mininet_cli()

    # end test_bgp_delayopen_without


def test_bgp_delayopen_singular():
    "Test of BGP functionality and behaviour with DelayOpenTimer enabled on one side of the peering"

    tgen = get_topogen()
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    # part 2: delay 240s r1 <=> no delay r4
    logger.info(
        "Starting test of BGP functionality and behaviour with DelayOpenTimer enabled on one side of the peering"
    )

    # 2.1 enable peering shutdown
    logger.info("Enable shutdown of peering between r1 and r4")
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.101.2 shutdown"'
    )
    tgen.net["r4"].cmd(
        'vtysh -c "conf t" -c "router bgp 65100" -c "neighbor 192.168.101.1 shutdown"'
    )

    # 2.2 wait for peers to shut down (poll output)
    for router_num in [1, 4]:
        logger.info(
            "Checking BGP summary after disabling shutdown of peering on r{}".format(
                router_num
            )
        )
        router = tgen.gears["r{}".format(router_num)]
        reffile = os.path.join(
            CWD, "r{}/bgp_delayopen_summary_shutdown.json".format(router_num)
        )
        expected = json.loads(open(reffile).read())
        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=5, wait=1)
        assertmsg = "BGP session on r{} did not shut down peer".format(router_num)
        assert res is None, assertmsg

    # 2.3 set delayopen on R1 to 240
    logger.info("Setting DelayOpenTime for neighbor r4 to 240 seconds on r1")
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.101.2 timers delayopen 240"'
    )

    # 2.4 check config (poll output)
    logger.info("Checking BGP neighbor configuration after setting DelayOpenTime on r1")
    router = tgen.gears["r1"]
    reffile = os.path.join(CWD, "r1/bgp_delayopen_neighbor.json")
    expected = json.loads(open(reffile).read())
    test_func = functools.partial(
        topotest.router_json_cmp, router, "show bgp neighbors json", expected
    )
    _, res = topotest.run_and_expect(test_func, None, count=5, wait=1)
    assertmsg = "BGP session on r1 failed to set DelayOpenTime for r4"
    assert res is None, assertmsg

    # 2.5 disable peering shutdown
    logger.info("Disable shutdown of peering between r1 and r4")
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.101.2 shutdown"'
    )
    tgen.net["r4"].cmd(
        'vtysh -c "conf t" -c "router bgp 65100" -c "no neighbor 192.168.101.1 shutdown"'
    )

    # 2.6 wait for peers to establish connection (poll output)
    for router_num in [1, 4]:
        logger.info(
            "Checking BGP summary after disabling shutdown of peering on r{}".format(
                router_num
            )
        )
        router = tgen.gears["r{}".format(router_num)]
        reffile = os.path.join(
            CWD, "r{}/bgp_delayopen_summary_established.json".format(router_num)
        )
        expected = json.loads(open(reffile).read())
        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=5, wait=1)
        assertmsg = (
            "BGP session on r{} did not establish a connection with peer".format(
                router_num
            )
        )
        assert res is None, assertmsg

    # 2.7 unset delayopen on R1
    logger.info("Disabling DelayOpenTimer for neighbor r4 on r1")
    tgen.net["r1"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.101.2 timers delayopen"'
    )

    # 2.8 check config (poll output)
    logger.info(
        "Checking BGP neighbor configuration after disabling DelayOpenTimer on r1"
    )
    delayopen_cfg = (
        tgen.net["r1"]
        .cmd('vtysh -c "show bgp neighbors json" | grep "DelayOpenTimeMsecs"')
        .rstrip()
    )
    assertmsg = "BGP session on r1 failed disable DelayOpenTimer for peer r4"
    assert delayopen_cfg == "", assertmsg

    # tgen.mininet_cli()

    # end test_bgp_delayopen_singular


def test_bgp_delayopen_dual():
    "Test of BGP functionality and behaviour with DelayOpenTimer enabled on both sides of the peering with different timer intervals"
    tgen = get_topogen()
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    # part 3: delay 60s R2 <=> delay 30s R5
    logger.info(
        "Starting test of BGP functionality and behaviour with DelayOpenTimer enabled on both sides of the peering with different timer intervals"
    )

    # 3.1 enable peering shutdown
    logger.info("Enable shutdown of peering between r2 and r5")
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.201.2 shutdown"'
    )
    tgen.net["r5"].cmd(
        'vtysh -c "conf t" -c "router bgp 65200" -c "neighbor 192.168.201.1 shutdown"'
    )

    # 3.2 wait for peers to shut down (pool output)
    for router_num in [2, 5]:
        logger.info(
            "Checking BGP summary after disabling shutdown of peering on r{}".format(
                router_num
            )
        )
        router = tgen.gears["r{}".format(router_num)]
        reffile = os.path.join(
            CWD, "r{}/bgp_delayopen_summary_shutdown.json".format(router_num)
        )
        expected = json.loads(open(reffile).read())
        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=5, wait=1)
        assertmsg = "BGP session on r{} did not shut down peer".format(router_num)
        assert res is None, assertmsg

    # 3.3 set delayopen on R2 to 60s and on R5 to 30s
    logger.info("Setting DelayOpenTime for neighbor r5 to 60 seconds on r2")
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.201.2 timers delayopen 60"'
    )
    logger.info("Setting DelayOpenTime for neighbor r2 to 30 seconds on r5")
    tgen.net["r5"].cmd(
        'vtysh -c "conf t" -c "router bgp 65200" -c "neighbor 192.168.201.1 timers delayopen 30"'
    )

    # 3.4 check config (poll output)
    for router_num in [2, 5]:
        logger.info(
            "Checking BGP neighbor configuration after setting DelayOpenTime on r{}i".format(
                router_num
            )
        )
        router = tgen.gears["r{}".format(router_num)]
        reffile = os.path.join(
            CWD, "r{}/bgp_delayopen_neighbor.json".format(router_num)
        )
        expected = json.loads(open(reffile).read())
        test_func = functools.partial(
            topotest.router_json_cmp, router, "show bgp neighbors json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=5, wait=1)
        assertmsg = "BGP session on r{} failed to set DelayOpenTime".format(router_num)
        assert res is None, assertmsg

    ## 3.5 disable peering shutdown
    logger.info("Disable shutdown of peering between r2 and r5")
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.201.2 shutdown"'
    )
    tgen.net["r5"].cmd(
        'vtysh -c "conf t" -c "router bgp 65200" -c "no neighbor 192.168.201.1 shutdown"'
    )

    ## 3.6 wait for peers to reach connect or active state (poll output)
    delay_start = int(time.time())
    for router_num in [2, 5]:
        logger.info(
            "Checking BGP summary after disabling shutdown of peering on r{}".format(
                router_num
            )
        )
        router = tgen.gears["r{}".format(router_num)]
        reffile = os.path.join(
            CWD, "r{}/bgp_delayopen_summary_connect.json".format(router_num)
        )
        expected = json.loads(open(reffile).read())
        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=5, wait=1)
        assertmsg = "BGP session on r{} did not enter Connect state with peer".format(
            router_num
        )
        assert res is None, assertmsg

    ## 3.7 wait for peers to establish connection (poll output)
    for router_num in [2, 5]:
        logger.info(
            "Checking BGP summary after disabling shutdown of peering on r{}".format(
                router_num
            )
        )
        router = tgen.gears["r{}".format(router_num)]
        reffile = os.path.join(
            CWD, "r{}/bgp_delayopen_summary_established.json".format(router_num)
        )
        expected = json.loads(open(reffile).read())
        test_func = functools.partial(
            topotest.router_json_cmp, router, "show ip bgp summary json", expected
        )
        _, res = topotest.run_and_expect(test_func, None, count=35, wait=1)
        assertmsg = (
            "BGP session on r{} did not establish a connection with peer".format(
                router_num
            )
        )
        assert res is None, assertmsg

    delay_stop = int(time.time())
    assertmsg = "BGP peering between r2 and r5 was established before DelayOpenTimer (30sec) on r2 could expire"
    assert (delay_stop - delay_start) >= 30, assertmsg

    # 3.8 unset delayopen on R2 and R5
    logger.info("Disabling DelayOpenTimer for neighbor r5 on r2")
    tgen.net["r2"].cmd(
        'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.201.2 timers delayopen"'
    )
    logger.info("Disabling DelayOpenTimer for neighbor r2 on r5")
    tgen.net["r5"].cmd(
        'vtysh -c "conf t" -c "router bgp 65200" -c "no neighbor 192.168.201.1 timers delayopen"'
    )

    # 3.9 check config (poll output)
    for router_num in [2, 5]:
        logger.info(
            "Checking BGP neighbor configuration after disabling DelayOpenTimer on r{}".format(
                router_num
            )
        )
        delayopen_cfg = (
            tgen.net["r{}".format(router_num)]
            .cmd('vtysh -c "show bgp neighbors json" | grep "DelayOpenTimeMsecs"')
            .rstrip()
        )
        assertmsg = "BGP session on r{} failed disable DelayOpenTimer".format(
            router_num
        )
        assert delayopen_cfg == "", assertmsg

    # tgen.mininet_cli()

    # end test_bgp_delayopen_dual


if __name__ == "__main__":
    args = ["-s"] + sys.argv[1:]
    sys.exit(pytest.main(args))
