summaryrefslogtreecommitdiffstats
path: root/tests/topotests/ospfapi
diff options
context:
space:
mode:
Diffstat (limited to 'tests/topotests/ospfapi')
-rwxr-xr-xtests/topotests/ospfapi/ctester.py154
l---------tests/topotests/ospfapi/lib1
-rw-r--r--tests/topotests/ospfapi/r1/ospfd.conf15
-rw-r--r--tests/topotests/ospfapi/r1/zebra.conf6
-rw-r--r--tests/topotests/ospfapi/r2/ospfd.conf15
-rw-r--r--tests/topotests/ospfapi/r2/zebra.conf7
-rw-r--r--tests/topotests/ospfapi/r3/ospfd.conf15
-rw-r--r--tests/topotests/ospfapi/r3/zebra.conf7
-rw-r--r--tests/topotests/ospfapi/r4/ospfd.conf15
-rw-r--r--tests/topotests/ospfapi/r4/zebra.conf6
-rw-r--r--tests/topotests/ospfapi/test_ospf_clientapi.py1586
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))