]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/ospfapi/test_ospf_clientapi.py
tests: ospfapi: fix non-determinism in test
[mirror_frr.git] / tests / topotests / ospfapi / test_ospf_clientapi.py
CommitLineData
ad9c18f3
CH
1#!/usr/bin/env python
2# -*- coding: utf-8 eval: (blacken-mode 1) -*-
acddc0ed 3# SPDX-License-Identifier: GPL-2.0-or-later
ad9c18f3 4#
9495c405 5# Copyright (c) 2021-2022, LabN Consulting, L.L.C.
ad9c18f3 6#
ad9c18f3
CH
7
8"""
9test_ospf_clientapi.py: Test the OSPF client API.
10"""
11
12import logging
13import os
14import re
15import signal
16import subprocess
17import sys
18import time
19from datetime import datetime, timedelta
20
21import pytest
4e7eb1e6 22from lib.common_config import (
39e1f084 23 kill_router_daemons,
4e7eb1e6
A
24 retry,
25 run_frr_cmd,
4e7eb1e6 26 shutdown_bringup_interface,
39e1f084
CH
27 start_router_daemons,
28 step,
4e7eb1e6 29)
663a0c96 30from lib.micronet import Timeout, comm_error
ad9c18f3
CH
31from lib.topogen import Topogen, TopoRouter
32from lib.topotest import interface_set_status, json_cmp
33
34pytestmark = [pytest.mark.ospfd]
35
36CWD = os.path.dirname(os.path.realpath(__file__))
37TESTDIR = os.path.abspath(CWD)
38
39CLIENTDIR = os.path.abspath(os.path.join(CWD, "../../../ospfclient"))
40if not os.path.exists(CLIENTDIR):
41 CLIENTDIR = os.path.join(CWD, "/usr/lib/frr")
42
43assert os.path.exists(
44 os.path.join(CLIENTDIR, "ospfclient.py")
45), "can't locate ospfclient.py"
46
47
48# ----------
49# Test Setup
50# ----------
51
663a0c96
CH
52#
53# r1 - r2
54# | |
55# r4 - r3
56#
57
ad9c18f3
CH
58
59@pytest.fixture(scope="function", name="tgen")
60def _tgen(request):
61 "Setup/Teardown the environment and provide tgen argument to tests"
62 nrouters = request.param
663a0c96
CH
63 topodef = {f"sw{i}": (f"r{i}", f"r{i+1}") for i in range(1, nrouters)}
64 if nrouters == 4:
65 topodef["sw4"] = ("r4", "r1")
ad9c18f3
CH
66
67 tgen = Topogen(topodef, request.module.__name__)
68 tgen.start_topology()
69
70 router_list = tgen.routers()
71 for _, router in router_list.items():
72 router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf")
73 router.load_config(TopoRouter.RD_OSPF, "ospfd.conf")
74 router.net.daemons_options["ospfd"] = "--apiserver"
75
76 tgen.start_router()
77
78 yield tgen
79
80 tgen.stop_topology()
81
82
83# Fixture that executes before each test
84@pytest.fixture(autouse=True)
85def skip_on_failure(tgen):
86 if tgen.routers_have_failure():
87 pytest.skip("skipped because of previous test failure")
88
89
90# ------------
91# Test Utility
92# ------------
93
94
95@retry(retry_timeout=45)
96def verify_ospf_database(tgen, dut, input_dict, cmd="show ip ospf database json"):
97 del tgen
98 show_ospf_json = run_frr_cmd(dut, cmd, isjson=True)
99 if not bool(show_ospf_json):
100 return "ospf is not running"
101 result = json_cmp(show_ospf_json, input_dict)
102 return str(result) if result else None
103
104
105def myreadline(f):
106 buf = b""
107 while True:
663a0c96 108 # logging.debug("READING 1 CHAR")
ad9c18f3
CH
109 c = f.read(1)
110 if not c:
111 return buf if buf else None
112 buf += c
663a0c96 113 # logging.debug("READ CHAR: '%s'", c)
ad9c18f3
CH
114 if c == b"\n":
115 return buf
116
117
663a0c96
CH
118def _wait_output(p, regex, maxwait=120):
119 timeout = Timeout(maxwait)
120 while not timeout.is_expired():
ad9c18f3
CH
121 # line = p.stdout.readline()
122 line = myreadline(p.stdout)
123 if not line:
663a0c96 124 assert None, "EOF waiting for '{}'".format(regex)
ad9c18f3
CH
125 line = line.decode("utf-8")
126 line = line.rstrip()
127 if line:
128 logging.debug("GOT LINE: '%s'", line)
129 m = re.search(regex, line)
130 if m:
131 return m
663a0c96
CH
132 assert None, "Failed to get output matching '{}' withint {} actual {}s".format(
133 regex, maxwait, timeout.elapsed()
134 )
ad9c18f3
CH
135
136
137# -----
138# Tests
139# -----
140
141
142def _test_reachability(tgen, testbin):
143 waitlist = [
663a0c96
CH
144 "1.0.0.0,2.0.0.0,4.0.0.0",
145 "2.0.0.0,4.0.0.0",
146 "1.0.0.0,2.0.0.0,4.0.0.0",
ad9c18f3
CH
147 ]
148 r2 = tgen.gears["r2"]
149 r3 = tgen.gears["r3"]
663a0c96 150 r4 = tgen.gears["r4"]
ad9c18f3
CH
151
152 wait_args = [f"--wait={x}" for x in waitlist]
153
154 p = None
155 try:
156 step("reachable: check for initial reachability")
157 p = r3.popen(
158 ["/usr/bin/timeout", "120", testbin, "-v", *wait_args],
159 encoding=None, # don't buffer
160 stdin=subprocess.DEVNULL,
161 stdout=subprocess.PIPE,
162 stderr=subprocess.STDOUT,
163 )
164 _wait_output(p, "SUCCESS: {}".format(waitlist[0]))
165
166 step("reachable: check for modified reachability")
167 interface_set_status(r2, "r2-eth0", False)
663a0c96 168 interface_set_status(r4, "r4-eth1", False)
ad9c18f3
CH
169 _wait_output(p, "SUCCESS: {}".format(waitlist[1]))
170
171 step("reachable: check for restored reachability")
172 interface_set_status(r2, "r2-eth0", True)
663a0c96 173 interface_set_status(r4, "r4-eth1", True)
ad9c18f3
CH
174 _wait_output(p, "SUCCESS: {}".format(waitlist[2]))
175 except Exception as error:
176 logging.error("ERROR: %s", error)
177 raise
178 finally:
179 if p:
180 p.terminate()
181 p.wait()
182
183
184@pytest.mark.parametrize("tgen", [4], indirect=True)
185def test_ospf_reachability(tgen):
186 testbin = os.path.join(TESTDIR, "ctester.py")
187 rc, o, e = tgen.gears["r2"].net.cmd_status([testbin, "--help"])
663a0c96 188 logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", testbin, rc, o, e)
ad9c18f3
CH
189 _test_reachability(tgen, testbin)
190
191
9495c405
CH
192def _test_router_id(tgen, testbin):
193 r1 = tgen.gears["r1"]
194 waitlist = [
663a0c96 195 "1.0.0.0",
9495c405 196 "1.1.1.1",
663a0c96 197 "1.0.0.0",
9495c405
CH
198 ]
199
200 mon_args = [f"--monitor={x}" for x in waitlist]
201
202 p = None
203 try:
204 step("router id: check for initial router id")
205 p = r1.popen(
206 ["/usr/bin/timeout", "120", testbin, "-v", *mon_args],
207 encoding=None, # don't buffer
208 stdin=subprocess.DEVNULL,
209 stdout=subprocess.PIPE,
210 stderr=subprocess.STDOUT,
211 )
212 _wait_output(p, "SUCCESS: {}".format(waitlist[0]))
213
214 step("router id: check for modified router id")
215 r1.vtysh_multicmd("conf t\nrouter ospf\nospf router-id 1.1.1.1")
216 _wait_output(p, "SUCCESS: {}".format(waitlist[1]))
217
218 step("router id: check for restored router id")
663a0c96 219 r1.vtysh_multicmd("conf t\nrouter ospf\nospf router-id 1.0.0.0")
9495c405
CH
220 _wait_output(p, "SUCCESS: {}".format(waitlist[2]))
221 except Exception as error:
222 logging.error("ERROR: %s", error)
223 raise
224 finally:
225 if p:
226 p.terminate()
227 p.wait()
228
229
230@pytest.mark.parametrize("tgen", [2], indirect=True)
231def test_ospf_router_id(tgen):
232 testbin = os.path.join(TESTDIR, "ctester.py")
233 rc, o, e = tgen.gears["r1"].net.cmd_status([testbin, "--help"])
663a0c96 234 logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", testbin, rc, o, e)
9495c405
CH
235 _test_router_id(tgen, testbin)
236
237
ad9c18f3
CH
238def _test_add_data(tgen, apibin):
239 "Test adding opaque data to domain"
240
241 r1 = tgen.gears["r1"]
242
243 step("add opaque: add opaque link local")
244
245 p = None
246 try:
247 p = r1.popen([apibin, "-v", "add,9,10.0.1.1,230,2,00000202"])
248 input_dict = {
663a0c96 249 "routerId": "1.0.0.0",
ad9c18f3
CH
250 "areas": {
251 "1.2.3.4": {
252 "linkLocalOpaqueLsa": [
253 {
254 "lsId": "230.0.0.2",
663a0c96 255 "advertisedRouter": "1.0.0.0",
ad9c18f3
CH
256 "sequenceNumber": "80000001",
257 }
258 ],
259 }
260 },
261 }
262 # Wait for it to show up
263 assert verify_ospf_database(tgen, r1, input_dict) is None
264
265 input_dict = {
266 "linkLocalOpaqueLsa": {
267 "areas": {
268 "1.2.3.4": [
269 {
270 "linkStateId": "230.0.0.2",
663a0c96 271 "advertisingRouter": "1.0.0.0",
ad9c18f3
CH
272 "lsaSeqNumber": "80000001",
273 "opaqueData": "00000202",
274 },
275 ],
276 }
277 },
278 }
279 # verify content
280 json_cmd = "show ip ospf da opaque-link json"
281 assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None
282
283 step("reset client, add opaque area, verify link local flushing")
284
285 p.send_signal(signal.SIGINT)
286 time.sleep(2)
287 p.wait()
288 p = None
289 p = r1.popen([apibin, "-v", "add,10,1.2.3.4,231,1,00010101"])
290 input_dict = {
663a0c96 291 "routerId": "1.0.0.0",
ad9c18f3
CH
292 "areas": {
293 "1.2.3.4": {
294 "linkLocalOpaqueLsa": [
295 {
296 "lsId": "230.0.0.2",
663a0c96 297 "advertisedRouter": "1.0.0.0",
ad9c18f3
CH
298 "sequenceNumber": "80000001",
299 "lsaAge": 3600,
300 }
301 ],
302 "areaLocalOpaqueLsa": [
303 {
304 "lsId": "231.0.0.1",
663a0c96 305 "advertisedRouter": "1.0.0.0",
ad9c18f3
CH
306 "sequenceNumber": "80000001",
307 },
308 ],
309 }
310 },
311 }
312 # Wait for it to show up
313 assert verify_ospf_database(tgen, r1, input_dict) is None
314
315 input_dict = {
316 "areaLocalOpaqueLsa": {
317 "areas": {
318 "1.2.3.4": [
319 {
320 "linkStateId": "231.0.0.1",
663a0c96 321 "advertisingRouter": "1.0.0.0",
ad9c18f3
CH
322 "lsaSeqNumber": "80000001",
323 "opaqueData": "00010101",
324 },
325 ],
326 }
327 },
328 }
329 # verify content
330 json_cmd = "show ip ospf da opaque-area json"
331 assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None
332
333 step("reset client, add opaque AS, verify area flushing")
334
335 p.send_signal(signal.SIGINT)
336 time.sleep(2)
337 p.wait()
338 p = None
339
340 p = r1.popen([apibin, "-v", "add,11,232,3,deadbeaf01234567"])
341 input_dict = {
663a0c96 342 "routerId": "1.0.0.0",
ad9c18f3
CH
343 "areas": {
344 "1.2.3.4": {
345 "areaLocalOpaqueLsa": [
346 {
347 "lsId": "231.0.0.1",
663a0c96 348 "advertisedRouter": "1.0.0.0",
ad9c18f3
CH
349 "sequenceNumber": "80000001",
350 "lsaAge": 3600,
351 },
352 ],
353 }
354 },
355 "asExternalOpaqueLsa": [
356 {
357 "lsId": "232.0.0.3",
663a0c96 358 "advertisedRouter": "1.0.0.0",
ad9c18f3
CH
359 "sequenceNumber": "80000001",
360 },
361 ],
362 }
363 # Wait for it to show up
364 assert verify_ospf_database(tgen, r1, input_dict) is None
365
366 input_dict = {
367 "asExternalOpaqueLsa": [
368 {
369 "linkStateId": "232.0.0.3",
663a0c96 370 "advertisingRouter": "1.0.0.0",
ad9c18f3
CH
371 "lsaSeqNumber": "80000001",
372 "opaqueData": "deadbeaf01234567",
373 },
374 ]
375 }
376 # verify content
377 json_cmd = "show ip ospf da opaque-as json"
378 assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None
379
380 step("stop client, verify AS flushing")
381
382 p.send_signal(signal.SIGINT)
383 time.sleep(2)
384 p.wait()
385 p = None
386
387 input_dict = {
663a0c96 388 "routerId": "1.0.0.0",
ad9c18f3
CH
389 "asExternalOpaqueLsa": [
390 {
391 "lsId": "232.0.0.3",
663a0c96 392 "advertisedRouter": "1.0.0.0",
ad9c18f3
CH
393 "sequenceNumber": "80000001",
394 "lsaAge": 3600,
395 },
396 ],
397 }
398 # Wait for it to be flushed
399 assert verify_ospf_database(tgen, r1, input_dict) is None
400
401 step("start client adding opaque domain, verify new sequence number and data")
402
403 # Originate it again
404 p = r1.popen([apibin, "-v", "add,11,232,3,ebadf00d"])
405 input_dict = {
663a0c96 406 "routerId": "1.0.0.0",
ad9c18f3
CH
407 "asExternalOpaqueLsa": [
408 {
409 "lsId": "232.0.0.3",
663a0c96 410 "advertisedRouter": "1.0.0.0",
ad9c18f3
CH
411 "sequenceNumber": "80000002",
412 },
413 ],
414 }
415 assert verify_ospf_database(tgen, r1, input_dict) is None
416
417 input_dict = {
418 "asExternalOpaqueLsa": [
419 {
420 "linkStateId": "232.0.0.3",
663a0c96 421 "advertisingRouter": "1.0.0.0",
ad9c18f3
CH
422 "lsaSeqNumber": "80000002",
423 "opaqueData": "ebadf00d",
424 },
425 ]
426 }
427 # verify content
428 json_cmd = "show ip ospf da opaque-as json"
429 assert verify_ospf_database(tgen, r1, input_dict, json_cmd) is None
430
663a0c96 431 logging.debug("sending interrupt to writer api client")
ad9c18f3
CH
432 p.send_signal(signal.SIGINT)
433 time.sleep(2)
434 p.wait()
435 p = None
436
437 except Exception:
438 if p:
439 p.terminate()
440 if p.wait():
441 comm_error(p)
442 p = None
443 raise
444 finally:
445 if p:
663a0c96 446 logging.debug("cleanup: sending interrupt to writer api client")
ad9c18f3
CH
447 p.terminate()
448 p.wait()
449
450
451@pytest.mark.parametrize("tgen", [2], indirect=True)
452def test_ospf_opaque_add_data3(tgen):
453 apibin = os.path.join(CLIENTDIR, "ospfclient.py")
454 rc, o, e = tgen.gears["r2"].net.cmd_status([apibin, "--help"])
663a0c96 455 logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin, rc, o, e)
ad9c18f3
CH
456 _test_add_data(tgen, apibin)
457
458
459def _test_opaque_add_del(tgen, apibin):
460 "Test adding opaque data to domain"
461
462 r1 = tgen.gears["r1"]
463 r2 = tgen.gears["r2"]
464
465 p = None
466 pread = None
663a0c96
CH
467 # Log to our stdin, stderr
468 pout = open(os.path.join(r1.net.logdir, "r1/add-del.log"), "a+")
ad9c18f3
CH
469 try:
470 step("reachable: check for add notification")
471 pread = r2.popen(
663a0c96 472 ["/usr/bin/timeout", "120", apibin, "-v", "--logtag=READER", "wait,120"],
ad9c18f3
CH
473 encoding=None, # don't buffer
474 stdin=subprocess.DEVNULL,
475 stdout=subprocess.PIPE,
476 stderr=subprocess.STDOUT,
477 )
0b242b11
LB
478 p = r1.popen(
479 [
480 apibin,
481 "-v",
c4fff21b 482 "add,9,10.0.1.1,230,1",
0b242b11 483 "add,9,10.0.1.1,230,2,00000202",
0b242b11 484 "wait,1",
c4fff21b
LB
485 "add,10,1.2.3.4,231,1",
486 "add,10,1.2.3.4,231,2,0102030405060708",
487 "wait,1",
488 "add,11,232,1",
489 "add,11,232,2,ebadf00d",
0b242b11 490 "wait,20",
c4fff21b
LB
491 "del,9,10.0.1.1,230,2,0",
492 "del,10,1.2.3.4,231,2,1",
493 "del,11,232,1,1",
0b242b11
LB
494 ]
495 )
0b242b11
LB
496 add_input_dict = {
497 "areas": {
498 "1.2.3.4": {
499 "linkLocalOpaqueLsa": [
c4fff21b
LB
500 {
501 "lsId": "230.0.0.1",
663a0c96 502 "advertisedRouter": "1.0.0.0",
c4fff21b 503 "sequenceNumber": "80000001",
663a0c96 504 "checksum": "76bf",
c4fff21b 505 },
0b242b11
LB
506 {
507 "lsId": "230.0.0.2",
663a0c96 508 "advertisedRouter": "1.0.0.0",
0b242b11 509 "sequenceNumber": "80000001",
663a0c96 510 "checksum": "8aa2",
c4fff21b 511 },
0b242b11 512 ],
c4fff21b 513 "linkLocalOpaqueLsaCount": 2,
0b242b11
LB
514 "areaLocalOpaqueLsa": [
515 {
516 "lsId": "231.0.0.1",
663a0c96 517 "advertisedRouter": "1.0.0.0",
0b242b11 518 "sequenceNumber": "80000001",
663a0c96 519 "checksum": "5bd8",
0b242b11
LB
520 },
521 {
522 "lsId": "231.0.0.2",
663a0c96 523 "advertisedRouter": "1.0.0.0",
0b242b11 524 "sequenceNumber": "80000001",
663a0c96 525 "checksum": "7690",
0b242b11
LB
526 },
527 ],
c4fff21b
LB
528 "areaLocalOpaqueLsaCount": 2,
529 },
0b242b11
LB
530 },
531 "asExternalOpaqueLsa": [
532 {
c4fff21b 533 "lsId": "232.0.0.1",
663a0c96 534 "advertisedRouter": "1.0.0.0",
0b242b11 535 "sequenceNumber": "80000001",
663a0c96 536 "checksum": "5ed5",
c4fff21b
LB
537 },
538 {
539 "lsId": "232.0.0.2",
663a0c96 540 "advertisedRouter": "1.0.0.0",
c4fff21b 541 "sequenceNumber": "80000001",
663a0c96 542 "checksum": "d9bd",
c4fff21b 543 },
0b242b11 544 ],
c4fff21b 545 "asExternalOpaqueLsaCount": 2,
0b242b11
LB
546 }
547
548 step("reachable: check for add LSAs")
549 json_cmd = "show ip ospf da json"
550 assert verify_ospf_database(tgen, r1, add_input_dict, json_cmd) is None
551 assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None
552
553 numcs = 3
554 json_cmds = [
555 "show ip ospf da opaque-link json",
556 "show ip ospf da opaque-area json",
557 "show ip ospf da opaque-as json",
558 ]
559 add_detail_input_dict = [
560 {
561 "linkLocalOpaqueLsa": {
562 "areas": {
563 "1.2.3.4": [
c4fff21b
LB
564 {
565 "linkStateId": "230.0.0.1",
663a0c96 566 "advertisingRouter": "1.0.0.0",
c4fff21b 567 "lsaSeqNumber": "80000001",
663a0c96 568 "checksum": "76bf",
c4fff21b
LB
569 "length": 20,
570 "opaqueDataLength": 0,
571 },
0b242b11
LB
572 {
573 "linkStateId": "230.0.0.2",
663a0c96 574 "advertisingRouter": "1.0.0.0",
0b242b11 575 "lsaSeqNumber": "80000001",
663a0c96 576 "checksum": "8aa2",
0b242b11
LB
577 "length": 24,
578 "opaqueId": 2,
579 "opaqueDataLength": 4,
c4fff21b 580 },
0b242b11
LB
581 ]
582 }
583 }
584 },
585 {
586 "areaLocalOpaqueLsa": {
587 "areas": {
588 "1.2.3.4": [
589 {
590 "linkStateId": "231.0.0.1",
663a0c96 591 "advertisingRouter": "1.0.0.0",
0b242b11 592 "lsaSeqNumber": "80000001",
663a0c96 593 "checksum": "5bd8",
c4fff21b
LB
594 "length": 20,
595 "opaqueDataLength": 0,
0b242b11
LB
596 },
597 {
598 "linkStateId": "231.0.0.2",
663a0c96 599 "advertisingRouter": "1.0.0.0",
0b242b11 600 "lsaSeqNumber": "80000001",
663a0c96 601 "checksum": "7690",
c4fff21b
LB
602 "length": 28,
603 "opaqueDataLength": 8,
0b242b11 604 },
c4fff21b
LB
605 ],
606 },
607 },
0b242b11
LB
608 },
609 {
610 "asExternalOpaqueLsa": [
611 {
c4fff21b 612 "linkStateId": "232.0.0.1",
663a0c96 613 "advertisingRouter": "1.0.0.0",
c4fff21b 614 "lsaSeqNumber": "80000001",
663a0c96 615 "checksum": "5ed5",
c4fff21b
LB
616 "length": 20,
617 "opaqueDataLength": 0,
618 },
619 {
620 "linkStateId": "232.0.0.2",
663a0c96 621 "advertisingRouter": "1.0.0.0",
0b242b11 622 "lsaSeqNumber": "80000001",
663a0c96 623 "checksum": "d9bd",
0b242b11
LB
624 "length": 24,
625 "opaqueDataLength": 4,
c4fff21b
LB
626 },
627 ],
0b242b11
LB
628 },
629 ]
630 i = 0
631 while i < numcs:
632 step("reachable: check for add LSA details: %s" % json_cmds[i])
633 assert (
634 verify_ospf_database(tgen, r1, add_detail_input_dict[i], json_cmds[i])
635 is None
636 )
637 assert (
638 verify_ospf_database(tgen, r2, add_detail_input_dict[i], json_cmds[i])
639 is None
640 )
641 i += 1
ad9c18f3
CH
642
643 # Wait for add notification
644 # RECV: LSA update msg for LSA 232.0.0.3 in area 0.0.0.0 seq 0x80000001 len 24 age 9
645
c4fff21b
LB
646 ls_ids = [
647 "230.0.0.1",
648 "230.0.0.2",
649 "231.0.0.1",
650 "231.0.0.2",
651 "232.0.0.1",
652 "232.0.0.2",
653 ]
0b242b11 654 for ls_id in ls_ids:
c4fff21b 655 step("reachable: check for API add notification: %s" % ls_id)
0b242b11
LB
656 waitfor = "RECV:.*update msg.*LSA {}.*age ([0-9]+)".format(ls_id)
657 _ = _wait_output(pread, waitfor)
658
659 del_input_dict = {
660 "areas": {
661 "1.2.3.4": {
662 "linkLocalOpaqueLsa": [
c4fff21b
LB
663 {
664 "lsId": "230.0.0.1",
663a0c96 665 "advertisedRouter": "1.0.0.0",
c4fff21b 666 "sequenceNumber": "80000001",
663a0c96 667 "checksum": "76bf",
c4fff21b 668 },
0b242b11
LB
669 {
670 "lsId": "230.0.0.2",
663a0c96 671 "advertisedRouter": "1.0.0.0",
c4fff21b 672 "lsaAge": 3600,
0b242b11 673 "sequenceNumber": "80000001",
663a0c96 674 "checksum": "8aa2",
c4fff21b 675 },
0b242b11 676 ],
c4fff21b 677 "linkLocalOpaqueLsaCount": 2,
0b242b11
LB
678 "areaLocalOpaqueLsa": [
679 {
0b242b11 680 "lsId": "231.0.0.1",
663a0c96 681 "advertisedRouter": "1.0.0.0",
0b242b11 682 "sequenceNumber": "80000001",
663a0c96 683 "checksum": "5bd8",
0b242b11
LB
684 },
685 {
0b242b11 686 "lsId": "231.0.0.2",
663a0c96 687 "advertisedRouter": "1.0.0.0",
c4fff21b
LB
688 "lsaAge": 3600,
689 "sequenceNumber": "80000002",
663a0c96 690 "checksum": "4fe2",
0b242b11
LB
691 },
692 ],
c4fff21b
LB
693 "areaLocalOpaqueLsaCount": 2,
694 },
0b242b11
LB
695 },
696 "asExternalOpaqueLsa": [
697 {
c4fff21b 698 "lsId": "232.0.0.1",
663a0c96 699 "advertisedRouter": "1.0.0.0",
c4fff21b 700 "lsaAge": 3600,
0b242b11 701 "sequenceNumber": "80000001",
663a0c96 702 "checksum": "5ed5",
c4fff21b
LB
703 },
704 {
705 "lsId": "232.0.0.2",
663a0c96 706 "advertisedRouter": "1.0.0.0",
c4fff21b 707 "sequenceNumber": "80000001",
663a0c96 708 "checksum": "d9bd",
c4fff21b 709 },
0b242b11 710 ],
c4fff21b 711 "asExternalOpaqueLsaCount": 2,
0b242b11
LB
712 }
713
714 step("reachable: check for explicit withdrawal LSAs")
715 json_cmd = "show ip ospf da json"
716 assert verify_ospf_database(tgen, r1, del_input_dict, json_cmd) is None
717 assert verify_ospf_database(tgen, r2, del_input_dict, json_cmd) is None
ad9c18f3 718
c4fff21b
LB
719 del_detail_input_dict = [
720 {
721 "linkLocalOpaqueLsa": {
722 "areas": {
723 "1.2.3.4": [
724 {
725 "linkStateId": "230.0.0.1",
663a0c96 726 "advertisingRouter": "1.0.0.0",
c4fff21b 727 "lsaSeqNumber": "80000001",
663a0c96 728 "checksum": "76bf",
c4fff21b
LB
729 "length": 20,
730 "opaqueDataLength": 0,
731 },
732 {
733 "linkStateId": "230.0.0.2",
663a0c96 734 "advertisingRouter": "1.0.0.0",
c4fff21b
LB
735 "lsaAge": 3600,
736 "lsaSeqNumber": "80000001",
663a0c96 737 "checksum": "8aa2",
c4fff21b
LB
738 "length": 24,
739 "opaqueId": 2,
740 "opaqueDataLength": 4,
741 },
742 ]
743 }
744 }
745 },
746 {
747 "areaLocalOpaqueLsa": {
748 "areas": {
749 "1.2.3.4": [
750 {
751 "linkStateId": "231.0.0.1",
663a0c96 752 "advertisingRouter": "1.0.0.0",
c4fff21b 753 "lsaSeqNumber": "80000001",
663a0c96 754 "checksum": "5bd8",
c4fff21b
LB
755 "length": 20,
756 "opaqueDataLength": 0,
757 },
758 {
759 "lsaAge": 3600,
760 "linkStateId": "231.0.0.2",
663a0c96 761 "advertisingRouter": "1.0.0.0",
c4fff21b 762 "lsaSeqNumber": "80000002",
663a0c96 763 "checksum": "4fe2",
c4fff21b
LB
764 # data removed
765 "length": 20,
766 "opaqueDataLength": 0,
767 },
768 ],
769 },
770 },
771 },
772 {
773 "asExternalOpaqueLsa": [
774 {
775 "linkStateId": "232.0.0.1",
663a0c96 776 "advertisingRouter": "1.0.0.0",
c4fff21b
LB
777 "lsaAge": 3600,
778 "lsaSeqNumber": "80000001",
663a0c96 779 "checksum": "5ed5",
c4fff21b
LB
780 "length": 20,
781 "opaqueDataLength": 0,
782 },
783 {
784 "linkStateId": "232.0.0.2",
663a0c96 785 "advertisingRouter": "1.0.0.0",
c4fff21b 786 "lsaSeqNumber": "80000001",
663a0c96 787 "checksum": "d9bd",
c4fff21b
LB
788 "length": 24,
789 "opaqueDataLength": 4,
790 },
791 ],
792 },
793 ]
794 i = 0
795 while i < numcs:
796 step("reachable: check for delete LSA details: %s" % json_cmds[i])
797 assert (
798 verify_ospf_database(tgen, r1, del_detail_input_dict[i], json_cmds[i])
799 is None
800 )
801 assert (
802 verify_ospf_database(tgen, r2, del_detail_input_dict[i], json_cmds[i])
803 is None
804 )
805 i += 1
806
ad9c18f3
CH
807 p.terminate()
808 if p.wait():
809 comm_error(p)
c4fff21b
LB
810
811 del_detail_input_dict = [
812 {
813 "linkLocalOpaqueLsa": {
814 "areas": {
815 "1.2.3.4": [
816 {
817 "linkStateId": "230.0.0.1",
663a0c96 818 "advertisingRouter": "1.0.0.0",
c4fff21b
LB
819 "lsaAge": 3600,
820 "lsaSeqNumber": "80000001",
663a0c96 821 "checksum": "76bf",
c4fff21b
LB
822 "length": 20,
823 "opaqueDataLength": 0,
824 },
825 {
826 "linkStateId": "230.0.0.2",
663a0c96 827 "advertisingRouter": "1.0.0.0",
c4fff21b
LB
828 "lsaAge": 3600,
829 "lsaSeqNumber": "80000001",
663a0c96 830 "checksum": "8aa2",
c4fff21b
LB
831 "length": 24,
832 "opaqueId": 2,
833 "opaqueDataLength": 4,
834 },
835 ]
836 }
0b242b11
LB
837 }
838 },
c4fff21b
LB
839 {
840 "areaLocalOpaqueLsa": {
841 "areas": {
842 "1.2.3.4": [
843 {
844 "lsaAge": 3600,
845 "linkStateId": "231.0.0.1",
663a0c96 846 "advertisingRouter": "1.0.0.0",
c4fff21b 847 "lsaSeqNumber": "80000001",
663a0c96 848 "checksum": "5bd8",
c4fff21b
LB
849 "length": 20,
850 "opaqueDataLength": 0,
851 },
852 {
853 "lsaAge": 3600,
854 "linkStateId": "231.0.0.2",
663a0c96 855 "advertisingRouter": "1.0.0.0",
c4fff21b 856 "lsaSeqNumber": "80000002",
663a0c96 857 "checksum": "4fe2",
c4fff21b
LB
858 # data removed
859 "length": 20,
860 "opaqueDataLength": 0,
861 },
862 ],
863 },
864 },
865 },
866 {
867 "asExternalOpaqueLsa": [
868 {
869 "linkStateId": "232.0.0.1",
663a0c96 870 "advertisingRouter": "1.0.0.0",
c4fff21b
LB
871 "lsaAge": 3600,
872 "lsaSeqNumber": "80000001",
663a0c96 873 "checksum": "5ed5",
c4fff21b
LB
874 "length": 20,
875 "opaqueDataLength": 0,
876 },
877 {
878 "linkStateId": "232.0.0.2",
663a0c96 879 "advertisingRouter": "1.0.0.0",
c4fff21b
LB
880 "lsaAge": 3600,
881 "lsaSeqNumber": "80000001",
663a0c96 882 "checksum": "d9bd",
c4fff21b
LB
883 "length": 24,
884 "opaqueDataLength": 4,
885 },
886 ],
887 },
888 ]
889 i = 0
890 while i < numcs:
891 step(
892 "reachable: check for post API shutdown delete LSA details: %s"
893 % json_cmds[i]
894 )
895 assert (
896 verify_ospf_database(tgen, r1, del_detail_input_dict[i], json_cmds[i])
897 is None
898 )
899 assert (
900 verify_ospf_database(tgen, r2, del_detail_input_dict[i], json_cmds[i])
901 is None
902 )
903 i += 1
ad9c18f3
CH
904
905 # step("reachable: check for flush/age out")
906 # # Wait for max age notification
907 # waitfor = "RECV:.*update msg.*LSA {}.*age 3600".format(ls_id)
908 # _wait_output(pread, waitfor)
c4fff21b
LB
909 ls_ids = [
910 "230.0.0.2",
911 "231.0.0.2",
912 "232.0.0.1",
913 "230.0.0.1",
914 "231.0.0.1",
915 "232.0.0.2",
916 ]
0b242b11 917 for ls_id in ls_ids:
c4fff21b
LB
918 step("reachable: check for API delete notification: %s" % ls_id)
919 waitfor = "RECV:.*delete msg.*LSA {}.*age".format(ls_id)
0b242b11 920 _ = _wait_output(pread, waitfor)
ad9c18f3
CH
921 except Exception:
922 if p:
923 p.terminate()
924 if p.wait():
925 comm_error(p)
926 p = None
927 raise
928 finally:
929 if pread:
930 pread.terminate()
931 pread.wait()
932 if p:
933 p.terminate()
934 p.wait()
935
936
937@pytest.mark.parametrize("tgen", [2], indirect=True)
938def test_ospf_opaque_delete_data3(tgen):
939 apibin = os.path.join(CLIENTDIR, "ospfclient.py")
940 rc, o, e = tgen.gears["r2"].net.cmd_status([apibin, "--help"])
663a0c96 941 logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin, rc, o, e)
ad9c18f3
CH
942 _test_opaque_add_del(tgen, apibin)
943
944
4e7eb1e6
A
945def _test_opaque_add_restart_add(tgen, apibin):
946 "Test adding an opaque LSA and then restarting ospfd"
947
948 r1 = tgen.gears["r1"]
949 r2 = tgen.gears["r2"]
950
951 p = None
952 pread = None
953 # Log to our stdin, stderr
954 pout = open(os.path.join(r1.net.logdir, "r1/add-del.log"), "a+")
955 try:
956 step("reachable: check for add notification")
957 pread = r2.popen(
958 ["/usr/bin/timeout", "120", apibin, "-v", "--logtag=READER", "wait,120"],
959 encoding=None, # don't buffer
960 stdin=subprocess.DEVNULL,
961 stdout=subprocess.PIPE,
962 stderr=subprocess.STDOUT,
963 )
964 p = r1.popen(
965 [
966 apibin,
967 "-v",
39e1f084 968 "add,10,1.2.3.4,231,1", # seq = 80000001
4e7eb1e6 969 "wait, 5",
39e1f084 970 "add,10,1.2.3.4,231,1,feedaceebeef", # seq = 80000002
4e7eb1e6
A
971 "wait, 5",
972 ]
973 )
974 add_input_dict = {
975 "areas": {
976 "1.2.3.4": {
977 "areaLocalOpaqueLsa": [
978 {
979 "lsId": "231.0.0.1",
980 "advertisedRouter": "1.0.0.0",
39e1f084
CH
981 "sequenceNumber": "80000002",
982 "checksum": "cd26",
4e7eb1e6
A
983 },
984 ],
985 "areaLocalOpaqueLsaCount": 1,
986 },
987 },
988 }
39e1f084 989 step("Wait for the Opaque LSA to be distributed")
4e7eb1e6
A
990 json_cmd = "show ip ospf da json"
991 assert verify_ospf_database(tgen, r1, add_input_dict, json_cmd) is None
992 assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None
993
994 step("Shutdown the interface on r1 to isolate it for r2")
995 shutdown_bringup_interface(tgen, "r1", "r1-eth0", False)
996
997 time.sleep(2)
998 step("Reset the client")
999 p.send_signal(signal.SIGINT)
1000 time.sleep(2)
1001 p.wait()
1002 p = None
1003
39e1f084
CH
1004 # Verify the OLD LSA is still there unchanged on R2
1005 assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None
1006
4e7eb1e6
A
1007 step("Kill ospfd on R1")
1008 kill_router_daemons(tgen, "r1", ["ospfd"])
1009 time.sleep(2)
1010
1011 step("Bring ospfd on R1 back up")
1012 start_router_daemons(tgen, "r1", ["ospfd"])
1013
39e1f084
CH
1014 # This will start off with sequence num 80000001
1015 # But should advance to 80000003 when we reestablish with r2
4e7eb1e6
A
1016 p = r1.popen(
1017 [
1018 apibin,
1019 "-v",
39e1f084 1020 "add,10,1.2.3.4,231,1,feedaceecafebeef", # seq=80000001
4e7eb1e6
A
1021 "wait, 5",
1022 ]
1023 )
1024
39e1f084
CH
1025 # verify the old value on r2 doesn't change yet
1026 time.sleep(2)
1027 assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None
4e7eb1e6 1028
4e7eb1e6 1029 json_cmd = "show ip ospf da opaque-area json"
39e1f084 1030 new_add_input_dict = {
4e7eb1e6
A
1031 "areaLocalOpaqueLsa": {
1032 "areas": {
1033 "1.2.3.4": [
1034 {
1035 "linkStateId": "231.0.0.1",
1036 "advertisingRouter": "1.0.0.0",
39e1f084
CH
1037 "lsaSeqNumber": "80000001",
1038 "checksum": "b07a",
4e7eb1e6
A
1039 "length": 28,
1040 "opaqueDataLength": 8,
1041 },
1042 ],
1043 },
1044 },
1045 }
39e1f084
CH
1046 # verify new value with initial seq number on r1
1047 assert verify_ospf_database(tgen, r1, new_add_input_dict, json_cmd) is None
1048
1049 step("Bring the interface on r1 back up for connection to r2")
1050 shutdown_bringup_interface(tgen, "r1", "r1-eth0", True)
1051
1052 step("Verify area opaque LSA refresh")
1053
1054 # Update the expected value to sequence number rev and new checksum
1055 update_dict = new_add_input_dict["areaLocalOpaqueLsa"]["areas"]["1.2.3.4"][0]
1056 update_dict["lsaSeqNumber"] = "80000003"
1057 update_dict["checksum"] = "cb27"
1058
1059 # should settle on the same value now.
1060 assert verify_ospf_database(tgen, r1, new_add_input_dict, json_cmd) is None
1061 assert verify_ospf_database(tgen, r2, new_add_input_dict, json_cmd) is None
4e7eb1e6
A
1062
1063 step("Shutdown the interface on r1 to isolate it for r2")
1064 shutdown_bringup_interface(tgen, "r1", "r1-eth0", False)
1065
1066 time.sleep(2)
1067 step("Reset the client")
1068 p.send_signal(signal.SIGINT)
1069 time.sleep(2)
1070 p.wait()
1071 p = None
1072
1073 step("Kill ospfd on R1")
1074 kill_router_daemons(tgen, "r1", ["ospfd"])
1075 time.sleep(2)
1076
1077 step("Bring ospfd on R1 back up")
1078 start_router_daemons(tgen, "r1", ["ospfd"])
1079
1080 step("Bring the interface on r1 back up for connection to r2")
1081 shutdown_bringup_interface(tgen, "r1", "r1-eth0", True)
1082
1083 step("Verify area opaque LSA Purging")
1084 json_cmd = "show ip ospf da opaque-area json"
1085 add_detail_input_dict = {
1086 "areaLocalOpaqueLsa": {
1087 "areas": {
1088 "1.2.3.4": [
1089 {
1090 "lsaAge": 3600,
1091 "linkStateId": "231.0.0.1",
1092 "advertisingRouter": "1.0.0.0",
39e1f084
CH
1093 "lsaSeqNumber": "80000003",
1094 "checksum": "cb27",
4e7eb1e6
A
1095 "length": 28,
1096 "opaqueDataLength": 8,
1097 },
1098 ],
1099 },
1100 },
1101 }
1102 assert verify_ospf_database(tgen, r1, add_detail_input_dict, json_cmd) is None
1103 assert verify_ospf_database(tgen, r2, add_detail_input_dict, json_cmd) is None
1104 step("Verify Area Opaque LSA removal after timeout (60 seconds)")
1105 time.sleep(60)
1106 json_cmd = "show ip ospf da opaque-area json"
1107 timeout_detail_input_dict = {
1108 "areaLocalOpaqueLsa": {
1109 "areas": {
1110 "1.2.3.4": [],
1111 },
1112 },
1113 }
1114 assert (
1115 verify_ospf_database(tgen, r1, timeout_detail_input_dict, json_cmd) is None
1116 )
1117 assert (
1118 verify_ospf_database(tgen, r2, timeout_detail_input_dict, json_cmd) is None
1119 )
1120
1121 except Exception:
1122 if p:
1123 p.terminate()
1124 if p.wait():
1125 comm_error(p)
1126 p = None
1127 raise
1128 finally:
1129 if pread:
1130 pread.terminate()
1131 pread.wait()
1132 if p:
1133 p.terminate()
1134 p.wait()
1135
1136
1137@pytest.mark.parametrize("tgen", [2], indirect=True)
1138def test_ospf_opaque_restart(tgen):
1139 apibin = os.path.join(CLIENTDIR, "ospfclient.py")
1140 rc, o, e = tgen.gears["r2"].net.cmd_status([apibin, "--help"])
1141 logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin, rc, o, e)
1142 _test_opaque_add_restart_add(tgen, apibin)
1143
1144
ad9c18f3
CH
1145if __name__ == "__main__":
1146 args = ["-s"] + sys.argv[1:]
1147 sys.exit(pytest.main(args))