From: nguggarigoud Date: Fri, 29 Oct 2021 11:14:33 +0000 (-0700) Subject: tests: Add topotests for MGMT daemon X-Git-Tag: base_9.0~274^2~7 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=f637ac01705537927f81cafa854ca0d0557bb2d1;p=matthieu%2Ffrr.git tests: Add topotests for MGMT daemon 1. MGMT daemon support in topotests. 2. Sanity tests for MGMTd. Signed-off-by: nguggarigoud --- diff --git a/tests/topotests/all_protocol_startup/test_all_protocol_startup.py b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py index fe84d496ac..f7c3a4c19d 100644 --- a/tests/topotests/all_protocol_startup/test_all_protocol_startup.py +++ b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py @@ -82,6 +82,7 @@ def setup_module(module): # # Main router for i in range(1, 2): + net["r%s" % i].loadConf("mgmtd", "%s/r%s/zebra.conf" % (thisDir, i)) net["r%s" % i].loadConf("zebra", "%s/r%s/zebra.conf" % (thisDir, i)) net["r%s" % i].loadConf("ripd", "%s/r%s/ripd.conf" % (thisDir, i)) net["r%s" % i].loadConf("ripngd", "%s/r%s/ripngd.conf" % (thisDir, i)) diff --git a/tests/topotests/bfd_topo2/test_bfd_topo2.py b/tests/topotests/bfd_topo2/test_bfd_topo2.py index b720218e9b..636dbf354d 100644 --- a/tests/topotests/bfd_topo2/test_bfd_topo2.py +++ b/tests/topotests/bfd_topo2/test_bfd_topo2.py @@ -45,6 +45,7 @@ def setup_module(mod): router_list = tgen.routers() for rname, router in router_list.items(): + daemon_file = "{}/{}/zebra.conf".format(CWD, rname) router.load_config(TopoRouter.RD_ZEBRA, daemon_file) diff --git a/tests/topotests/config_timing/test_config_timing.py b/tests/topotests/config_timing/test_config_timing.py index fec9d485b5..f196deacdd 100644 --- a/tests/topotests/config_timing/test_config_timing.py +++ b/tests/topotests/config_timing/test_config_timing.py @@ -24,7 +24,7 @@ import math import os import sys import pytest - +from lib import topotest CWD = os.path.dirname(os.path.realpath(__file__)) sys.path.append(os.path.join(CWD, "../")) @@ -109,7 +109,9 @@ def test_static_timing(): router.logdir, rname, "{}-routes-{}.conf".format(iptype.lower(), optype) ) with open(config_file, "w") as f: - for i, net in enumerate(get_ip_networks(super_prefix, base_count, count)): + for i, net in enumerate( + get_ip_networks(super_prefix, base_count, count) + ): if i in bad_indices: if add: f.write("ip route {} {} bad_input\n".format(net, via)) @@ -164,20 +166,49 @@ def test_static_timing(): [u"2100:1111:2220::/44", u"2100:3333:4440::/44"], ] + # This apparently needed to allow for various mgmtd/staticd/zebra connections to form + # which then SLOWS execution down. If we don't include this value then the + # initial, baseline establishing, time is 2 time faster (e.g., 5s instead of 10s), + # but all later runs are slower and fail. + # + # This should be done differently based on actual facts. + topotest.sleep(5) + bad_indices = [] for ipv6 in [False, True]: base_delta = do_config( - prefix_count, prefix_count, bad_indices, 0, 0, True, ipv6, prefix_base[ipv6][0] + prefix_count, + prefix_count, + bad_indices, + 0, + 0, + True, + ipv6, + prefix_base[ipv6][0], ) # Another set of same number of prefixes do_config( - prefix_count, prefix_count, bad_indices, base_delta, 3, True, ipv6, prefix_base[ipv6][1] + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + True, + ipv6, + prefix_base[ipv6][1], ) # Duplicate config do_config( - prefix_count, prefix_count, bad_indices, base_delta, 3, True, ipv6, prefix_base[ipv6][0] + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + True, + ipv6, + prefix_base[ipv6][0], ) # Remove 1/2 of duplicate @@ -194,15 +225,36 @@ def test_static_timing(): # Add all back in so 1/2 replicate 1/2 new do_config( - prefix_count, prefix_count, bad_indices, base_delta, 3, True, ipv6, prefix_base[ipv6][0] + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + True, + ipv6, + prefix_base[ipv6][0], ) # remove all delta = do_config( - prefix_count, prefix_count, bad_indices, base_delta, 3, False, ipv6, prefix_base[ipv6][0] + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + False, + ipv6, + prefix_base[ipv6][0], ) delta += do_config( - prefix_count, prefix_count, bad_indices, base_delta, 3, False, ipv6, prefix_base[ipv6][1] + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + False, + ipv6, + prefix_base[ipv6][1], ) diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py index 3e02769d87..e5a1e75837 100644 --- a/tests/topotests/lib/common_config.py +++ b/tests/topotests/lib/common_config.py @@ -110,6 +110,7 @@ DEBUG_LOGS = { "debug zebra vxlan", "debug zebra nht", ], + "mgmt": [], "ospf": [ "debug ospf event", "debug ospf ism", @@ -450,6 +451,8 @@ def check_router_status(tgen): result = rnode.check_router_running() if result != "": daemons = [] + if "mgmtd" in result: + daemons.append("mgmtd") if "bgpd" in result: daemons.append("bgpd") if "zebra" in result: @@ -1047,6 +1050,11 @@ def start_topology(tgen): feature.add("ospf6") break + # Loading empty mgmtd.conf file to router, to start the mgmtd daemon + router.load_config( + TopoRouter.RD_MGMTD, "{}/{}/mgmtd.conf".format(tgen.logdir, rname) + ) + # Loading empty zebra.conf file to router, to start the zebra deamon router.load_config( TopoRouter.RD_ZEBRA, "{}/{}/zebra.conf".format(tgen.logdir, rname) @@ -2590,7 +2598,7 @@ def create_route_maps(tgen, input_dict, build=False): nexthop = set_data.setdefault("nexthop", None) origin = set_data.setdefault("origin", None) ext_comm_list = set_data.setdefault("extcommunity", {}) - metrictype = set_data.setdefault("metric-type", {}) + metrictype = set_data.setdefault("metric-type", None) # Local Preference if local_preference: diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index 16d89f079a..41da660b7d 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -485,7 +485,7 @@ class Topogen(object): memleak_file = os.environ.get("TOPOTESTS_CHECK_MEMLEAK") or self.config.get( self.CONFIG_SECTION, "memleak_path" ) - if memleak_file == "" or memleak_file == None: + if memleak_file == "" or memleak_file is None: return False return True @@ -713,6 +713,7 @@ class TopoRouter(TopoGear): RD_PATH = 17 RD_SNMP = 18 RD_PIM6 = 19 + RD_MGMTD = 20 RD = { RD_FRR: "frr", RD_ZEBRA: "zebra", @@ -734,6 +735,7 @@ class TopoRouter(TopoGear): RD_PBRD: "pbrd", RD_PATH: "pathd", RD_SNMP: "snmpd", + RD_MGMTD: "mgmtd", } def __init__(self, tgen, cls, name, **params): @@ -810,7 +812,7 @@ class TopoRouter(TopoGear): TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6, TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP, TopoRouter.RD_PIM, TopoRouter.RD_PIM6, TopoRouter.RD_PBR, - TopoRouter.RD_SNMP. + TopoRouter.RD_SNMP, TopoRouter.RD_MGMTD. Possible `source` values are `None` for an empty config file, a path name which is used directly, or a file name with no path components which is first looked for @@ -1017,7 +1019,7 @@ class TopoRouter(TopoGear): memleak_file = ( os.environ.get("TOPOTESTS_CHECK_MEMLEAK") or self.params["memleak_path"] ) - if memleak_file == "" or memleak_file == None: + if memleak_file == "" or memleak_file is None: return self.stop() @@ -1174,7 +1176,7 @@ class TopoExaBGP(TopoHost): self.run("chown -R exabgp:exabgp /etc/exabgp") output = self.run(exacmd + " -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg") - if output == None or len(output) == 0: + if output is None or len(output) == 0: output = "" logger.info("{} exabgp started, output={}".format(self.name, output)) @@ -1269,6 +1271,7 @@ def diagnose_env_linux(rundir): "pim6d", "ldpd", "pbrd", + "mgmtd", ]: path = os.path.join(frrdir, fname) if not os.path.isfile(path): @@ -1283,9 +1286,10 @@ def diagnose_env_linux(rundir): logger.error("could not find {} in {}".format(fname, frrdir)) ret = False else: - if fname != "zebra": + if fname != "zebra" or fname != "mgmtd": continue + os.system("{} -v 2>&1 >{}/frr_mgmtd.txt".format(path, rundir)) os.system("{} -v 2>&1 >{}/frr_zebra.txt".format(path, rundir)) # Test MPLS availability diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 0cd60b228d..8965e59d6b 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -1371,6 +1371,7 @@ class Router(Node): "pbrd": 0, "pathd": 0, "snmpd": 0, + "mgmtd": 0, } self.daemons_options = {"zebra": ""} self.reportCores = True @@ -1398,6 +1399,10 @@ class Router(Node): if not os.path.isfile(zebra_path): raise Exception("FRR zebra binary doesn't exist at {}".format(zebra_path)) + mgmtd_path = os.path.join(self.daemondir, "mgmtd") + if not os.path.isfile(mgmtd_path): + raise Exception("FRR MGMTD binary doesn't exist at {}".format(mgmtd_path)) + # pylint: disable=W0221 # Some params are only meaningful for the parent class. def config(self, **params): @@ -1415,6 +1420,10 @@ class Router(Node): zpath = os.path.join(self.daemondir, "zebra") if not os.path.isfile(zpath): raise Exception("No zebra binary found in {}".format(zpath)) + + cpath = os.path.join(self.daemondir, "mgmtd") + if not os.path.isfile(zpath): + raise Exception("No MGMTD binary found in {}".format(cpath)) # Allow user to specify routertype when the path was specified. if params.get("routertype") is not None: self.routertype = params.get("routertype") @@ -1567,6 +1576,10 @@ class Router(Node): self.cmd_raises("rm -f " + conf_file) self.cmd_raises("touch " + conf_file) else: + # copy zebra.conf to mgmtd folder, which can be used during startup + if daemon == 'zebra': + conf_file_mgmt = "/etc/{}/{}.conf".format(self.routertype, 'mgmtd') + self.cmd_raises("cp {} {}".format(source, conf_file_mgmt)) self.cmd_raises("cp {} {}".format(source, conf_file)) if not self.unified_config or daemon == "frr": @@ -1578,6 +1591,17 @@ class Router(Node): self.cmd('echo "agentXSocket /etc/frr/agentx" >> /etc/snmp/frr.conf') self.cmd('echo "mibs +ALL" > /etc/snmp/snmp.conf') + if (daemon == "zebra") and (self.daemons["mgmtd"] == 0): + # Add mgmtd with zebra - if it exists + try: + mgmtd_path = os.path.join(self.daemondir, "mgmtd") + except: + pdb.set_trace() + if os.path.isfile(mgmtd_path): + self.daemons["mgmtd"] = 1 + self.daemons_options["mgmtd"] = "" + # Auto-Started mgmtd has no config, so it will read from zebra config + if (daemon == "zebra") and (self.daemons["staticd"] == 0): # Add staticd with zebra - if it exists try: @@ -1589,6 +1613,7 @@ class Router(Node): self.daemons["staticd"] = 1 self.daemons_options["staticd"] = "" # Auto-Started staticd has no config, so it will read from zebra config + else: logger.info("No daemon {} known".format(daemon)) # print "Daemons after:", self.daemons @@ -1834,7 +1859,13 @@ class Router(Node): else: logger.info("%s: %s %s started", self, self.routertype, daemon) - # Start Zebra first + # Start mgmtd first + if "mgmtd" in daemons_list: + start_daemon("mgmtd") + while "mgmtd" in daemons_list: + daemons_list.remove("mgmtd") + + # Start Zebra after mgmtd if "zebra" in daemons_list: start_daemon("zebra", "-s 90000000") while "zebra" in daemons_list: diff --git a/tests/topotests/mgmt_tests/test_yang_mgmt.py b/tests/topotests/mgmt_tests/test_yang_mgmt.py new file mode 100644 index 0000000000..06c18d7dfa --- /dev/null +++ b/tests/topotests/mgmt_tests/test_yang_mgmt.py @@ -0,0 +1,540 @@ +#!/usr/bin/python + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# +""" + +1. Verify mgmt commit check. +2. Verify mgmt commit apply. +3. Verify mgmt commit abort. +4. Verify mgmt delete config. +5. Kill mgmtd - verify that static routes are intact. +6. Kill mgmtd - verify that watch frr restarts. +7. Show and CLI - Execute all the newly introduced commands of mgmtd. +8. Verify mgmt rollback functionality. + +""" +import sys +import time +import os +import pytest +import platform + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +# Import topoJson from lib, to create topology and initial configuration +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + create_static_routes, + check_address_types, + step, + shutdown_bringup_interface, + stop_router, + start_router, + apply_raw_config, + kill_router_daemons, + start_router_daemons, +) +from lib.topolog import logger +from lib.bgp import verify_bgp_convergence, create_router_bgp, verify_bgp_rib +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +ADDR_TYPES = check_address_types() +NETWORK = {"ipv4": ["11.0.20.1/32", "11.0.20.2/32"], "ipv6": ["2::1/128", "2::2/128"]} +NETWORK2 = {"ipv4": "11.0.20.1/32", "ipv6": "2::1/128"} +PREFIX1 = {"ipv4": "110.0.20.1/32", "ipv6": "20::1/128"} + + +def setup_module(mod): + """ + Sets up the pytest environment. + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/yang_mgmt.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start deamons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + 'These tests will not run. (have kernel "{}", ' + "requires kernel >= 4.19)".format(platform.release()) + ) + pytest.skip(error_msg) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment. + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology: %s", mod) + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def populate_nh(): + """ + Populate nexthops. + """ + + next_hop_ip = { + "nh1": { + "ipv4": topo["routers"]["r1"]["links"]["r2-link0"]["ipv4"].split("/")[0], + "ipv6": topo["routers"]["r1"]["links"]["r2-link0"]["ipv6"].split("/")[0], + }, + "nh2": { + "ipv4": topo["routers"]["r1"]["links"]["r2-link1"]["ipv4"].split("/")[0], + "ipv6": topo["routers"]["r1"]["links"]["r2-link1"]["ipv6"].split("/")[0], + }, + } + return next_hop_ip + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_mgmt_commit_check(request): + """ + Verify mgmt commit check. + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + + step("Mgmt Commit check") + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.1.1.2/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/bh-type unspec", + "mgmt commit check", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Mgmt Commit check") + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.1.1.2/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/bh-type unspec", + "mgmt commit check", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("verify that the route is not configured, as commit apply not done.") + + dut = "r1" + protocol = "static" + input_dict_4 = { + "r2": { + "static_routes": [ + { + "network": "1192.1.1.2/32", + "next_hop": "Null0", + } + ] + } + } + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is not True + ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name) + + write_test_footer(tc_name) + + +def test_mgmt_commit_apply(request): + """ + Verify mgmt commit apply. + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + + step("Mgmt Commit apply with Valid Configuration") + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.1.1.20/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/vrf default", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Mgmt Commit apply with Invalid Configuration") + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.1.1.20/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/vrf default", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is not True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("verify that the route is configured") + + dut = "r1" + protocol = "static" + input_dict_4 = {"r2": {"static_routes": [{"network": "192.1.1.20/32"}]}} + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name) + + write_test_footer(tc_name) + + +def test_mgmt_commit_abort(request): + """ + Verify mgmt commit abort. + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + + step("Mgmt Commit abort") + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.1.1.3/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/vrf default", + "mgmt commit abort", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("verify that the route is not configured") + + dut = "r1" + protocol = "static" + input_dict_4 = { + "r2": { + "static_routes": [ + { + "network": "192.1.1.3/32", + "next_hop": "Null0", + } + ] + } + } + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is not True + ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name) + + write_test_footer(tc_name) + + +def test_mgmt_delete_config(request): + """ + Verify mgmt delete config. + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + + step("Mgmt - Configure a static route using commit apply") + + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.168.1.3/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/vrf default", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify that the route is added to RIB") + dut = "r1" + protocol = "static" + input_dict_4 = { + "r2": { + "static_routes": [ + { + "network": "192.168.1.3/32", + "next_hop": "Null0", + } + ] + } + } + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name) + + step("Mgmt delete config") + raw_config = { + "r1": { + "raw_config": [ + "mgmt delete-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.168.1.3/32'][afi-safi='frr-routing:ipv4-unicast']", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify that the route is deleted from RIB") + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is not True + ), "Testcase {} : Failed" "Error: Routes is still present in RIB".format(tc_name) + + write_test_footer(tc_name) + + +def test_mgmt_chaos_stop_start_frr(request): + """ + Kill mgmtd - verify that watch frr restarts. + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + next_hop_ip = populate_nh() + + step("Configure Static route with next hop null 0") + + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.1.11.200/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/bh-type unspec", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("verify that the route is configured and present in the zebra") + + dut = "r1" + protocol = "static" + input_dict_4 = {"r2": {"static_routes": [{"network": "192.1.11.200/32"}]}} + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name) + + step("Restart frr") + stop_router(tgen, "r1") + start_router(tgen, "r1") + step("Verify routes are intact in zebra.") + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name) + + step("delete the configured route and ") + raw_config = { + "r1": { + "raw_config": [ + "mgmt delete-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.1.11.200/32'][afi-safi='frr-routing:ipv4-unicast']", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("verify that the route is deleted and deleted from zebra") + + dut = "r1" + protocol = "static" + input_dict_4 = {"r1": {"static_routes": [{"network": "192.1.11.200/32"}]}} + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is not True + ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name) + + write_test_footer(tc_name) + + +def test_mgmt_chaos_kill_daemon(request): + """ + Kill mgmtd - verify that static routes are intact + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + next_hop_ip = populate_nh() + + step("Configure Static route with next hop null 0") + + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.1.11.200/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/bh-type unspec", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("verify that the route is configured and present in the zebra") + + dut = "r1" + protocol = "static" + input_dict_4 = {"r2": {"static_routes": [{"network": "192.1.11.200/32"}]}} + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name) + + step("Kill static daemon on R2.") + kill_router_daemons(tgen, "r1", ["staticd"]) + + step("Bring up staticd daemon on R2.") + start_router_daemons(tgen, "r1", ["staticd"]) + + step("Verify routes are intact in zebra.") + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name) + + step("Kill mgmt daemon on R2.") + kill_router_daemons(tgen, "r1", ["mgmtd"]) + + step("Bring up zebra daemon on R2.") + start_router_daemons(tgen, "r1", ["mgmtd"]) + + step("Verify routes are intact in zebra.") + result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/mgmt_tests/yang_mgmt.json b/tests/topotests/mgmt_tests/yang_mgmt.json new file mode 100644 index 0000000000..0fe3bb1dbe --- /dev/null +++ b/tests/topotests/mgmt_tests/yang_mgmt.json @@ -0,0 +1,157 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link0": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link0": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1-link0": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "keepalivetimer": 1, + "holddowntimer": 4 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "keepalivetimer": 1, + "holddowntimer": 4 + } + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r1-link0": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "keepalivetimer": 1, + "holddowntimer": 4 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "keepalivetimer": 1, + "holddowntimer": 4 + } + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/msdp_mesh_topo1/test_msdp_mesh_topo1.py b/tests/topotests/msdp_mesh_topo1/test_msdp_mesh_topo1.py index 2f31608e64..6d9304d864 100644 --- a/tests/topotests/msdp_mesh_topo1/test_msdp_mesh_topo1.py +++ b/tests/topotests/msdp_mesh_topo1/test_msdp_mesh_topo1.py @@ -71,6 +71,7 @@ def setup_module(mod): router_list = tgen.routers() for rname, router in router_list.items(): + daemon_file = "{}/{}/zebra.conf".format(CWD, rname) if os.path.isfile(daemon_file): router.load_config(TopoRouter.RD_ZEBRA, daemon_file) diff --git a/tests/topotests/msdp_topo1/test_msdp_topo1.py b/tests/topotests/msdp_topo1/test_msdp_topo1.py index 4a007e7d20..1af58b0a01 100755 --- a/tests/topotests/msdp_topo1/test_msdp_topo1.py +++ b/tests/topotests/msdp_topo1/test_msdp_topo1.py @@ -82,6 +82,7 @@ def setup_module(mod): router_list = tgen.routers() for rname, router in router_list.items(): + daemon_file = "{}/{}/zebra.conf".format(CWD, rname) if os.path.isfile(daemon_file): router.load_config(TopoRouter.RD_ZEBRA, daemon_file) diff --git a/tests/topotests/ospf6_topo2/test_ospf6_topo2.py b/tests/topotests/ospf6_topo2/test_ospf6_topo2.py index 9ec0266aa0..f95f7bbe5e 100644 --- a/tests/topotests/ospf6_topo2/test_ospf6_topo2.py +++ b/tests/topotests/ospf6_topo2/test_ospf6_topo2.py @@ -129,6 +129,7 @@ def setup_module(mod): router_list = tgen.routers() for rname, router in router_list.items(): + daemon_file = "{}/{}/zebra.conf".format(CWD, rname) if os.path.isfile(daemon_file): router.load_config(TopoRouter.RD_ZEBRA, daemon_file) diff --git a/tests/topotests/static_simple/r1/mgmtd.conf b/tests/topotests/static_simple/r1/mgmtd.conf new file mode 100644 index 0000000000..0f9f97ca1a --- /dev/null +++ b/tests/topotests/static_simple/r1/mgmtd.conf @@ -0,0 +1 @@ +log timestamp precision 3 diff --git a/tests/topotests/static_simple/r1/staticd.conf b/tests/topotests/static_simple/r1/staticd.conf new file mode 100644 index 0000000000..0f9f97ca1a --- /dev/null +++ b/tests/topotests/static_simple/r1/staticd.conf @@ -0,0 +1 @@ +log timestamp precision 3 diff --git a/tests/topotests/static_simple/r1/zebra.conf b/tests/topotests/static_simple/r1/zebra.conf new file mode 100644 index 0000000000..ec827617ab --- /dev/null +++ b/tests/topotests/static_simple/r1/zebra.conf @@ -0,0 +1,11 @@ +log timestamp precision 3 + +interface r1-eth0 + ip address 101.0.0.1/24 + ipv6 address 2101::1/64 +exit + +interface r1-eth1 vrf red + ip address 102.0.0.1/24 + ipv6 address 2102::1/64 +exit diff --git a/tests/topotests/static_simple/test_static_simple.py b/tests/topotests/static_simple/test_static_simple.py new file mode 100644 index 0000000000..817336ac21 --- /dev/null +++ b/tests/topotests/static_simple/test_static_simple.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2021, LabN Consulting, L.L.C. +# Copyright (c) 2019-2020 by +# Donatas Abraitis +# +""" +Test static route functionality +""" + +import datetime +import ipaddress +import math +import os +import sys +import re + +import pytest +from lib.topogen import TopoRouter, Topogen, get_topogen +from lib.topolog import logger +from lib.common_config import retry, step + +pytestmark = [pytest.mark.staticd] + + +@pytest.fixture(scope="module") +def tgen(request): + "Setup/Teardown the environment and provide tgen argument to tests" + + topodef = {"s1": ("r1",), "s2": ("r1",)} + + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + # Setup VRF red + router.net.add_l3vrf("red", 10) + router.net.add_loop("lo-red") + router.net.attach_iface_to_l3vrf("lo-red", "red") + router.net.attach_iface_to_l3vrf(rname + "-eth1", "red") + # and select daemons to run + router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + router.load_config(TopoRouter.RD_MGMTD) + router.load_config(TopoRouter.RD_STATIC) + + tgen.start_router() + yield tgen + tgen.stop_topology() + + +def get_ip_networks(super_prefix, count): + count_log2 = math.log(count, 2) + if count_log2 != int(count_log2): + count_log2 = int(count_log2) + 1 + else: + count_log2 = int(count_log2) + network = ipaddress.ip_network(super_prefix) + return tuple(network.subnets(count_log2))[0:count] + + +def enable_debug(router): + router.vtysh_cmd("debug northbound callbacks configuration") + + +def disable_debug(router): + router.vtysh_cmd("no debug northbound callbacks configuration") + + +@retry(retry_timeout=3, initial_wait=0.1) +def check_kernel(r1, super_prefix, count, add, is_blackhole, vrf, matchvia): + network = ipaddress.ip_network(super_prefix) + vrfstr = f" vrf {vrf}" if vrf else "" + if network.version == 6: + kernel = r1.run(f"ip -6 route show{vrfstr}") + else: + kernel = r1.run(f"ip -4 route show{vrfstr}") + + logger.debug("checking kernel routing table%s:\n%s", vrfstr, kernel) + for i, net in enumerate(get_ip_networks(super_prefix, count)): + if not add: + assert str(net) not in kernel + continue + + if is_blackhole: + route = f"blackhole {str(net)} proto (static|196) metric 20" + else: + route = ( + f"{str(net)}(?: nhid [0-9]+)? {matchvia} " + "proto (static|196) metric 20" + ) + assert re.search(route, kernel), f"Failed to find \n'{route}'\n in \n'{kernel}'" + + +def do_config( + r1, + count, + add=True, + do_ipv6=False, + via=None, + vrf=None, + use_cli=False, +): + optype = "adding" if add else "removing" + iptype = "IPv6" if do_ipv6 else "IPv4" + + # + # Set the route details + # + + if vrf: + super_prefix = "2002::/48" if do_ipv6 else "20.0.0.0/8" + else: + super_prefix = "2001::/48" if do_ipv6 else "10.0.0.0/8" + + matchtype = "" + matchvia = "" + if via == "blackhole": + pass + elif via: + matchvia = f"dev {via}" + else: + if vrf: + via = "2102::2" if do_ipv6 else "102.0.0.2" + matchvia = f"via {via} dev r1-eth1" + else: + via = "2101::2" if do_ipv6 else "101.0.0.2" + matchvia = f"via {via} dev r1-eth0" + + vrfdbg = " in vrf {}".format(vrf) if vrf else "" + logger.debug("{} {} static {} routes{}".format(optype, count, iptype, vrfdbg)) + + # + # Generate config file in a retrievable place + # + + config_file = os.path.join( + r1.logdir, r1.name, "{}-routes-{}.conf".format(iptype.lower(), optype) + ) + with open(config_file, "w") as f: + if use_cli: + f.write("configure terminal\n") + if vrf: + f.write("vrf {}\n".format(vrf)) + + for i, net in enumerate(get_ip_networks(super_prefix, count)): + if add: + f.write("ip route {} {}\n".format(net, via)) + else: + f.write("no ip route {} {}\n".format(net, via)) + + # + # Load config file. + # + + if use_cli: + load_command = 'vtysh < "{}"'.format(config_file) + else: + load_command = 'vtysh -f "{}"'.format(config_file) + tstamp = datetime.datetime.now() + output = r1.cmd_raises(load_command) + delta = (datetime.datetime.now() - tstamp).total_seconds() + + # + # Verify the results are in the kernel + # + check_kernel(r1, super_prefix, count, add, via == "blackhole", vrf, matchvia) + + optyped = "added" if add else "removed" + logger.debug( + "{} {} {} static routes under {}{} in {}s".format( + optyped, count, iptype.lower(), super_prefix, vrfdbg, delta + ) + ) + + +def guts(tgen, vrf, use_cli): + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.routers()["r1"] + + step("add via gateway", reset=True) + do_config(r1, 1, True, False, vrf=vrf, use_cli=use_cli) + step("remove via gateway") + do_config(r1, 1, False, False, vrf=vrf, use_cli=use_cli) + + via = f"lo-{vrf}" if vrf else "lo" + step("add via loopback") + do_config(r1, 1, True, False, via=via, vrf=vrf, use_cli=use_cli) + step("remove via loopback") + do_config(r1, 1, False, False, via=via, vrf=vrf, use_cli=use_cli) + + step("add via blackhole") + do_config(r1, 1, True, False, via="blackhole", vrf=vrf, use_cli=use_cli) + step("remove via blackhole") + do_config(r1, 1, False, False, via="blackhole", vrf=vrf, use_cli=use_cli) + + +def test_static_cli(tgen): + guts(tgen, "", True) + + +def test_static_file(tgen): + guts(tgen, "", False) + + +def test_static_vrf_cli(tgen): + guts(tgen, "red", True) + + +def test_static_vrf_file(tgen): + guts(tgen, "red", False)