diff options
Diffstat (limited to 'tests/topotests/ospfapi')
-rwxr-xr-x | tests/topotests/ospfapi/ctester.py | 154 | ||||
l--------- | tests/topotests/ospfapi/lib | 1 | ||||
-rw-r--r-- | tests/topotests/ospfapi/r1/ospfd.conf | 15 | ||||
-rw-r--r-- | tests/topotests/ospfapi/r1/zebra.conf | 6 | ||||
-rw-r--r-- | tests/topotests/ospfapi/r2/ospfd.conf | 15 | ||||
-rw-r--r-- | tests/topotests/ospfapi/r2/zebra.conf | 7 | ||||
-rw-r--r-- | tests/topotests/ospfapi/r3/ospfd.conf | 15 | ||||
-rw-r--r-- | tests/topotests/ospfapi/r3/zebra.conf | 7 | ||||
-rw-r--r-- | tests/topotests/ospfapi/r4/ospfd.conf | 15 | ||||
-rw-r--r-- | tests/topotests/ospfapi/r4/zebra.conf | 6 | ||||
-rw-r--r-- | tests/topotests/ospfapi/test_ospf_clientapi.py | 1586 |
11 files changed, 1827 insertions, 0 deletions
diff --git a/tests/topotests/ospfapi/ctester.py b/tests/topotests/ospfapi/ctester.py new file mode 100755 index 0000000..ab23744 --- /dev/null +++ b/tests/topotests/ospfapi/ctester.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: MIT +# +# January 17 2022, Christian Hopps <chopps@labn.net> +# +# Copyright 2022, LabN Consulting, L.L.C. +import argparse +import asyncio +import logging +import os +import sys + +CWD = os.path.dirname(os.path.realpath(__file__)) + +CLIENTDIR = os.path.abspath(os.path.join(CWD, "../../../ospfclient")) +if not os.path.exists(CLIENTDIR): + CLIENTDIR = os.path.join(CWD, "/usr/lib/frr") +assert os.path.exists( + os.path.join(CLIENTDIR, "ospfclient.py") +), "can't locate ospfclient.py" + +sys.path[0:0] = [CLIENTDIR] + +import ospfclient as api # pylint: disable=E0401 # noqa: E402 + + +async def do_monitor(c, args): + cv = asyncio.Condition() + + async def cb(new_router_id, _): + assert new_router_id == c.router_id + logging.info("NEW ROUTER ID: %s", new_router_id) + sys.stdout.flush() + async with cv: + cv.notify_all() + + logging.debug("API using monitor router ID callback") + await c.monitor_router_id(callback=cb) + + for check in args.monitor: + logging.info("Waiting for %s", check) + + while True: + async with cv: + got = c.router_id + if str(check) == str(got): + break + logging.debug("expected '%s' != '%s'\nwaiting on notify", check, got) + await cv.wait() + + logging.info("SUCCESS: %s", check) + print("SUCCESS: {}".format(check)) + sys.stdout.flush() + + +async def do_wait(c, args): + cv = asyncio.Condition() + + async def cb(added, removed): + logging.debug("callback: added: %s removed: %s", added, removed) + sys.stdout.flush() + async with cv: + cv.notify_all() + + logging.debug("API using monitor reachable callback") + await c.monitor_reachable(callback=cb) + + for w in args.wait: + check = ",".join(sorted(list(w.split(",")))) + logging.info("Waiting for %s", check) + + while True: + async with cv: + got = ",".join(sorted([str(x) for x in c.reachable_routers])) + if check == got: + break + logging.debug("expected '%s' != '%s'\nwaiting on notify", check, got) + await cv.wait() + + logging.info("SUCCESS: %s", check) + print("SUCCESS: {}".format(check)) + sys.stdout.flush() + + +async def async_main(args): + c = api.OspfOpaqueClient(args.server) + await c.connect() + if sys.version_info[1] > 6: + asyncio.create_task(c._handle_msg_loop()) # pylint: disable=W0212 + else: + asyncio.get_event_loop().create_task( + c._handle_msg_loop() # pylint: disable=W0212 + ) + + if args.monitor: + await do_monitor(c, args) + if args.wait: + await do_wait(c, args) + return 0 + + +def main(*args): + ap = argparse.ArgumentParser(args) + ap.add_argument( + "--monitor", action="append", help="monitor and wait for this router ID" + ) + ap.add_argument("--server", default="localhost", help="OSPF API server") + ap.add_argument( + "--wait", action="append", help="wait for comma-sep set of reachable routers" + ) + ap.add_argument("-v", "--verbose", action="store_true", help="be verbose") + args = ap.parse_args() + + level = logging.DEBUG if args.verbose else logging.INFO + logging.basicConfig( + level=level, format="%(asctime)s %(levelname)s: TESTER: %(name)s: %(message)s" + ) + + # We need to flush this output to stdout right away + h = logging.StreamHandler(sys.stdout) + h.flush = sys.stdout.flush + f = logging.Formatter("%(asctime)s %(name)s: %(levelname)s: %(message)s") + h.setFormatter(f) + logger = logging.getLogger("ospfclient") + logger.addHandler(h) + logger.propagate = False + + logging.info("ctester: starting") + sys.stdout.flush() + + status = 3 + try: + if sys.version_info[1] > 6: + status = asyncio.run(async_main(args)) + else: + loop = asyncio.get_event_loop() + try: + status = loop.run_until_complete(async_main(args)) + finally: + loop.close() + except KeyboardInterrupt: + logging.info("Exiting, received KeyboardInterrupt in main") + except Exception as error: + logging.info("Exiting, unexpected exception %s", error, exc_info=True) + else: + logging.info("api: clean exit") + + return status + + +if __name__ == "__main__": + exit_status = main() + sys.exit(exit_status) diff --git a/tests/topotests/ospfapi/lib b/tests/topotests/ospfapi/lib new file mode 120000 index 0000000..dc598c5 --- /dev/null +++ b/tests/topotests/ospfapi/lib @@ -0,0 +1 @@ +../lib
\ No newline at end of file diff --git a/tests/topotests/ospfapi/r1/ospfd.conf b/tests/topotests/ospfapi/r1/ospfd.conf new file mode 100644 index 0000000..338691b --- /dev/null +++ b/tests/topotests/ospfapi/r1/ospfd.conf @@ -0,0 +1,15 @@ +! +interface r1-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf area 1.2.3.4 +! +interface r1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf area 1.2.3.4 +! +router ospf + ospf router-id 1.0.0.0 + capability opaque +! diff --git a/tests/topotests/ospfapi/r1/zebra.conf b/tests/topotests/ospfapi/r1/zebra.conf new file mode 100644 index 0000000..7451591 --- /dev/null +++ b/tests/topotests/ospfapi/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 10.0.1.1/24 +! +interface r1-eth1 + ip address 10.0.4.1/24 diff --git a/tests/topotests/ospfapi/r2/ospfd.conf b/tests/topotests/ospfapi/r2/ospfd.conf new file mode 100644 index 0000000..1bf2a0c --- /dev/null +++ b/tests/topotests/ospfapi/r2/ospfd.conf @@ -0,0 +1,15 @@ +! +interface r2-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf area 1.2.3.4 +! +interface r2-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf area 1.2.3.4 +! +router ospf + ospf router-id 2.0.0.0 + capability opaque +! diff --git a/tests/topotests/ospfapi/r2/zebra.conf b/tests/topotests/ospfapi/r2/zebra.conf new file mode 100644 index 0000000..e66d52d --- /dev/null +++ b/tests/topotests/ospfapi/r2/zebra.conf @@ -0,0 +1,7 @@ +! +interface r2-eth0 + ip address 10.0.1.2/24 +! +interface r2-eth1 + ip address 10.0.2.2/24 +! diff --git a/tests/topotests/ospfapi/r3/ospfd.conf b/tests/topotests/ospfapi/r3/ospfd.conf new file mode 100644 index 0000000..ecf2042 --- /dev/null +++ b/tests/topotests/ospfapi/r3/ospfd.conf @@ -0,0 +1,15 @@ +! +interface r3-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf area 1.2.3.4 +! +interface r3-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf area 1.2.3.4 +! +router ospf + ospf router-id 3.0.0.0 + capability opaque +! diff --git a/tests/topotests/ospfapi/r3/zebra.conf b/tests/topotests/ospfapi/r3/zebra.conf new file mode 100644 index 0000000..072c297 --- /dev/null +++ b/tests/topotests/ospfapi/r3/zebra.conf @@ -0,0 +1,7 @@ +! +interface r3-eth0 + ip address 10.0.2.3/24 +! +interface r3-eth1 + ip address 10.0.3.3/24 +! diff --git a/tests/topotests/ospfapi/r4/ospfd.conf b/tests/topotests/ospfapi/r4/ospfd.conf new file mode 100644 index 0000000..4de8cae --- /dev/null +++ b/tests/topotests/ospfapi/r4/ospfd.conf @@ -0,0 +1,15 @@ +! +interface r4-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf area 1.2.3.4 +! +interface r4-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf area 1.2.3.4 +! +router ospf + ospf router-id 4.0.0.0 + capability opaque +! diff --git a/tests/topotests/ospfapi/r4/zebra.conf b/tests/topotests/ospfapi/r4/zebra.conf new file mode 100644 index 0000000..233ffa5 --- /dev/null +++ b/tests/topotests/ospfapi/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r4-eth0 + ip address 10.0.3.4/24 +! +interface r4-eth1 + ip address 10.0.4.4/24
\ No newline at end of file diff --git a/tests/topotests/ospfapi/test_ospf_clientapi.py b/tests/topotests/ospfapi/test_ospf_clientapi.py new file mode 100644 index 0000000..7a7ea85 --- /dev/null +++ b/tests/topotests/ospfapi/test_ospf_clientapi.py @@ -0,0 +1,1586 @@ +#!/usr/bin/env python +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2021-2022, LabN Consulting, L.L.C. +# + +""" +test_ospf_clientapi.py: Test the OSPF client API. +""" + +import logging +import os +import re +import signal +import subprocess +import sys +import time +from datetime import datetime, timedelta +from functools import partial + +import pytest +from lib.common_config import ( + kill_router_daemons, + retry, + run_frr_cmd, + shutdown_bringup_interface, + start_router_daemons, + step, +) +from lib.micronet import Timeout, comm_error +from lib.topogen import Topogen, TopoRouter +from lib.topotest import interface_set_status, json_cmp + +# 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 + +pytestmark = [pytest.mark.ospfd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +TESTDIR = os.path.abspath(CWD) + +CLIENTDIR = os.path.abspath(os.path.join(CWD, "../../../ospfclient")) +if not os.path.exists(CLIENTDIR): + CLIENTDIR = os.path.join(CWD, "/usr/lib/frr") + +assert os.path.exists( + os.path.join(CLIENTDIR, "ospfclient.py") +), "can't locate ospfclient.py" + + +# ---------- +# Test Setup +# ---------- + +# +# r1 - r2 +# | | +# r4 - r3 +# + + +@pytest.fixture(scope="function", name="tgen") +def _tgen(request): + "Setup/Teardown the environment and provide tgen argument to tests" + nrouters = request.param + topodef = {f"sw{i}": (f"r{i}", f"r{i+1}") for i in range(1, nrouters)} + if nrouters == 4: + topodef["sw4"] = ("r4", "r1") + + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for _, router in router_list.items(): + router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + router.load_config(TopoRouter.RD_OSPF, "ospfd.conf") + router.net.daemons_options["ospfd"] = "--apiserver" + + tgen.start_router() + + yield tgen + + tgen.stop_topology() + + +# Fixture that executes before each test +@pytest.fixture(autouse=True) +def skip_on_failure(tgen): + if tgen.routers_have_failure(): + pytest.skip("skipped because of previous test failure") + + +# ------------ +# Test Utility +# ------------ + + +@retry(retry_timeout=45) +def verify_ospf_database(tgen, dut, input_dict, cmd="show ip ospf database json"): + del tgen + show_ospf_json = run_frr_cmd(dut, cmd, isjson=True) + if not bool(show_ospf_json): + return "ospf is not running" + result = json_cmp(show_ospf_json, input_dict) + return str(result) if result else None + + +def myreadline(f): + buf = b"" + while True: + # logging.debug("READING 1 CHAR") + c = f.read(1) + if not c: + return buf if buf else None + buf += c + # logging.debug("READ CHAR: '%s'", c) + if c == b"\n": + return buf + + +def _wait_output(p, regex, maxwait=120): + timeout = Timeout(maxwait) + while not timeout.is_expired(): + # line = p.stdout.readline() + line = myreadline(p.stdout) + if not line: + assert None, "EOF waiting for '{}'".format(regex) + line = line.decode("utf-8") + line = line.rstrip() + if line: + logging.debug("GOT LINE: '%s'", line) + m = re.search(regex, line) + if m: + return m + assert None, "Failed to get output matching '{}' withint {} actual {}s".format( + regex, maxwait, timeout.elapsed() + ) + + +# ----- +# Tests +# ----- + + +def _test_reachability(tgen, testbin): + waitlist = [ + "1.0.0.0,2.0.0.0,4.0.0.0", + "2.0.0.0,4.0.0.0", + "1.0.0.0,2.0.0.0,4.0.0.0", + ] + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + r4 = tgen.gears["r4"] + + wait_args = [f"--wait={x}" for x in waitlist] + + p = None + try: + step("reachable: check for initial reachability") + p = r3.popen( + ["/usr/bin/timeout", "120", testbin, "-v", *wait_args], + encoding=None, # don't buffer + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + _wait_output(p, "SUCCESS: {}".format(waitlist[0])) + + step("reachable: check for modified reachability") + interface_set_status(r2, "r2-eth0", False) + interface_set_status(r4, "r4-eth1", False) + _wait_output(p, "SUCCESS: {}".format(waitlist[1])) + + step("reachable: check for restored reachability") + interface_set_status(r2, "r2-eth0", True) + interface_set_status(r4, "r4-eth1", True) + _wait_output(p, "SUCCESS: {}".format(waitlist[2])) + except Exception as error: + logging.error("ERROR: %s", error) + raise + finally: + if p: + p.terminate() + p.wait() + + +@pytest.mark.parametrize("tgen", [4], indirect=True) +def test_ospf_reachability(tgen): + testbin = os.path.join(TESTDIR, "ctester.py") + rc, o, e = tgen.gears["r2"].net.cmd_status([testbin, "--help"]) + logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", testbin, rc, o, e) + _test_reachability(tgen, testbin) + + +def _test_router_id(tgen, testbin): + r1 = tgen.gears["r1"] + waitlist = [ + "1.0.0.0", + "1.1.1.1", + "1.0.0.0", + ] + + mon_args = [f"--monitor={x}" for x in waitlist] + + p = None + try: + step("router id: check for initial router id") + p = r1.popen( + ["/usr/bin/timeout", "120", testbin, "-v", *mon_args], + encoding=None, # don't buffer + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + _wait_output(p, "SUCCESS: {}".format(waitlist[0])) + + step("router id: check for modified router id") + r1.vtysh_multicmd("conf t\nrouter ospf\nospf router-id 1.1.1.1") + _wait_output(p, "SUCCESS: {}".format(waitlist[1])) + + step("router id: check for restored router id") + r1.vtysh_multicmd("conf t\nrouter ospf\nospf router-id 1.0.0.0") + _wait_output(p, "SUCCESS: {}".format(waitlist[2])) + except Exception as error: + logging.error("ERROR: %s", error) + raise + finally: + if p: + p.terminate() + p.wait() + + +@pytest.mark.parametrize("tgen", [2], indirect=True) +def test_ospf_router_id(tgen): + testbin = os.path.join(TESTDIR, "ctester.py") + rc, o, e = tgen.gears["r1"].net.cmd_status([testbin, "--help"]) + logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", testbin, rc, o, e) + _test_router_id(tgen, testbin) + + +def _test_add_data(tgen, apibin): + "Test adding opaque data to domain" + + r1 = tgen.gears["r1"] + + step("add opaque: add opaque link local") + + p = None + try: + p = r1.popen([apibin, "-v", "add,9,10.0.1.1,230,2,00000202"]) + input_dict = { + "routerId": "1.0.0.0", + "areas": { + "1.2.3.4": { + "linkLocalOpaqueLsa": [ + { + "lsId": "230.0.0.2", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + } + ], + } + }, + } + # Wait for it to show up + assert verify_ospf_database(tgen, r1, input_dict) is None + + input_dict = { + "linkLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "linkStateId": "230.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "opaqueData": "00000202", + }, + ], + } + }, + } + # verify content + json_cmd = "show ip ospf da opaque-link json" + assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None + + step("reset client, add opaque area, verify link local flushing") + + p.send_signal(signal.SIGINT) + time.sleep(2) + p.wait() + p = None + p = r1.popen([apibin, "-v", "add,10,1.2.3.4,231,1,00010101"]) + input_dict = { + "routerId": "1.0.0.0", + "areas": { + "1.2.3.4": { + "linkLocalOpaqueLsa": [ + { + "lsId": "230.0.0.2", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "lsaAge": 3600, + } + ], + "areaLocalOpaqueLsa": [ + { + "lsId": "231.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + }, + ], + } + }, + } + # Wait for it to show up + assert verify_ospf_database(tgen, r1, input_dict) is None + + input_dict = { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "linkStateId": "231.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "opaqueData": "00010101", + }, + ], + } + }, + } + # verify content + json_cmd = "show ip ospf da opaque-area json" + assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None + + step("reset client, add opaque AS, verify area flushing") + + p.send_signal(signal.SIGINT) + time.sleep(2) + p.wait() + p = None + + p = r1.popen([apibin, "-v", "add,11,232,3,deadbeaf01234567"]) + input_dict = { + "routerId": "1.0.0.0", + "areas": { + "1.2.3.4": { + "areaLocalOpaqueLsa": [ + { + "lsId": "231.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "lsaAge": 3600, + }, + ], + } + }, + "asExternalOpaqueLsa": [ + { + "lsId": "232.0.0.3", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + }, + ], + } + # Wait for it to show up + assert verify_ospf_database(tgen, r1, input_dict) is None + + input_dict = { + "asExternalOpaqueLsa": [ + { + "linkStateId": "232.0.0.3", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "opaqueData": "deadbeaf01234567", + }, + ] + } + # verify content + json_cmd = "show ip ospf da opaque-as json" + assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None + + step("stop client, verify AS flushing") + + p.send_signal(signal.SIGINT) + time.sleep(2) + p.wait() + p = None + + input_dict = { + "routerId": "1.0.0.0", + "asExternalOpaqueLsa": [ + { + "lsId": "232.0.0.3", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "lsaAge": 3600, + }, + ], + } + # Wait for it to be flushed + assert verify_ospf_database(tgen, r1, input_dict) is None + + step("start client adding opaque domain, verify new sequence number and data") + + # Originate it again + p = r1.popen([apibin, "-v", "add,11,232,3,ebadf00d"]) + input_dict = { + "routerId": "1.0.0.0", + "asExternalOpaqueLsa": [ + { + "lsId": "232.0.0.3", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000002", + }, + ], + } + assert verify_ospf_database(tgen, r1, input_dict) is None + + input_dict = { + "asExternalOpaqueLsa": [ + { + "linkStateId": "232.0.0.3", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000002", + "opaqueData": "ebadf00d", + }, + ] + } + # verify content + json_cmd = "show ip ospf da opaque-as json" + assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None + + logging.debug("sending interrupt to writer api client") + p.send_signal(signal.SIGINT) + time.sleep(2) + p.wait() + p = None + + except Exception: + if p: + p.terminate() + if p.wait(): + comm_error(p) + p = None + raise + finally: + if p: + logging.debug("cleanup: sending interrupt to writer api client") + p.terminate() + p.wait() + + +@pytest.mark.parametrize("tgen", [2], indirect=True) +def test_ospf_opaque_add_data3(tgen): + apibin = os.path.join(CLIENTDIR, "ospfclient.py") + rc, o, e = tgen.gears["r2"].net.cmd_status([apibin, "--help"]) + logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin, rc, o, e) + _test_add_data(tgen, apibin) + + +def _test_opaque_add_del(tgen, apibin): + "Test adding opaque data to domain" + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + p = None + pread = None + # Log to our stdin, stderr + pout = open(os.path.join(r1.net.logdir, "r1/add-del.log"), "a+") + try: + step("reachable: check for add notification") + pread = r2.popen( + ["/usr/bin/timeout", "120", apibin, "-v", "--logtag=READER", "wait,120"], + encoding=None, # don't buffer + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + p = r1.popen( + [ + apibin, + "-v", + "add,9,10.0.1.1,230,1", + "add,9,10.0.1.1,230,2,00000202", + "wait,1", + "add,10,1.2.3.4,231,1", + "add,10,1.2.3.4,231,2,0102030405060708", + "wait,1", + "add,11,232,1", + "add,11,232,2,ebadf00d", + "wait,20", + "del,9,10.0.1.1,230,2,0", + "del,10,1.2.3.4,231,2,1", + "del,11,232,1,1", + ] + ) + add_input_dict = { + "areas": { + "1.2.3.4": { + "linkLocalOpaqueLsa": [ + { + "lsId": "230.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "checksum": "76bf", + }, + { + "lsId": "230.0.0.2", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "checksum": "8aa2", + }, + ], + "linkLocalOpaqueLsaCount": 2, + "areaLocalOpaqueLsa": [ + { + "lsId": "231.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "checksum": "5bd8", + }, + { + "lsId": "231.0.0.2", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "checksum": "7690", + }, + ], + "areaLocalOpaqueLsaCount": 2, + }, + }, + "asExternalOpaqueLsa": [ + { + "lsId": "232.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "checksum": "5ed5", + }, + { + "lsId": "232.0.0.2", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "checksum": "d9bd", + }, + ], + "asExternalOpaqueLsaCount": 2, + } + + step("reachable: check for add LSAs") + json_cmd = "show ip ospf da json" + assert verify_ospf_database(tgen, r1, add_input_dict, json_cmd) is None + assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None + + numcs = 3 + json_cmds = [ + "show ip ospf da opaque-link json", + "show ip ospf da opaque-area json", + "show ip ospf da opaque-as json", + ] + add_detail_input_dict = [ + { + "linkLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "linkStateId": "230.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "76bf", + "length": 20, + "opaqueDataLength": 0, + }, + { + "linkStateId": "230.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "8aa2", + "length": 24, + "opaqueId": 2, + "opaqueDataLength": 4, + }, + ] + } + } + }, + { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "linkStateId": "231.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "5bd8", + "length": 20, + "opaqueDataLength": 0, + }, + { + "linkStateId": "231.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "7690", + "length": 28, + "opaqueDataLength": 8, + }, + ], + }, + }, + }, + { + "asExternalOpaqueLsa": [ + { + "linkStateId": "232.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "5ed5", + "length": 20, + "opaqueDataLength": 0, + }, + { + "linkStateId": "232.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "d9bd", + "length": 24, + "opaqueDataLength": 4, + }, + ], + }, + ] + i = 0 + while i < numcs: + step("reachable: check for add LSA details: %s" % json_cmds[i]) + assert ( + verify_ospf_database(tgen, r1, add_detail_input_dict[i], json_cmds[i]) + is None + ) + assert ( + verify_ospf_database(tgen, r2, add_detail_input_dict[i], json_cmds[i]) + is None + ) + i += 1 + + # Wait for add notification + # RECV: LSA update msg for LSA 232.0.0.3 in area 0.0.0.0 seq 0x80000001 len 24 age 9 + + ls_ids = [ + "230.0.0.1", + "230.0.0.2", + "231.0.0.1", + "231.0.0.2", + "232.0.0.1", + "232.0.0.2", + ] + for ls_id in ls_ids: + step("reachable: check for API add notification: %s" % ls_id) + waitfor = "RECV:.*update msg.*LSA {}.*age ([0-9]+)".format(ls_id) + _ = _wait_output(pread, waitfor) + + del_input_dict = { + "areas": { + "1.2.3.4": { + "linkLocalOpaqueLsa": [ + { + "lsId": "230.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "checksum": "76bf", + }, + { + "lsId": "230.0.0.2", + "advertisedRouter": "1.0.0.0", + "lsaAge": 3600, + "sequenceNumber": "80000001", + "checksum": "8aa2", + }, + ], + "linkLocalOpaqueLsaCount": 2, + "areaLocalOpaqueLsa": [ + { + "lsId": "231.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "checksum": "5bd8", + }, + { + "lsId": "231.0.0.2", + "advertisedRouter": "1.0.0.0", + "lsaAge": 3600, + "sequenceNumber": "80000002", + "checksum": "4fe2", + }, + ], + "areaLocalOpaqueLsaCount": 2, + }, + }, + "asExternalOpaqueLsa": [ + { + "lsId": "232.0.0.1", + "advertisedRouter": "1.0.0.0", + "lsaAge": 3600, + "sequenceNumber": "80000001", + "checksum": "5ed5", + }, + { + "lsId": "232.0.0.2", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + "checksum": "d9bd", + }, + ], + "asExternalOpaqueLsaCount": 2, + } + + step("reachable: check for explicit withdrawal LSAs") + json_cmd = "show ip ospf da json" + assert verify_ospf_database(tgen, r1, del_input_dict, json_cmd) is None + assert verify_ospf_database(tgen, r2, del_input_dict, json_cmd) is None + + del_detail_input_dict = [ + { + "linkLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "linkStateId": "230.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "76bf", + "length": 20, + "opaqueDataLength": 0, + }, + { + "linkStateId": "230.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaAge": 3600, + "lsaSeqNumber": "80000001", + "checksum": "8aa2", + "length": 24, + "opaqueId": 2, + "opaqueDataLength": 4, + }, + ] + } + } + }, + { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "linkStateId": "231.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "5bd8", + "length": 20, + "opaqueDataLength": 0, + }, + { + "lsaAge": 3600, + "linkStateId": "231.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000002", + "checksum": "4fe2", + # data removed + "length": 20, + "opaqueDataLength": 0, + }, + ], + }, + }, + }, + { + "asExternalOpaqueLsa": [ + { + "linkStateId": "232.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaAge": 3600, + "lsaSeqNumber": "80000001", + "checksum": "5ed5", + "length": 20, + "opaqueDataLength": 0, + }, + { + "linkStateId": "232.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "d9bd", + "length": 24, + "opaqueDataLength": 4, + }, + ], + }, + ] + i = 0 + while i < numcs: + step("reachable: check for delete LSA details: %s" % json_cmds[i]) + assert ( + verify_ospf_database(tgen, r1, del_detail_input_dict[i], json_cmds[i]) + is None + ) + assert ( + verify_ospf_database(tgen, r2, del_detail_input_dict[i], json_cmds[i]) + is None + ) + i += 1 + + p.terminate() + if p.wait(): + comm_error(p) + + del_detail_input_dict = [ + { + "linkLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "linkStateId": "230.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaAge": 3600, + "lsaSeqNumber": "80000001", + "checksum": "76bf", + "length": 20, + "opaqueDataLength": 0, + }, + { + "linkStateId": "230.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaAge": 3600, + "lsaSeqNumber": "80000001", + "checksum": "8aa2", + "length": 24, + "opaqueId": 2, + "opaqueDataLength": 4, + }, + ] + } + } + }, + { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "lsaAge": 3600, + "linkStateId": "231.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "5bd8", + "length": 20, + "opaqueDataLength": 0, + }, + { + "lsaAge": 3600, + "linkStateId": "231.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000002", + "checksum": "4fe2", + # data removed + "length": 20, + "opaqueDataLength": 0, + }, + ], + }, + }, + }, + { + "asExternalOpaqueLsa": [ + { + "linkStateId": "232.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaAge": 3600, + "lsaSeqNumber": "80000001", + "checksum": "5ed5", + "length": 20, + "opaqueDataLength": 0, + }, + { + "linkStateId": "232.0.0.2", + "advertisingRouter": "1.0.0.0", + "lsaAge": 3600, + "lsaSeqNumber": "80000001", + "checksum": "d9bd", + "length": 24, + "opaqueDataLength": 4, + }, + ], + }, + ] + i = 0 + while i < numcs: + step( + "reachable: check for post API shutdown delete LSA details: %s" + % json_cmds[i] + ) + assert ( + verify_ospf_database(tgen, r1, del_detail_input_dict[i], json_cmds[i]) + is None + ) + assert ( + verify_ospf_database(tgen, r2, del_detail_input_dict[i], json_cmds[i]) + is None + ) + i += 1 + + # step("reachable: check for flush/age out") + # # Wait for max age notification + # waitfor = "RECV:.*update msg.*LSA {}.*age 3600".format(ls_id) + # _wait_output(pread, waitfor) + ls_ids = [ + "230.0.0.2", + "231.0.0.2", + "232.0.0.1", + "230.0.0.1", + "231.0.0.1", + "232.0.0.2", + ] + for ls_id in ls_ids: + step("reachable: check for API delete notification: %s" % ls_id) + waitfor = "RECV:.*delete msg.*LSA {}.*age".format(ls_id) + _ = _wait_output(pread, waitfor) + except Exception: + if p: + p.terminate() + if p.wait(): + comm_error(p) + p = None + raise + finally: + if pread: + pread.terminate() + pread.wait() + if p: + p.terminate() + p.wait() + + +@pytest.mark.parametrize("tgen", [2], indirect=True) +def test_ospf_opaque_delete_data3(tgen): + apibin = os.path.join(CLIENTDIR, "ospfclient.py") + rc, o, e = tgen.gears["r2"].net.cmd_status([apibin, "--help"]) + logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin, rc, o, e) + _test_opaque_add_del(tgen, apibin) + + +def _test_opaque_add_restart_add(tgen, apibin): + "Test adding an opaque LSA and then restarting ospfd" + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + p = None + pread = None + # Log to our stdin, stderr + pout = open(os.path.join(r1.net.logdir, "r1/add-del.log"), "a+") + try: + step("reachable: check for add notification") + pread = r2.popen( + ["/usr/bin/timeout", "120", apibin, "-v", "--logtag=READER", "wait,120"], + encoding=None, # don't buffer + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + p = r1.popen( + [ + apibin, + "-v", + "add,10,1.2.3.4,231,1", # seq = 80000001 + "wait, 5", + "add,10,1.2.3.4,231,1,feedaceebeef", # seq = 80000002 + "wait, 5", + ] + ) + add_input_dict = { + "areas": { + "1.2.3.4": { + "areaLocalOpaqueLsa": [ + { + "lsId": "231.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000002", + "checksum": "cd26", + }, + ], + "areaLocalOpaqueLsaCount": 1, + }, + }, + } + step("Wait for the Opaque LSA to be distributed") + json_cmd = "show ip ospf da json" + assert verify_ospf_database(tgen, r1, add_input_dict, json_cmd) is None + assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None + + step("Shutdown the interface on r1 to isolate it for r2") + shutdown_bringup_interface(tgen, "r1", "r1-eth0", False) + + time.sleep(2) + step("Reset the client") + p.send_signal(signal.SIGINT) + time.sleep(2) + p.wait() + p = None + + # Verify the OLD LSA is still there unchanged on R2 + assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None + + step("Kill ospfd on R1") + kill_router_daemons(tgen, "r1", ["ospfd"]) + time.sleep(2) + + step("Bring ospfd on R1 back up") + start_router_daemons(tgen, "r1", ["ospfd"]) + + # This will start off with sequence num 80000001 + # But should advance to 80000003 when we reestablish with r2 + p = r1.popen( + [ + apibin, + "-v", + "add,10,1.2.3.4,231,1,feedaceecafebeef", # seq=80000001 + "wait, 5", + ] + ) + + # verify the old value on r2 doesn't change yet + time.sleep(2) + assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None + + json_cmd = "show ip ospf da opaque-area json" + new_add_input_dict = { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "linkStateId": "231.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000001", + "checksum": "b07a", + "length": 28, + "opaqueDataLength": 8, + }, + ], + }, + }, + } + # verify new value with initial seq number on r1 + assert verify_ospf_database(tgen, r1, new_add_input_dict, json_cmd) is None + + step("Bring the interface on r1 back up for connection to r2") + shutdown_bringup_interface(tgen, "r1", "r1-eth0", True) + + step("Verify area opaque LSA refresh") + + # Update the expected value to sequence number rev and new checksum + update_dict = new_add_input_dict["areaLocalOpaqueLsa"]["areas"]["1.2.3.4"][0] + update_dict["lsaSeqNumber"] = "80000003" + update_dict["checksum"] = "cb27" + + # should settle on the same value now. + assert verify_ospf_database(tgen, r1, new_add_input_dict, json_cmd) is None + assert verify_ospf_database(tgen, r2, new_add_input_dict, json_cmd) is None + + step("Shutdown the interface on r1 to isolate it for r2") + shutdown_bringup_interface(tgen, "r1", "r1-eth0", False) + + time.sleep(2) + step("Reset the client") + p.send_signal(signal.SIGINT) + time.sleep(2) + p.wait() + p = None + + step("Kill ospfd on R1") + kill_router_daemons(tgen, "r1", ["ospfd"]) + time.sleep(2) + + step("Bring ospfd on R1 back up") + start_router_daemons(tgen, "r1", ["ospfd"]) + + step("Bring the interface on r1 back up for connection to r2") + shutdown_bringup_interface(tgen, "r1", "r1-eth0", True) + + step("Verify area opaque LSA Purging") + json_cmd = "show ip ospf da opaque-area json" + add_detail_input_dict = { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "lsaAge": 3600, + "linkStateId": "231.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000003", + "checksum": "cb27", + "length": 28, + "opaqueDataLength": 8, + }, + ], + }, + }, + } + assert verify_ospf_database(tgen, r1, add_detail_input_dict, json_cmd) is None + assert verify_ospf_database(tgen, r2, add_detail_input_dict, json_cmd) is None + step("Verify Area Opaque LSA removal after timeout (60 seconds)") + time.sleep(60) + json_cmd = "show ip ospf da opaque-area json" + timeout_detail_input_dict = { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [], + }, + }, + } + assert ( + verify_ospf_database(tgen, r1, timeout_detail_input_dict, json_cmd) is None + ) + assert ( + verify_ospf_database(tgen, r2, timeout_detail_input_dict, json_cmd) is None + ) + + except Exception: + if p: + p.terminate() + if p.wait(): + comm_error(p) + p = None + raise + finally: + if pread: + pread.terminate() + pread.wait() + if p: + p.terminate() + p.wait() + + +@pytest.mark.parametrize("tgen", [2], indirect=True) +def test_ospf_opaque_restart(tgen): + apibin = os.path.join(CLIENTDIR, "ospfclient.py") + rc, o, e = tgen.gears["r2"].net.cmd_status([apibin, "--help"]) + logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin, rc, o, e) + _test_opaque_add_restart_add(tgen, apibin) + + +def _test_opaque_interface_disable(tgen, apibin): + "Test disabling opaque capability on an interface" + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + tc_name = "opaque_interface_disable" + + p = None + pread = None + # Log to our stdin, stderr + pout = open(os.path.join(r1.net.logdir, "r1/intf-disable.log"), "a+") + try: + # STEP 1 in test_ospf_opaque_interface_disable and STEP 56 in CI tests + step("Disable OSPF opaque LSA Copability on r1's interface to r2") + r1.vtysh_multicmd("conf t\ninterface r1-eth0\nno ip ospf capability opaque") + time.sleep(15) + + # STEP 2 in test_ospf_opaque_interface_disable and STEP 57 in CI tests + step("Verify the r1 configuration of 'no ip ospf capability opaque'") + no_capability_opaque_cfg = ( + tgen.net["r1"] + .cmd( + 'vtysh -c "show running ospfd" | grep "^ no ip ospf capability opaque"' + ) + .rstrip() + ) + assertmsg = ( + "'no ip ospf capability opaque' applied, but not present in configuration" + ) + assert no_capability_opaque_cfg == " no ip ospf capability opaque", assertmsg + + # STEP 3 in test_ospf_opaque_interface_disable and STEP 58 in CI tests + step("Verify the ospf opaque option is not applied to the r1 interface") + r1_interface_without_opaque = { + "interfaces": { + "r1-eth0": { + "ifUp": True, + "ospfEnabled": True, + "ipAddress": "10.0.1.1", + "ospfIfType": "Broadcast", + "opaqueCapable": False, + } + } + } + r1_interface_with_opaque = { + "interfaces": { + "r1-eth0": { + "ifUp": True, + "ospfEnabled": True, + "ipAddress": "10.0.1.1", + "ospfIfType": "Broadcast", + "opaqueCapable": True, + } + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf interface json", r1_interface_without_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r1 OSPF interface doesn't have opaque capability disabled" + assert result is None, assertmsg + + r1_neighbor_without_opaque = { + "neighbors": { + "2.0.0.0": [ + { + "optionsList": "*|-|-|-|-|-|E|-", + } + ] + } + } + r2_neighbor_without_opaque = { + "neighbors": { + "1.0.0.0": [ + { + "optionsList": "*|-|-|-|-|-|E|-", + } + ] + } + } + # STEP 4 in test_ospf_opaque_interface_disable and STEP 59 in CI tests + step("Verify that the r1 neighbor options don't include opaque") + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf neighbor detail json", r1_neighbor_without_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r1 OSPF neighbor has opaque option in optionsList" + assert result is None, assertmsg + + # STEP 5 in test_ospf_opaque_interface_disable and STEP 60 in CI tests + step("Verify that the r1 neighbor options don't include opaque") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf neighbor detail json", r2_neighbor_without_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF neighbor has opaque option in optionsList" + assert result is None, assertmsg + + # STEP 6 in test_ospf_opaque_interface_disable and STEP 61 in CI tests + step( + "Verify no r2 configuration of 'no ip ospf capability opaque' in r2 configuration" + ) + rc, _, _ = tgen.net["r2"].cmd_status( + "show running ospfd | grep -q 'ip ospf capability opaque'", warn=False + ) + assertmsg = "'no ip ospf capability opaque' not applied, but not present in r2 configuration" + assert rc, assertmsg + + # STEP 7 in test_ospf_opaque_interface_disable and STEP 62 in CI tests + step("Verify the ospf opaque option is applied to the r2 interface") + r2_interface_without_opaque = { + "interfaces": { + "r2-eth0": { + "ifUp": True, + "ospfEnabled": True, + "ipAddress": "10.0.1.2", + "ospfIfType": "Broadcast", + "opaqueCapable": False, + } + } + } + r2_interface_with_opaque = { + "interfaces": { + "r2-eth0": { + "ifUp": True, + "ospfEnabled": True, + "ipAddress": "10.0.1.2", + "ospfIfType": "Broadcast", + "opaqueCapable": True, + } + } + } + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf interface json", r2_interface_with_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF interface has opaque capability disabled" + assert result is None, assertmsg + + # STEP 8 in test_ospf_opaque_interface_disable and STEP 63 in CI tests + step("Install opaque LSAs on r1") + pread = r2.popen( + ["/usr/bin/timeout", "120", apibin, "-v", "--logtag=READER", "wait,120"], + encoding=None, # don't buffer + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + p = r1.popen( + [ + apibin, + "-v", + "add,9,10.0.1.1,230,1,feedaceedeadbeef", + "add,10,1.2.3.4,231,1,feedaceecafebeef", + "add,11,232,1,feedaceebaddbeef", + "wait,20", + ] + ) + opaque_LSAs_in_database = { + "areas": { + "1.2.3.4": { + "linkLocalOpaqueLsa": [ + { + "lsId": "230.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + }, + ], + "linkLocalOpaqueLsaCount": 1, + "areaLocalOpaqueLsa": [ + { + "lsId": "231.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + }, + ], + "areaLocalOpaqueLsaCount": 1, + }, + }, + "asExternalOpaqueLsa": [ + { + "lsId": "232.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000001", + }, + ], + "asExternalOpaqueLsaCount": 1, + } + opaque_area_empty_database = { + "routerId":"2.0.0.0", + "areaLocalOpaqueLsa":{ + "areas":{ + "1.2.3.4":[ + ] + } + } + } + + # STEP 9 in test_ospf_opaque_interface_disable and STEP 64 in CI tests + step("Check that LSAs are added on r1") + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf database json", opaque_LSAs_in_database + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r1 OSPF database doesn't contain opaque LSAs" + assert result is None, assertmsg + + # STEP 10 in test_ospf_opaque_interface_disable and STEP 65 in CI tests + step("Check that LSAs are not added on r2") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf database opaque-area json", + opaque_area_empty_database, True + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF area database contains opaque LSAs" + assert result is None, assertmsg + + # STEP 11 in test_ospf_opaque_interface_disable and STEP 66 in CI tests + step("Enable OSPF opaque LSA Copability on r1's interface to r2") + r1.vtysh_multicmd("conf t\ninterface r1-eth0\nip ospf capability opaque") + time.sleep(15) + + # STEP 12 in test_ospf_opaque_interface_disable and STEP 67 in CI tests + step("Verify no r1 configuration of 'no ip ospf capability opaque'") + rc, _, _ = tgen.net["r1"].cmd_status( + "show running ospfd | grep -q 'ip ospf capability opaque'", warn=False + ) + assertmsg = "'no ip ospf capability opaque' not applied, but not present in r1 configuration" + assert rc, assertmsg + + # STEP 13 in test_ospf_opaque_interface_disable and STEP 68 in CI tests + step("Verify the ospf opaque option is applied to the r1 interface") + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf interface json", r1_interface_with_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r1 OSPF interface doesn't have opaque capability disabled" + assert result is None, assertmsg + + r1_neighbor_with_opaque = { + "neighbors": { + "2.0.0.0": [ + { + "optionsList": "*|O|-|-|-|-|E|-", + } + ] + } + } + r2_neighbor_with_opaque = { + "neighbors": { + "1.0.0.0": [ + { + "optionsList": "*|O|-|-|-|-|E|-", + } + ] + } + } + # STEP 14 in test_ospf_opaque_interface_disable and STEP 69 in CI tests + step("Verify that the r1 neighbor options include opaque") + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf neighbor detail json", r1_neighbor_with_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r1 OSPF neighbor doesn't have opaque option in optionsList" + assert result is None, assertmsg + + # STEP 15 in test_ospf_opaque_interface_disable and STEP 70 in CI tests + step("Verify that the r2 neighbor options include opaque") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf neighbor detail json", r2_neighbor_with_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF neighbor doesn't have opaque option in optionsList" + assert result is None, assertmsg + + # STEP 16 in test_ospf_opaque_interface_disable and STEP 71 in CI tests + step("Check that LSAs are now added to r2") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf database json", opaque_LSAs_in_database + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF database doesn't contains opaque LSAs" + assert result is None, assertmsg + + # STEP 17 in test_ospf_opaque_interface_disable and STEP 72 in CI tests + step( + "Disable Opaque Capability on r2's interface to r1 using the interface address" + ) + r2.vtysh_multicmd( + "conf t\ninterface r2-eth0\nno ip ospf capability opaque 10.0.1.2" + ) + + # STEP 18 in test_ospf_opaque_interface_disable and STEP 73 in CI tests + step("Clear the OSPF process on r2 to clear the OSPF LSDB") + r2.vtysh_multicmd("clear ip ospf process") + time.sleep(15) + + # STEP 19 in test_ospf_opaque_interface_disable and STEP 74 in CI tests + step("Verify the r2 configuration of 'no ip ospf capability opaque 10.0.1.2'") + no_capability_opaque_cfg = ( + tgen.net["r2"] + .cmd_nostatus( + 'vtysh -c "show running ospfd" | grep "^ no ip ospf capability opaque 10.0.1.2"' + ) + .rstrip() + ) + assertmsg = "'no ip ospf capability opaque 10.0.1.2' applied, but not present in configuration" + assert ( + no_capability_opaque_cfg == " no ip ospf capability opaque 10.0.1.2" + ), assertmsg + + # STEP 20 in test_ospf_opaque_interface_disable and STEP 75 in CI tests + step("Verify the ospf opaque option is not applied to the r2 interface") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf interface json", r2_interface_without_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r1 OSPF interface doesn't have opaque capability disabled" + assert result is None, assertmsg + + # STEP 21 in test_ospf_opaque_interface_disable and STEP 76 in CI tests + step("Verify that the r1 neighbor options don't include opaque") + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf neighbor detail json", r1_neighbor_without_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r1 OSPF neighbor has opaque option in optionsList" + assert result is None, assertmsg + + # STEP 22 in test_ospf_opaque_interface_disable and STEP 77 in CI tests + step("Verify that the r2 neighbor options don't include opaque") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf neighbor detail json", r2_neighbor_without_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF neighbor has opaque option in optionsList" + assert result is None, assertmsg + + # STEP 23 in test_ospf_opaque_interface_disable and STEP 78 in CI tests + step("Verify that r1 still has the opaque LSAs") + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf database json", opaque_LSAs_in_database + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r1 OSPF database doesn't contain opaque LSAs" + assert result is None, assertmsg + + # STEP 24 in test_ospf_opaque_interface_disable and STEP 79 in CI tests + step("Verify that r2 doesn't have the opaque LSAs") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf database opaque-area json", + opaque_area_empty_database, True + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF area database contains opaque LSAs" + assert result is None, assertmsg + + # STEP 25 in test_ospf_opaque_interface_disable and STEP 80 in CI tests + step("Remove the 'no ip ospf capability opaque 10.0.1.2' config from r2 ") + r2.vtysh_multicmd( + "conf t\ninterface r2-eth0\nip ospf capability opaque 10.0.1.2" + ) + time.sleep(15) + + # STEP 26 in test_ospf_opaque_interface_disable and STEP 81 in CI tests + step("Verify the r2 removal of 'no ip ospf capability opaque 10.0.1.2'") + rc, _, _ = tgen.net["r2"].cmd_status( + "show running ospfd | grep -q 'ip ospf capability opaque'", warn=False + ) + assertmsg = "'no ip ospf capability opaque' not applied, but not present in r2 configuration" + assert rc, assertmsg + + # STEP 27 in test_ospf_opaque_interface_disable and STEP 82 in CI tests + step("Verify the ospf opaque option is applied to the r2 interface") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf interface json", r2_interface_with_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF interface doesn't have opaque capability disabled" + assert result is None, assertmsg + + # STEP 28 in test_ospf_opaque_interface_disable and STEP 83 in CI tests + step("Verify that the r2 neighbor options include opaque") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf neighbor detail json", r2_neighbor_with_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF neighbor doesn't have opaque option in optionsList" + assert result is None, assertmsg + + # STEP 29 in test_ospf_opaque_interface_disable and STEP 84 in CI tests + step("Verify that the r1 neighbor options include opaque") + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf neighbor detail json", r1_neighbor_with_opaque + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r1 OSPF neighbor doesn't have opaque option in optionsList" + assert result is None, assertmsg + + # STEP 30 in test_ospf_opaque_interface_disable and STEP 85 in CLI tests + step("Verify that r2 now has the opaque LSAs") + test_func = partial( + topotest.router_json_cmp, r2, "show ip ospf database json", opaque_LSAs_in_database + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "r2 OSPF database doesn't contain opaque LSAs" + assert result is None, assertmsg + + except Exception: + if p: + p.terminate() + if p.wait(): + comm_error(p) + p = None + raise + finally: + if pread: + pread.terminate() + pread.wait() + if p: + p.terminate() + p.wait() + + +@pytest.mark.parametrize("tgen", [2], indirect=True) +def test_ospf_opaque_interface_disable(tgen): + apibin = os.path.join(CLIENTDIR, "ospfclient.py") + rc, o, e = tgen.gears["r2"].net.cmd_status([apibin, "--help"]) + logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin, rc, o, e) + _test_opaque_interface_disable(tgen, apibin) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) |