diff --git a/tests/topotests/mgmt_startup/r1/mgmtd.conf b/tests/topotests/mgmt_startup/r1/mgmtd.conf new file mode 100644 index 0000000000..ecc829c662 --- /dev/null +++ b/tests/topotests/mgmt_startup/r1/mgmtd.conf @@ -0,0 +1,13 @@ +debug northbound notifications +debug northbound libyang +debug northbound events +debug northbound callbacks +debug mgmt backend datastore frontend transaction +debug mgmt client backend +debug mgmt client frontend + +ip route 12.0.0.0/24 101.0.0.2 +ip route 13.0.0.0/24 101.0.0.3 + +ipv6 route 2012::/48 2101::2 +ipv6 route 2013::/48 2101::3 diff --git a/tests/topotests/mgmt_startup/r1/zebra.conf b/tests/topotests/mgmt_startup/r1/zebra.conf new file mode 100644 index 0000000000..98cc95ba2b --- /dev/null +++ b/tests/topotests/mgmt_startup/r1/zebra.conf @@ -0,0 +1,7 @@ +log timestamp precision 3 +log file frr2.log + +interface r1-eth0 + ip address 101.0.0.1/24 + ipv6 address 2101::1/64 +exit \ No newline at end of file diff --git a/tests/topotests/mgmt_startup/r2/staticd.conf b/tests/topotests/mgmt_startup/r2/staticd.conf new file mode 100644 index 0000000000..b581ed2dc3 --- /dev/null +++ b/tests/topotests/mgmt_startup/r2/staticd.conf @@ -0,0 +1,7 @@ +log timestamp precision 3 + +ip route 11.0.0.0/24 101.0.0.1 +ip route 13.0.0.0/24 101.0.0.3 + +ipv6 route 2011::/48 2102::1 +ipv6 route 2013::/48 2102::3 \ No newline at end of file diff --git a/tests/topotests/mgmt_startup/r2/zebra.conf b/tests/topotests/mgmt_startup/r2/zebra.conf new file mode 100644 index 0000000000..1d37a65737 --- /dev/null +++ b/tests/topotests/mgmt_startup/r2/zebra.conf @@ -0,0 +1,12 @@ +log timestamp precision 3 +debug northbound notifications +debug northbound libyang +debug northbound events +debug northbound callbacks +debug mgmt backend datastore frontend transaction +debug mgmt client backend +debug mgmt client frontend + +interface r2-eth0 + ip address 101.0.0.2/24 + ipv6 address 2101::2/64 diff --git a/tests/topotests/mgmt_startup/r3/zebra.conf b/tests/topotests/mgmt_startup/r3/zebra.conf new file mode 100644 index 0000000000..8419d74975 --- /dev/null +++ b/tests/topotests/mgmt_startup/r3/zebra.conf @@ -0,0 +1,18 @@ +log timestamp precision 3 +debug northbound notifications +debug northbound libyang +debug northbound events +debug northbound callbacks +debug mgmt backend datastore frontend transaction +debug mgmt client backend +debug mgmt client frontend + +interface r3-eth0 + ip address 101.0.0.3/24 + ipv6 address 2101::3/64 + +ip route 11.0.0.0/24 101.0.0.1 +ip route 12.0.0.0/24 101.0.0.2 + +ipv6 route 2011::/48 2101::1 +ipv6 route 2012::/48 2101::2 diff --git a/tests/topotests/mgmt_startup/test_bigconf.py b/tests/topotests/mgmt_startup/test_bigconf.py new file mode 100644 index 0000000000..9213845d2c --- /dev/null +++ b/tests/topotests/mgmt_startup/test_bigconf.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: ISC +# +# May 2 2023, Christian Hopps +# +# Copyright (c) 2023, LabN Consulting, L.L.C. +# +""" +Test static route startup functionality +""" + +import datetime +import ipaddress +import logging +import math +import os +import re + +import pytest +from lib.common_config import retry, step +from lib.topogen import Topogen, TopoRouter +from lib.topolog import logger +from munet.base import Timeout + +CWD = os.path.dirname(os.path.realpath(__file__)) + +# pytestmark = [pytest.mark.staticd, pytest.mark.mgmtd] +pytestmark = [pytest.mark.staticd] + + +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] + + +track = Timeout(0) +ROUTE_COUNT = 5000 +ROUTE_RANGE = [None, None] + + +def write_big_route_conf(rtr, super_prefix, count): + start = None + end = None + with open(f"{CWD}/{rtr.name}/big.conf", "w+", encoding="ascii") as f: + for net in get_ip_networks(super_prefix, count): + end = net + if not start: + start = net + f.write(f"ip route {net} lo\n") + + return start, end + + +@pytest.fixture(scope="module") +def tgen(request): + "Setup/Teardown the environment and provide tgen argument to tests" + + global start_time + topodef = { + "s1": ("r1",), + } + + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + + start, end = write_big_route_conf(tgen.gears["r1"].net, "10.0.0.0/8", ROUTE_COUNT) + ROUTE_RANGE[0] = start + ROUTE_RANGE[1] = end + + # configure mgmtd using current mgmtd config file + tgen.gears["r1"].load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + tgen.gears["r1"].load_config(TopoRouter.RD_MGMTD, "big.conf") + + track.started_on = datetime.datetime.now() + + tgen.start_router() + yield tgen + tgen.stop_topology() + + +@retry(retry_timeout=3, initial_wait=0.1) +def check_kernel(r1, prefix, expected=True): + net = ipaddress.ip_network(prefix) + if net.version == 6: + kernel = r1.net.cmd_nostatus("ip -6 route show", warn=not expected) + else: + kernel = r1.net.cmd_nostatus("ip -4 route show", warn=not expected) + + logger.debug("checking kernel routing table:\n%s", kernel) + route = f"{str(net)}(?: nhid [0-9]+)?.*proto (static|196)" + m = re.search(route, kernel) + if expected and not m: + return f"Failed to find \n'{route}'\n in \n'{kernel}'" + elif not expected and m: + return f"Failed found \n'{route}'\n in \n'{kernel}'" + return None + + +def test_staticd_late_start(tgen): + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.routers()["r1"] + + step(f"Verifying {ROUTE_COUNT} startup routes are present") + + timeo = Timeout(30) + for remaining in timeo: + rc, o, e = r1.net.cmd_status("vtysh -c 'show version'") + if not rc: + break + print("nogo: ", rc, o, e) + assert not timeo.is_expired() + logging.info("r1: vtysh connected after %ss", track.elapsed()) + + result = check_kernel(r1, ROUTE_RANGE[0], retry_timeout=20) + assert result is None + logging.info("r1: first route installed after %ss", track.elapsed()) + + result = check_kernel(r1, ROUTE_RANGE[1], retry_timeout=20) + assert result is None + logging.info("r1: last route installed after %ss", track.elapsed()) diff --git a/tests/topotests/mgmt_startup/test_config.py b/tests/topotests/mgmt_startup/test_config.py new file mode 100644 index 0000000000..c215652daf --- /dev/null +++ b/tests/topotests/mgmt_startup/test_config.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: ISC +# +# May 2 2023, Christian Hopps +# +# Copyright (c) 2023, LabN Consulting, L.L.C. +# +""" +Test static route functionality using old or new configuration files. + +User compat: + + - mgmtd split config will first look to `/etc/frr/zebra.conf` + then `/etc/frr/staticd.conf` and finally `/etc/frr/mgmtd.conf` + + - When new components are converted to mgmtd their split config should be + added here too. + +Topotest compat: + + - `mgmtd.conf` is copied to `/etc/frr/` for use by mgmtd when implicit load, + or explicit load no config specified. + + - `staticd.conf` is copied to `/etc/frr/` for use by mgmtd when staticd + is explicit load implict config, and explicit config. + +""" + +import ipaddress +import re + +import pytest +from lib.common_config import retry, step +from lib.topogen import Topogen, TopoRouter +from lib.topolog import logger + +# pytestmark = [pytest.mark.staticd, pytest.mark.mgmtd] +pytestmark = [pytest.mark.staticd] + + +@pytest.fixture(scope="module") +def tgen(request): + "Setup/Teardown the environment and provide tgen argument to tests" + + topodef = { + "s1": ("r1", "r2", "r3"), + } + + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + + # configure mgmtd using current mgmtd config file + tgen.gears["r1"].load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + tgen.gears["r1"].load_config(TopoRouter.RD_MGMTD, "mgmtd.conf") + + # user/topotest compat: + # configure mgmtd using old staticd config file, with explicity staticd + # load. + tgen.gears["r2"].load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + tgen.gears["r2"].load_config(TopoRouter.RD_STATIC, "staticd.conf") + + # user compat: + # configure mgmtd using backup config file `zebra.conf` + tgen.gears["r3"].load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + + tgen.start_router() + yield tgen + tgen.stop_topology() + + +@retry(retry_timeout=3, initial_wait=0.1) +def check_kernel(r1, prefix, expected=True): + net = ipaddress.ip_network(prefix) + if net.version == 6: + kernel = r1.net.cmd_nostatus("ip -6 route show", warn=not expected) + else: + kernel = r1.net.cmd_nostatus("ip -4 route show", warn=not expected) + + logger.debug("checking kernel routing table:\n%s", kernel) + route = f"{str(net)}(?: nhid [0-9]+)?.*proto (static|196)" + m = re.search(route, kernel) + if expected and not m: + return f"Failed to find \n'{route}'\n in \n'{kernel}'" + elif not expected and m: + return f"Failed found \n'{route}'\n in \n'{kernel}'" + return None + + +def test_staticd_late_start(tgen): + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for x in ["r1", "r2", "r3"]: + tgen.gears[x].net.cmd_nostatus( + "vtysh -c 'debug mgmt client frontend' " + "-c 'debug mgmt client backend' " + "-c 'debug mgmt backend frontend datastore transaction'" + ) + + r1 = tgen.routers()["r1"] + r2 = tgen.routers()["r2"] + r3 = tgen.routers()["r3"] + + step("Verifying routes are present on r1") + result = check_kernel(r1, "12.0.0.0/24") + assert result is None + result = check_kernel(r1, "13.0.0.0/24") + assert result is None + + step("Verifying routes are present on r2") + result = check_kernel(r2, "11.0.0.0/24") + assert result is None + result = check_kernel(r2, "13.0.0.0/24") + assert result is None + + step("Verifying routes are present on r3") + result = check_kernel(r3, "11.0.0.0/24") + assert result is None + result = check_kernel(r3, "12.0.0.0/24") + assert result is None diff --git a/tests/topotests/mgmt_startup/test_startup.py b/tests/topotests/mgmt_startup/test_startup.py new file mode 100644 index 0000000000..48540354b4 --- /dev/null +++ b/tests/topotests/mgmt_startup/test_startup.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: ISC +# +# May 2 2023, Christian Hopps +# +# Copyright (c) 2023, LabN Consulting, L.L.C. +# +""" +Test static route functionality using old or new configuration files. + +User compat: + + - mgmtd split config will first look to `/etc/frr/zebra.conf` + then `/etc/frr/staticd.conf` and finally `/etc/frr/mgmtd.conf` + + - When new components are converted to mgmtd their split config should be + added here too. + +Topotest compat: + + - `mgmtd.conf` is copied to `/etc/frr/` for use by mgmtd when implicit load, + or explicit load no config specified. + + - `staticd.conf` is copied to `/etc/frr/` for use by mgmtd when staticd + is explicit load implict config, and explicit config. + +""" + +import ipaddress +import re +import time + +import pytest +from lib.common_config import create_static_routes, retry, step, verify_rib +from lib.topogen import Topogen, TopoRouter +from lib.topolog import logger + +# pytestmark = [pytest.mark.staticd, pytest.mark.mgmtd] +pytestmark = [pytest.mark.staticd] + + +@pytest.fixture(scope="module") +def tgen(request): + "Setup/Teardown the environment and provide tgen argument to tests" + + topodef = { + "s1": ("r1",), + } + + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + + # configure mgmtd using current mgmtd config file + tgen.gears["r1"].load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + tgen.gears["r1"].load_config(TopoRouter.RD_MGMTD) + + # Explicit disable staticd now.. + tgen.gears["r1"].net.daemons["staticd"] = 0 + + tgen.start_router() + yield tgen + tgen.stop_topology() + + +@retry(retry_timeout=3, initial_wait=0.1) +def check_kernel(r1, prefix, expected=True): + net = ipaddress.ip_network(prefix) + if net.version == 6: + kernel = r1.net.cmd_nostatus("ip -6 route show", warn=not expected) + else: + kernel = r1.net.cmd_nostatus("ip -4 route show", warn=not expected) + + logger.debug("checking kernel routing table:\n%s", kernel) + route = f"{str(net)}(?: nhid [0-9]+)?.*proto (static|196)" + m = re.search(route, kernel) + if expected and not m: + return f"Failed to find \n'{route}'\n in \n'{kernel}'" + elif not expected and m: + return f"Failed found \n'{route}'\n in \n'{kernel}'" + return None + + +def test_staticd_late_start(tgen): + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # for x in ["r1"]: + # tgen.gears[x].net.cmd_nostatus( + # "vtysh -c 'debug mgmt client frontend' " + # "-c 'debug mgmt client backend' " + # "-c 'debug mgmt backend frontend datastore transaction'" + # ) + + r1 = tgen.routers()["r1"] + + step("Verifying startup route is not present w/o staticd running") + result = check_kernel(r1, "12.0.0.0/24", expected=False) + assert result is not None + + step("Configure another static route verify is not present w/o staticd running") + r1.net.cmd_nostatus("vtysh -c 'config t' -c 'ip route 12.1.0.0/24 101.0.0.2'") + result = check_kernel(r1, "12.0.0.0/24", expected=False) + assert result is not None + result = check_kernel(r1, "12.1.0.0/24", expected=False) + assert result is not None + + step("Starting staticd") + r1.startDaemons(["staticd"]) + + step("Verifying both routes are present") + result = check_kernel(r1, "12.0.0.0/24") + assert result is None + result = check_kernel(r1, "12.1.0.0/24") + assert result is None