]>
git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/ospfapi/test_ospf_clientapi.py
2 # -*- coding: utf-8 eval: (blacken-mode 1) -*-
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Copyright (c) 2021-2022, LabN Consulting, L.L.C.
9 test_ospf_clientapi.py: Test the OSPF client API.
19 from datetime
import datetime
, timedelta
23 from lib
.common_config
import retry
, run_frr_cmd
, step
24 from lib
.micronet
import Timeout
, comm_error
25 from lib
.topogen
import Topogen
, TopoRouter
26 from lib
.topotest
import interface_set_status
, json_cmp
28 pytestmark
= [pytest
.mark
.ospfd
]
30 CWD
= os
.path
.dirname(os
.path
.realpath(__file__
))
31 TESTDIR
= os
.path
.abspath(CWD
)
33 CLIENTDIR
= os
.path
.abspath(os
.path
.join(CWD
, "../../../ospfclient"))
34 if not os
.path
.exists(CLIENTDIR
):
35 CLIENTDIR
= os
.path
.join(CWD
, "/usr/lib/frr")
37 assert os
.path
.exists(
38 os
.path
.join(CLIENTDIR
, "ospfclient.py")
39 ), "can't locate ospfclient.py"
53 @pytest.fixture(scope
="function", name
="tgen")
55 "Setup/Teardown the environment and provide tgen argument to tests"
56 nrouters
= request
.param
57 topodef
= {f
"sw{i}": (f
"r{i}", f
"r{i+1}") for i
in range(1, nrouters
)}
59 topodef
["sw4"] = ("r4", "r1")
61 tgen
= Topogen(topodef
, request
.module
.__name
__)
64 router_list
= tgen
.routers()
65 for _
, router
in router_list
.items():
66 router
.load_config(TopoRouter
.RD_ZEBRA
, "zebra.conf")
67 router
.load_config(TopoRouter
.RD_OSPF
, "ospfd.conf")
68 router
.net
.daemons_options
["ospfd"] = "--apiserver"
77 # Fixture that executes before each test
78 @pytest.fixture(autouse
=True)
79 def skip_on_failure(tgen
):
80 if tgen
.routers_have_failure():
81 pytest
.skip("skipped because of previous test failure")
89 @retry(retry_timeout
=45)
90 def verify_ospf_database(tgen
, dut
, input_dict
, cmd
="show ip ospf database json"):
92 show_ospf_json
= run_frr_cmd(dut
, cmd
, isjson
=True)
93 if not bool(show_ospf_json
):
94 return "ospf is not running"
95 result
= json_cmp(show_ospf_json
, input_dict
)
96 return str(result
) if result
else None
102 # logging.debug("READING 1 CHAR")
105 return buf
if buf
else None
107 # logging.debug("READ CHAR: '%s'", c)
112 def _wait_output(p
, regex
, maxwait
=120):
113 timeout
= Timeout(maxwait
)
114 while not timeout
.is_expired():
115 # line = p.stdout.readline()
116 line
= myreadline(p
.stdout
)
118 assert None, "EOF waiting for '{}'".format(regex
)
119 line
= line
.decode("utf-8")
122 logging
.debug("GOT LINE: '%s'", line
)
123 m
= re
.search(regex
, line
)
126 assert None, "Failed to get output matching '{}' withint {} actual {}s".format(
127 regex
, maxwait
, timeout
.elapsed()
136 def _test_reachability(tgen
, testbin
):
138 "1.0.0.0,2.0.0.0,4.0.0.0",
140 "1.0.0.0,2.0.0.0,4.0.0.0",
142 r2
= tgen
.gears
["r2"]
143 r3
= tgen
.gears
["r3"]
144 r4
= tgen
.gears
["r4"]
146 wait_args
= [f
"--wait={x}" for x
in waitlist
]
150 step("reachable: check for initial reachability")
152 ["/usr/bin/timeout", "120", testbin
, "-v", *wait_args
],
153 encoding
=None, # don't buffer
154 stdin
=subprocess
.DEVNULL
,
155 stdout
=subprocess
.PIPE
,
156 stderr
=subprocess
.STDOUT
,
158 _wait_output(p
, "SUCCESS: {}".format(waitlist
[0]))
160 step("reachable: check for modified reachability")
161 interface_set_status(r2
, "r2-eth0", False)
162 interface_set_status(r4
, "r4-eth1", False)
163 _wait_output(p
, "SUCCESS: {}".format(waitlist
[1]))
165 step("reachable: check for restored reachability")
166 interface_set_status(r2
, "r2-eth0", True)
167 interface_set_status(r4
, "r4-eth1", True)
168 _wait_output(p
, "SUCCESS: {}".format(waitlist
[2]))
169 except Exception as error
:
170 logging
.error("ERROR: %s", error
)
178 @pytest.mark
.parametrize("tgen", [4], indirect
=True)
179 def test_ospf_reachability(tgen
):
180 testbin
= os
.path
.join(TESTDIR
, "ctester.py")
181 rc
, o
, e
= tgen
.gears
["r2"].net
.cmd_status([testbin
, "--help"])
182 logging
.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", testbin
, rc
, o
, e
)
183 _test_reachability(tgen
, testbin
)
186 def _test_router_id(tgen
, testbin
):
187 r1
= tgen
.gears
["r1"]
194 mon_args
= [f
"--monitor={x}" for x
in waitlist
]
198 step("router id: check for initial router id")
200 ["/usr/bin/timeout", "120", testbin
, "-v", *mon_args
],
201 encoding
=None, # don't buffer
202 stdin
=subprocess
.DEVNULL
,
203 stdout
=subprocess
.PIPE
,
204 stderr
=subprocess
.STDOUT
,
206 _wait_output(p
, "SUCCESS: {}".format(waitlist
[0]))
208 step("router id: check for modified router id")
209 r1
.vtysh_multicmd("conf t\nrouter ospf\nospf router-id 1.1.1.1")
210 _wait_output(p
, "SUCCESS: {}".format(waitlist
[1]))
212 step("router id: check for restored router id")
213 r1
.vtysh_multicmd("conf t\nrouter ospf\nospf router-id 1.0.0.0")
214 _wait_output(p
, "SUCCESS: {}".format(waitlist
[2]))
215 except Exception as error
:
216 logging
.error("ERROR: %s", error
)
224 @pytest.mark
.parametrize("tgen", [2], indirect
=True)
225 def test_ospf_router_id(tgen
):
226 testbin
= os
.path
.join(TESTDIR
, "ctester.py")
227 rc
, o
, e
= tgen
.gears
["r1"].net
.cmd_status([testbin
, "--help"])
228 logging
.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", testbin
, rc
, o
, e
)
229 _test_router_id(tgen
, testbin
)
232 def _test_add_data(tgen
, apibin
):
233 "Test adding opaque data to domain"
235 r1
= tgen
.gears
["r1"]
237 step("add opaque: add opaque link local")
241 p
= r1
.popen([apibin
, "-v", "add,9,10.0.1.1,230,2,00000202"])
243 "routerId": "1.0.0.0",
246 "linkLocalOpaqueLsa": [
249 "advertisedRouter": "1.0.0.0",
250 "sequenceNumber": "80000001",
256 # Wait for it to show up
257 assert verify_ospf_database(tgen
, r1
, input_dict
) is None
260 "linkLocalOpaqueLsa": {
264 "linkStateId": "230.0.0.2",
265 "advertisingRouter": "1.0.0.0",
266 "lsaSeqNumber": "80000001",
267 "opaqueData": "00000202",
274 json_cmd
= "show ip ospf da opaque-link json"
275 assert verify_ospf_database(tgen
, r1
, input_dict
, json_cmd
) is None
277 step("reset client, add opaque area, verify link local flushing")
279 p
.send_signal(signal
.SIGINT
)
283 p
= r1
.popen([apibin
, "-v", "add,10,1.2.3.4,231,1,00010101"])
285 "routerId": "1.0.0.0",
288 "linkLocalOpaqueLsa": [
291 "advertisedRouter": "1.0.0.0",
292 "sequenceNumber": "80000001",
296 "areaLocalOpaqueLsa": [
299 "advertisedRouter": "1.0.0.0",
300 "sequenceNumber": "80000001",
306 # Wait for it to show up
307 assert verify_ospf_database(tgen
, r1
, input_dict
) is None
310 "areaLocalOpaqueLsa": {
314 "linkStateId": "231.0.0.1",
315 "advertisingRouter": "1.0.0.0",
316 "lsaSeqNumber": "80000001",
317 "opaqueData": "00010101",
324 json_cmd
= "show ip ospf da opaque-area json"
325 assert verify_ospf_database(tgen
, r1
, input_dict
, json_cmd
) is None
327 step("reset client, add opaque AS, verify area flushing")
329 p
.send_signal(signal
.SIGINT
)
334 p
= r1
.popen([apibin
, "-v", "add,11,232,3,deadbeaf01234567"])
336 "routerId": "1.0.0.0",
339 "areaLocalOpaqueLsa": [
342 "advertisedRouter": "1.0.0.0",
343 "sequenceNumber": "80000001",
349 "asExternalOpaqueLsa": [
352 "advertisedRouter": "1.0.0.0",
353 "sequenceNumber": "80000001",
357 # Wait for it to show up
358 assert verify_ospf_database(tgen
, r1
, input_dict
) is None
361 "asExternalOpaqueLsa": [
363 "linkStateId": "232.0.0.3",
364 "advertisingRouter": "1.0.0.0",
365 "lsaSeqNumber": "80000001",
366 "opaqueData": "deadbeaf01234567",
371 json_cmd
= "show ip ospf da opaque-as json"
372 assert verify_ospf_database(tgen
, r1
, input_dict
, json_cmd
) is None
374 step("stop client, verify AS flushing")
376 p
.send_signal(signal
.SIGINT
)
382 "routerId": "1.0.0.0",
383 "asExternalOpaqueLsa": [
386 "advertisedRouter": "1.0.0.0",
387 "sequenceNumber": "80000001",
392 # Wait for it to be flushed
393 assert verify_ospf_database(tgen
, r1
, input_dict
) is None
395 step("start client adding opaque domain, verify new sequence number and data")
398 p
= r1
.popen([apibin
, "-v", "add,11,232,3,ebadf00d"])
400 "routerId": "1.0.0.0",
401 "asExternalOpaqueLsa": [
404 "advertisedRouter": "1.0.0.0",
405 "sequenceNumber": "80000002",
409 assert verify_ospf_database(tgen
, r1
, input_dict
) is None
412 "asExternalOpaqueLsa": [
414 "linkStateId": "232.0.0.3",
415 "advertisingRouter": "1.0.0.0",
416 "lsaSeqNumber": "80000002",
417 "opaqueData": "ebadf00d",
422 json_cmd
= "show ip ospf da opaque-as json"
423 assert verify_ospf_database(tgen
, r1
, input_dict
, json_cmd
) is None
425 logging
.debug("sending interrupt to writer api client")
426 p
.send_signal(signal
.SIGINT
)
440 logging
.debug("cleanup: sending interrupt to writer api client")
445 @pytest.mark
.parametrize("tgen", [2], indirect
=True)
446 def test_ospf_opaque_add_data3(tgen
):
447 apibin
= os
.path
.join(CLIENTDIR
, "ospfclient.py")
448 rc
, o
, e
= tgen
.gears
["r2"].net
.cmd_status([apibin
, "--help"])
449 logging
.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin
, rc
, o
, e
)
450 _test_add_data(tgen
, apibin
)
453 def _test_opaque_add_del(tgen
, apibin
):
454 "Test adding opaque data to domain"
456 r1
= tgen
.gears
["r1"]
457 r2
= tgen
.gears
["r2"]
461 # Log to our stdin, stderr
462 pout
= open(os
.path
.join(r1
.net
.logdir
, "r1/add-del.log"), "a+")
464 step("reachable: check for add notification")
466 ["/usr/bin/timeout", "120", apibin
, "-v", "--logtag=READER", "wait,120"],
467 encoding
=None, # don't buffer
468 stdin
=subprocess
.DEVNULL
,
469 stdout
=subprocess
.PIPE
,
470 stderr
=subprocess
.STDOUT
,
476 "add,9,10.0.1.1,230,1",
477 "add,9,10.0.1.1,230,2,00000202",
479 "add,10,1.2.3.4,231,1",
480 "add,10,1.2.3.4,231,2,0102030405060708",
483 "add,11,232,2,ebadf00d",
485 "del,9,10.0.1.1,230,2,0",
486 "del,10,1.2.3.4,231,2,1",
493 "linkLocalOpaqueLsa": [
496 "advertisedRouter": "1.0.0.0",
497 "sequenceNumber": "80000001",
502 "advertisedRouter": "1.0.0.0",
503 "sequenceNumber": "80000001",
507 "linkLocalOpaqueLsaCount": 2,
508 "areaLocalOpaqueLsa": [
511 "advertisedRouter": "1.0.0.0",
512 "sequenceNumber": "80000001",
517 "advertisedRouter": "1.0.0.0",
518 "sequenceNumber": "80000001",
522 "areaLocalOpaqueLsaCount": 2,
525 "asExternalOpaqueLsa": [
528 "advertisedRouter": "1.0.0.0",
529 "sequenceNumber": "80000001",
534 "advertisedRouter": "1.0.0.0",
535 "sequenceNumber": "80000001",
539 "asExternalOpaqueLsaCount": 2,
542 step("reachable: check for add LSAs")
543 json_cmd
= "show ip ospf da json"
544 assert verify_ospf_database(tgen
, r1
, add_input_dict
, json_cmd
) is None
545 assert verify_ospf_database(tgen
, r2
, add_input_dict
, json_cmd
) is None
549 "show ip ospf da opaque-link json",
550 "show ip ospf da opaque-area json",
551 "show ip ospf da opaque-as json",
553 add_detail_input_dict
= [
555 "linkLocalOpaqueLsa": {
559 "linkStateId": "230.0.0.1",
560 "advertisingRouter": "1.0.0.0",
561 "lsaSeqNumber": "80000001",
564 "opaqueDataLength": 0,
567 "linkStateId": "230.0.0.2",
568 "advertisingRouter": "1.0.0.0",
569 "lsaSeqNumber": "80000001",
573 "opaqueDataLength": 4,
580 "areaLocalOpaqueLsa": {
584 "linkStateId": "231.0.0.1",
585 "advertisingRouter": "1.0.0.0",
586 "lsaSeqNumber": "80000001",
589 "opaqueDataLength": 0,
592 "linkStateId": "231.0.0.2",
593 "advertisingRouter": "1.0.0.0",
594 "lsaSeqNumber": "80000001",
597 "opaqueDataLength": 8,
604 "asExternalOpaqueLsa": [
606 "linkStateId": "232.0.0.1",
607 "advertisingRouter": "1.0.0.0",
608 "lsaSeqNumber": "80000001",
611 "opaqueDataLength": 0,
614 "linkStateId": "232.0.0.2",
615 "advertisingRouter": "1.0.0.0",
616 "lsaSeqNumber": "80000001",
619 "opaqueDataLength": 4,
626 step("reachable: check for add LSA details: %s" % json_cmds
[i
])
628 verify_ospf_database(tgen
, r1
, add_detail_input_dict
[i
], json_cmds
[i
])
632 verify_ospf_database(tgen
, r2
, add_detail_input_dict
[i
], json_cmds
[i
])
637 # Wait for add notification
638 # RECV: LSA update msg for LSA 232.0.0.3 in area 0.0.0.0 seq 0x80000001 len 24 age 9
649 step("reachable: check for API add notification: %s" % ls_id
)
650 waitfor
= "RECV:.*update msg.*LSA {}.*age ([0-9]+)".format(ls_id
)
651 _
= _wait_output(pread
, waitfor
)
656 "linkLocalOpaqueLsa": [
659 "advertisedRouter": "1.0.0.0",
660 "sequenceNumber": "80000001",
665 "advertisedRouter": "1.0.0.0",
667 "sequenceNumber": "80000001",
671 "linkLocalOpaqueLsaCount": 2,
672 "areaLocalOpaqueLsa": [
675 "advertisedRouter": "1.0.0.0",
676 "sequenceNumber": "80000001",
681 "advertisedRouter": "1.0.0.0",
683 "sequenceNumber": "80000002",
687 "areaLocalOpaqueLsaCount": 2,
690 "asExternalOpaqueLsa": [
693 "advertisedRouter": "1.0.0.0",
695 "sequenceNumber": "80000001",
700 "advertisedRouter": "1.0.0.0",
701 "sequenceNumber": "80000001",
705 "asExternalOpaqueLsaCount": 2,
708 step("reachable: check for explicit withdrawal LSAs")
709 json_cmd
= "show ip ospf da json"
710 assert verify_ospf_database(tgen
, r1
, del_input_dict
, json_cmd
) is None
711 assert verify_ospf_database(tgen
, r2
, del_input_dict
, json_cmd
) is None
713 del_detail_input_dict
= [
715 "linkLocalOpaqueLsa": {
719 "linkStateId": "230.0.0.1",
720 "advertisingRouter": "1.0.0.0",
721 "lsaSeqNumber": "80000001",
724 "opaqueDataLength": 0,
727 "linkStateId": "230.0.0.2",
728 "advertisingRouter": "1.0.0.0",
730 "lsaSeqNumber": "80000001",
734 "opaqueDataLength": 4,
741 "areaLocalOpaqueLsa": {
745 "linkStateId": "231.0.0.1",
746 "advertisingRouter": "1.0.0.0",
747 "lsaSeqNumber": "80000001",
750 "opaqueDataLength": 0,
754 "linkStateId": "231.0.0.2",
755 "advertisingRouter": "1.0.0.0",
756 "lsaSeqNumber": "80000002",
760 "opaqueDataLength": 0,
767 "asExternalOpaqueLsa": [
769 "linkStateId": "232.0.0.1",
770 "advertisingRouter": "1.0.0.0",
772 "lsaSeqNumber": "80000001",
775 "opaqueDataLength": 0,
778 "linkStateId": "232.0.0.2",
779 "advertisingRouter": "1.0.0.0",
780 "lsaSeqNumber": "80000001",
783 "opaqueDataLength": 4,
790 step("reachable: check for delete LSA details: %s" % json_cmds
[i
])
792 verify_ospf_database(tgen
, r1
, del_detail_input_dict
[i
], json_cmds
[i
])
796 verify_ospf_database(tgen
, r2
, del_detail_input_dict
[i
], json_cmds
[i
])
805 del_detail_input_dict
= [
807 "linkLocalOpaqueLsa": {
811 "linkStateId": "230.0.0.1",
812 "advertisingRouter": "1.0.0.0",
814 "lsaSeqNumber": "80000001",
817 "opaqueDataLength": 0,
820 "linkStateId": "230.0.0.2",
821 "advertisingRouter": "1.0.0.0",
823 "lsaSeqNumber": "80000001",
827 "opaqueDataLength": 4,
834 "areaLocalOpaqueLsa": {
839 "linkStateId": "231.0.0.1",
840 "advertisingRouter": "1.0.0.0",
841 "lsaSeqNumber": "80000001",
844 "opaqueDataLength": 0,
848 "linkStateId": "231.0.0.2",
849 "advertisingRouter": "1.0.0.0",
850 "lsaSeqNumber": "80000002",
854 "opaqueDataLength": 0,
861 "asExternalOpaqueLsa": [
863 "linkStateId": "232.0.0.1",
864 "advertisingRouter": "1.0.0.0",
866 "lsaSeqNumber": "80000001",
869 "opaqueDataLength": 0,
872 "linkStateId": "232.0.0.2",
873 "advertisingRouter": "1.0.0.0",
875 "lsaSeqNumber": "80000001",
878 "opaqueDataLength": 4,
886 "reachable: check for post API shutdown delete LSA details: %s"
890 verify_ospf_database(tgen
, r1
, del_detail_input_dict
[i
], json_cmds
[i
])
894 verify_ospf_database(tgen
, r2
, del_detail_input_dict
[i
], json_cmds
[i
])
899 # step("reachable: check for flush/age out")
900 # # Wait for max age notification
901 # waitfor = "RECV:.*update msg.*LSA {}.*age 3600".format(ls_id)
902 # _wait_output(pread, waitfor)
912 step("reachable: check for API delete notification: %s" % ls_id
)
913 waitfor
= "RECV:.*delete msg.*LSA {}.*age".format(ls_id
)
914 _
= _wait_output(pread
, waitfor
)
931 @pytest.mark
.parametrize("tgen", [2], indirect
=True)
932 def test_ospf_opaque_delete_data3(tgen
):
933 apibin
= os
.path
.join(CLIENTDIR
, "ospfclient.py")
934 rc
, o
, e
= tgen
.gears
["r2"].net
.cmd_status([apibin
, "--help"])
935 logging
.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin
, rc
, o
, e
)
936 _test_opaque_add_del(tgen
, apibin
)
939 if __name__
== "__main__":
940 args
= ["-s"] + sys
.argv
[1:]
941 sys
.exit(pytest
.main(args
))