#!/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", "opaqueValues": {"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", "opaqueValues": { "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", "opaqueValues": { "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", "opaqueValues": { "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, "opaqueLength": 0, }, { "linkStateId": "230.0.0.2", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000001", "checksum": "8aa2", "length": 24, "opaqueId": 2, "opaqueLength": 4, }, ] } } }, { "areaLocalOpaqueLsa": { "areas": { "1.2.3.4": [ { "linkStateId": "231.0.0.1", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000001", "checksum": "5bd8", "length": 20, "opaqueLength": 0, }, { "linkStateId": "231.0.0.2", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000001", "checksum": "7690", "length": 28, "opaqueLength": 8, }, ], }, }, }, { "asExternalOpaqueLsa": [ { "linkStateId": "232.0.0.1", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000001", "checksum": "5ed5", "length": 20, "opaqueLength": 0, }, { "linkStateId": "232.0.0.2", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000001", "checksum": "d9bd", "length": 24, "opaqueLength": 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, "opaqueLength": 0, }, { "linkStateId": "230.0.0.2", "advertisingRouter": "1.0.0.0", "lsaAge": 3600, "lsaSeqNumber": "80000001", "checksum": "8aa2", "length": 24, "opaqueId": 2, "opaqueLength": 4, }, ] } } }, { "areaLocalOpaqueLsa": { "areas": { "1.2.3.4": [ { "linkStateId": "231.0.0.1", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000001", "checksum": "5bd8", "length": 20, "opaqueLength": 0, }, { "lsaAge": 3600, "linkStateId": "231.0.0.2", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000002", "checksum": "4fe2", # data removed "length": 20, "opaqueLength": 0, }, ], }, }, }, { "asExternalOpaqueLsa": [ { "linkStateId": "232.0.0.1", "advertisingRouter": "1.0.0.0", "lsaAge": 3600, "lsaSeqNumber": "80000001", "checksum": "5ed5", "length": 20, "opaqueLength": 0, }, { "linkStateId": "232.0.0.2", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000001", "checksum": "d9bd", "length": 24, "opaqueLength": 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, "opaqueLength": 0, }, { "linkStateId": "230.0.0.2", "advertisingRouter": "1.0.0.0", "lsaAge": 3600, "lsaSeqNumber": "80000001", "checksum": "8aa2", "length": 24, "opaqueId": 2, "opaqueLength": 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, "opaqueLength": 0, }, { "lsaAge": 3600, "linkStateId": "231.0.0.2", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000002", "checksum": "4fe2", # data removed "length": 20, "opaqueLength": 0, }, ], }, }, }, { "asExternalOpaqueLsa": [ { "linkStateId": "232.0.0.1", "advertisingRouter": "1.0.0.0", "lsaAge": 3600, "lsaSeqNumber": "80000001", "checksum": "5ed5", "length": 20, "opaqueLength": 0, }, { "linkStateId": "232.0.0.2", "advertisingRouter": "1.0.0.0", "lsaAge": 3600, "lsaSeqNumber": "80000001", "checksum": "d9bd", "length": 24, "opaqueLength": 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, "opaqueLength": 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, "opaqueLength": 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) def _test_opaque_link_local_lsa_crash(tgen, apibin): "Test disabling opaque capability on an interface" r1 = tgen.gears["r1"] r2 = tgen.gears["r2"] tc_name = "opaque_interface_disable" p = None # Log to our stdin, stderr pout = open(os.path.join(r1.net.logdir, "r1/intf-disable.log"), "a+") try: step("Add a link-local opaque LSA for r1-eth0") pread = r1.popen([apibin, "-v", "add,9,10.0.1.1,230,1,feedaceedeadbeef"]) input_dict = { "linkLocalOpaqueLsa": { "areas": { "1.2.3.4": [ { "linkStateId": "230.0.0.1", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000001", "opaqueValues": { "opaqueData": "feedaceedeadbeef", }, }, ], } }, } # verify content json_cmd = "show ip ospf da opaque-link json" assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None step("Shut down r1-eth0 and verify there is no crash") r1.vtysh_multicmd("conf t\ninterface r1-eth0\nshut") time.sleep(2) step("Bring r1-eth0 back up and verify there is no crash") r1.vtysh_multicmd("conf t\ninterface r1-eth0\nno shut") step("Add another link-local opaque LSA for r1-eth0") pread = r1.popen([apibin, "-v", "add,9,10.0.1.1,230,1,feedaceecafebeef"]) input_dict = { "linkLocalOpaqueLsa": { "areas": { "1.2.3.4": [ { "linkStateId": "230.0.0.1", "advertisingRouter": "1.0.0.0", "lsaSeqNumber": "80000001", "opaqueValues": { "opaqueData": "feedaceecafebeef", }, }, ], } }, } # verify content json_cmd = "show ip ospf da opaque-link json" assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None except Exception: if p: p.terminate() if p.wait(): comm_error(p) p = None raise finally: if p: p.terminate() p.wait() p = None @pytest.mark.parametrize("tgen", [2], indirect=True) def test_ospf_opaque_link_local_lsa_crash(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_link_local_lsa_crash(tgen, apibin) if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args))