]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/bgp_link_bw_ip/test_bgp_linkbw_ip.py
tests: Add pytest.mark.bgpd for tests missing this mark
[mirror_frr.git] / tests / topotests / bgp_link_bw_ip / test_bgp_linkbw_ip.py
1 #!/usr/bin/env python
2
3 #
4 # test_bgp_linkbw_ip.py
5 #
6 # Copyright (c) 2020 by
7 # Cumulus Networks, Inc
8 # Vivek Venkatraman
9 #
10 # Permission to use, copy, modify, and/or distribute this software
11 # for any purpose with or without fee is hereby granted, provided
12 # that the above copyright notice and this permission notice appear
13 # in all copies.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
16 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
18 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
19 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
20 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
21 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
22 # OF THIS SOFTWARE.
23 #
24
25 """
26 test_bgp_linkbw_ip.py: Test weighted ECMP using BGP link-bandwidth
27 """
28
29 import os
30 import re
31 import sys
32 from functools import partial
33 import pytest
34 import json
35
36 # Save the Current Working Directory to find configuration files.
37 CWD = os.path.dirname(os.path.realpath(__file__))
38 sys.path.append(os.path.join(CWD, "../"))
39
40 # pylint: disable=C0413
41 # Import topogen and topotest helpers
42 from lib import topotest
43 from lib.topogen import Topogen, TopoRouter, get_topogen
44 from lib.topolog import logger
45
46 # Required to instantiate the topology builder class.
47 from mininet.topo import Topo
48
49 pytestmark = [pytest.mark.bgpd]
50
51
52 """
53 This topology is for validating one of the primary use cases for
54 weighted ECMP (a.k.a. Unequal cost multipath) using BGP link-bandwidth:
55 https://tools.ietf.org/html/draft-mohanty-bess-ebgp-dmz
56
57 The topology consists of two PODs. Pod-1 consists of a spine switch
58 and two leaf switches, with two servers attached to the first leaf and
59 one to the second leaf. Pod-2 consists of one spine and one leaf, with
60 one server connected to the leaf. The PODs are connected by a super-spine
61 switch.
62
63 Note that the use of the term "switch" above is in keeping with common
64 data-center terminology. These devices are all regular routers; for
65 this scenario, the servers are also routers as they have to announce
66 anycast IP (VIP) addresses via BGP.
67 """
68
69
70 class BgpLinkBwTopo(Topo):
71 "Test topology builder"
72
73 def build(self, *_args, **_opts):
74 "Build function"
75 tgen = get_topogen(self)
76
77 # Create 10 routers - 1 super-spine, 2 spines, 3 leafs
78 # and 4 servers
79 routers = {}
80 for i in range(1, 11):
81 routers[i] = tgen.add_router("r{}".format(i))
82
83 # Create 13 "switches" - to interconnect the above routers
84 switches = {}
85 for i in range(1, 14):
86 switches[i] = tgen.add_switch("s{}".format(i))
87
88 # Interconnect R1 (super-spine) to R2 and R3 (the two spines)
89 switches[1].add_link(tgen.gears["r1"])
90 switches[1].add_link(tgen.gears["r2"])
91 switches[2].add_link(tgen.gears["r1"])
92 switches[2].add_link(tgen.gears["r3"])
93
94 # Interconnect R2 (spine in pod-1) to R4 and R5 (the associated
95 # leaf switches)
96 switches[3].add_link(tgen.gears["r2"])
97 switches[3].add_link(tgen.gears["r4"])
98 switches[4].add_link(tgen.gears["r2"])
99 switches[4].add_link(tgen.gears["r5"])
100
101 # Interconnect R3 (spine in pod-2) to R6 (associated leaf)
102 switches[5].add_link(tgen.gears["r3"])
103 switches[5].add_link(tgen.gears["r6"])
104
105 # Interconnect leaf switches to servers
106 switches[6].add_link(tgen.gears["r4"])
107 switches[6].add_link(tgen.gears["r7"])
108 switches[7].add_link(tgen.gears["r4"])
109 switches[7].add_link(tgen.gears["r8"])
110 switches[8].add_link(tgen.gears["r5"])
111 switches[8].add_link(tgen.gears["r9"])
112 switches[9].add_link(tgen.gears["r6"])
113 switches[9].add_link(tgen.gears["r10"])
114
115 # Create empty networks for the servers
116 switches[10].add_link(tgen.gears["r7"])
117 switches[11].add_link(tgen.gears["r8"])
118 switches[12].add_link(tgen.gears["r9"])
119 switches[13].add_link(tgen.gears["r10"])
120
121
122 def setup_module(mod):
123 "Sets up the pytest environment"
124 tgen = Topogen(BgpLinkBwTopo, mod.__name__)
125 tgen.start_topology()
126
127 router_list = tgen.routers()
128 for rname, router in router_list.items():
129 router.load_config(
130 TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
131 )
132 router.load_config(
133 TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
134 )
135
136 # Initialize all routers.
137 tgen.start_router()
138
139 # tgen.mininet_cli()
140
141
142 def teardown_module(mod):
143 "Teardown the pytest environment"
144 tgen = get_topogen()
145 tgen.stop_topology()
146
147
148 def test_bgp_linkbw_adv():
149 "Test #1: Test BGP link-bandwidth advertisement based on number of multipaths"
150 logger.info(
151 "\nTest #1: Test BGP link-bandwidth advertisement based on number of multipaths"
152 )
153
154 tgen = get_topogen()
155 if tgen.routers_have_failure():
156 pytest.skip("skipped because of router(s) failure")
157
158 r1 = tgen.gears["r1"]
159 r2 = tgen.gears["r2"]
160
161 # Configure anycast IP on server r7
162 logger.info("Configure anycast IP on server r7")
163
164 tgen.net["r7"].cmd("ip addr add 198.10.1.1/32 dev r7-eth1")
165
166 # Check on spine router r2 for link-bw advertisement by leaf router r4
167 logger.info("Check on spine router r2 for link-bw advertisement by leaf router r4")
168
169 json_file = "{}/r2/bgp-route-1.json".format(CWD)
170 expected = json.loads(open(json_file).read())
171 test_func = partial(
172 topotest.router_json_cmp, r2, "show bgp ipv4 uni 198.10.1.1/32 json", expected
173 )
174 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
175 assertmsg = "JSON output mismatch on spine router r2"
176 assert result is None, assertmsg
177
178 # Check on spine router r2 that default weight is used as there is no multipath
179 logger.info(
180 "Check on spine router r2 that default weight is used as there is no multipath"
181 )
182
183 json_file = "{}/r2/ip-route-1.json".format(CWD)
184 expected = json.loads(open(json_file).read())
185 test_func = partial(
186 topotest.router_json_cmp, r2, "show ip route 198.10.1.1/32 json", expected
187 )
188 _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5)
189 assertmsg = "JSON output mismatch on spine router r2"
190 assert result is None, assertmsg
191
192 # Check on super-spine router r1 that link-bw has been propagated by spine router r2
193 logger.info(
194 "Check on super-spine router r1 that link-bw has been propagated by spine router r2"
195 )
196
197 json_file = "{}/r1/bgp-route-1.json".format(CWD)
198 expected = json.loads(open(json_file).read())
199 test_func = partial(
200 topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected
201 )
202 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
203 assertmsg = "JSON output mismatch on super-spine router r1"
204 assert result is None, assertmsg
205
206
207 def test_bgp_cumul_linkbw():
208 "Test #2: Test cumulative link-bandwidth propagation"
209 logger.info("\nTest #2: Test cumulative link-bandwidth propagation")
210
211 tgen = get_topogen()
212 if tgen.routers_have_failure():
213 pytest.skip("skipped because of router(s) failure")
214
215 r1 = tgen.gears["r1"]
216 r2 = tgen.gears["r2"]
217 r4 = tgen.gears["r4"]
218
219 # Configure anycast IP on additional server r8
220 logger.info("Configure anycast IP on server r8")
221
222 tgen.net["r8"].cmd("ip addr add 198.10.1.1/32 dev r8-eth1")
223
224 # Check multipath on leaf router r4
225 logger.info("Check multipath on leaf router r4")
226
227 json_file = "{}/r4/bgp-route-1.json".format(CWD)
228 expected = json.loads(open(json_file).read())
229 test_func = partial(
230 topotest.router_json_cmp, r4, "show bgp ipv4 uni 198.10.1.1/32 json", expected
231 )
232 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
233 assertmsg = "JSON output mismatch on leaf router r4"
234 assert result is None, assertmsg
235
236 # Check regular ECMP is in effect on leaf router r4
237 logger.info("Check regular ECMP is in effect on leaf router r4")
238
239 json_file = "{}/r4/ip-route-1.json".format(CWD)
240 expected = json.loads(open(json_file).read())
241 test_func = partial(
242 topotest.router_json_cmp, r4, "show ip route 198.10.1.1/32 json", expected
243 )
244 _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5)
245 assertmsg = "JSON output mismatch on leaf router r4"
246 assert result is None, assertmsg
247
248 # Check on spine router r2 that leaf has propagated the cumulative link-bw based on num-multipaths
249 logger.info(
250 "Check on spine router r2 that leaf has propagated the cumulative link-bw based on num-multipaths"
251 )
252
253 json_file = "{}/r2/bgp-route-2.json".format(CWD)
254 expected = json.loads(open(json_file).read())
255 test_func = partial(
256 topotest.router_json_cmp, r2, "show bgp ipv4 uni 198.10.1.1/32 json", expected
257 )
258 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
259 assertmsg = "JSON output mismatch on spine router r2"
260 assert result is None, assertmsg
261
262
263 def test_weighted_ecmp():
264 "Test #3: Test weighted ECMP - multipath with next hop weights"
265 logger.info("\nTest #3: Test weighted ECMP - multipath with next hop weights")
266
267 tgen = get_topogen()
268 if tgen.routers_have_failure():
269 pytest.skip("skipped because of router(s) failure")
270
271 r1 = tgen.gears["r1"]
272 r2 = tgen.gears["r2"]
273
274 # Configure anycast IP on additional server r9
275 logger.info("Configure anycast IP on server r9")
276
277 tgen.net["r9"].cmd("ip addr add 198.10.1.1/32 dev r9-eth1")
278
279 # Check multipath on spine router r2
280 logger.info("Check multipath on spine router r2")
281 json_file = "{}/r2/bgp-route-3.json".format(CWD)
282 expected = json.loads(open(json_file).read())
283 test_func = partial(
284 topotest.router_json_cmp, r2, "show bgp ipv4 uni 198.10.1.1/32 json", expected
285 )
286 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
287 assertmsg = "JSON output mismatch on spine router r2"
288 assert result is None, assertmsg
289
290 # Check weighted ECMP is in effect on the spine router r2
291 logger.info("Check weighted ECMP is in effect on the spine router r2")
292
293 json_file = "{}/r2/ip-route-2.json".format(CWD)
294 expected = json.loads(open(json_file).read())
295 test_func = partial(
296 topotest.router_json_cmp, r2, "show ip route 198.10.1.1/32 json", expected
297 )
298 _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5)
299 assertmsg = "JSON output mismatch on spine router r2"
300 assert result is None, assertmsg
301
302 # Configure anycast IP on additional server r10
303 logger.info("Configure anycast IP on server r10")
304
305 tgen.net["r10"].cmd("ip addr add 198.10.1.1/32 dev r10-eth1")
306
307 # Check multipath on super-spine router r1
308 logger.info("Check multipath on super-spine router r1")
309 json_file = "{}/r1/bgp-route-2.json".format(CWD)
310 expected = json.loads(open(json_file).read())
311 test_func = partial(
312 topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected
313 )
314 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
315 assertmsg = "JSON output mismatch on super-spine router r1"
316 assert result is None, assertmsg
317
318 # Check weighted ECMP is in effect on the super-spine router r1
319 logger.info("Check weighted ECMP is in effect on the super-spine router r1")
320 json_file = "{}/r1/ip-route-1.json".format(CWD)
321 expected = json.loads(open(json_file).read())
322 test_func = partial(
323 topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected
324 )
325 _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5)
326 assertmsg = "JSON output mismatch on super-spine router r1"
327 assert result is None, assertmsg
328
329
330 def test_weighted_ecmp_link_flap():
331 "Test #4: Test weighted ECMP rebalancing upon change (link flap)"
332 logger.info("\nTest #4: Test weighted ECMP rebalancing upon change (link flap)")
333
334 tgen = get_topogen()
335 if tgen.routers_have_failure():
336 pytest.skip("skipped because of router(s) failure")
337
338 r1 = tgen.gears["r1"]
339 r2 = tgen.gears["r2"]
340
341 # Bring down link on server r9
342 logger.info("Bring down link on server r9")
343
344 tgen.net["r9"].cmd("ip link set dev r9-eth1 down")
345
346 # Check spine router r2 has only one path
347 logger.info("Check spine router r2 has only one path")
348
349 json_file = "{}/r2/ip-route-3.json".format(CWD)
350 expected = json.loads(open(json_file).read())
351 test_func = partial(
352 topotest.router_json_cmp, r2, "show ip route 198.10.1.1/32 json", expected
353 )
354 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
355 assertmsg = "JSON output mismatch on spine router r2"
356 assert result is None, assertmsg
357
358 # Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1
359 logger.info(
360 "Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1"
361 )
362
363 json_file = "{}/r1/bgp-route-3.json".format(CWD)
364 expected = json.loads(open(json_file).read())
365 test_func = partial(
366 topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected
367 )
368 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
369 assertmsg = "JSON output mismatch on super-spine router r1"
370 assert result is None, assertmsg
371
372 json_file = "{}/r1/ip-route-2.json".format(CWD)
373 expected = json.loads(open(json_file).read())
374 test_func = partial(
375 topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected
376 )
377 _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5)
378 assertmsg = "JSON output mismatch on super-spine router r1"
379 assert result is None, assertmsg
380
381 # Bring up link on server r9
382 logger.info("Bring up link on server r9")
383
384 tgen.net["r9"].cmd("ip link set dev r9-eth1 up")
385
386 # Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1
387 logger.info(
388 "Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1"
389 )
390
391 json_file = "{}/r1/bgp-route-2.json".format(CWD)
392 expected = json.loads(open(json_file).read())
393 test_func = partial(
394 topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected
395 )
396 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
397 assertmsg = "JSON output mismatch on super-spine router r1"
398 assert result is None, assertmsg
399
400 json_file = "{}/r1/ip-route-1.json".format(CWD)
401 expected = json.loads(open(json_file).read())
402 test_func = partial(
403 topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected
404 )
405 _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5)
406 assertmsg = "JSON output mismatch on super-spine router r1"
407 assert result is None, assertmsg
408
409
410 def test_weighted_ecmp_second_anycast_ip():
411 "Test #5: Test weighted ECMP for a second anycast IP"
412 logger.info("\nTest #5: Test weighted ECMP for a second anycast IP")
413
414 tgen = get_topogen()
415 if tgen.routers_have_failure():
416 pytest.skip("skipped because of router(s) failure")
417
418 r1 = tgen.gears["r1"]
419 r2 = tgen.gears["r2"]
420
421 # Configure anycast IP on additional server r7, r9 and r10
422 logger.info("Configure anycast IP on server r7, r9 and r10")
423
424 tgen.net["r7"].cmd("ip addr add 198.10.1.11/32 dev r7-eth1")
425 tgen.net["r9"].cmd("ip addr add 198.10.1.11/32 dev r9-eth1")
426 tgen.net["r10"].cmd("ip addr add 198.10.1.11/32 dev r10-eth1")
427
428 # Check link-bandwidth and weighted ECMP on super-spine router r1
429 logger.info("Check link-bandwidth and weighted ECMP on super-spine router r1")
430
431 json_file = "{}/r1/bgp-route-4.json".format(CWD)
432 expected = json.loads(open(json_file).read())
433 test_func = partial(
434 topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.11/32 json", expected
435 )
436 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
437 assertmsg = "JSON output mismatch on super-spine router r1"
438 assert result is None, assertmsg
439
440 json_file = "{}/r1/ip-route-3.json".format(CWD)
441 expected = json.loads(open(json_file).read())
442 test_func = partial(
443 topotest.router_json_cmp, r1, "show ip route 198.10.1.11/32 json", expected
444 )
445 _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5)
446 assertmsg = "JSON output mismatch on super-spine router r1"
447 assert result is None, assertmsg
448
449
450 def test_paths_with_and_without_linkbw():
451 "Test #6: Test paths with and without link-bandwidth - receiver should resort to regular ECMP"
452 logger.info(
453 "\nTest #6: Test paths with and without link-bandwidth - receiver should resort to regular ECMP"
454 )
455
456 tgen = get_topogen()
457 if tgen.routers_have_failure():
458 pytest.skip("skipped because of router(s) failure")
459
460 r1 = tgen.gears["r1"]
461
462 # Configure leaf router r6 to not advertise any link-bandwidth
463 logger.info("Configure leaf router r6 to not advertise any link-bandwidth")
464
465 tgen.net["r6"].cmd(
466 'vtysh -c "conf t" -c "router bgp 65303" -c "address-family ipv4 unicast" -c "no neighbor 11.1.3.1 route-map anycast_ip out"'
467 )
468
469 # Check link-bandwidth change on super-spine router r1
470 logger.info("Check link-bandwidth change on super-spine router r1")
471
472 json_file = "{}/r1/bgp-route-5.json".format(CWD)
473 expected = json.loads(open(json_file).read())
474 test_func = partial(
475 topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected
476 )
477 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
478 assertmsg = "JSON output mismatch on super-spine router r1"
479 assert result is None, assertmsg
480
481 # Check super-spine router r1 resorts to regular ECMP
482 logger.info("Check super-spine router r1 resorts to regular ECMP")
483
484 json_file = "{}/r1/ip-route-4.json".format(CWD)
485 expected = json.loads(open(json_file).read())
486 test_func = partial(
487 topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected
488 )
489 _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5)
490 assertmsg = "JSON output mismatch on super-spine router r1"
491 assert result is None, assertmsg
492
493 json_file = "{}/r1/ip-route-5.json".format(CWD)
494 expected = json.loads(open(json_file).read())
495 test_func = partial(
496 topotest.router_json_cmp, r1, "show ip route 198.10.1.11/32 json", expected
497 )
498 _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5)
499 assertmsg = "JSON output mismatch on super-spine router r1"
500 assert result is None, assertmsg
501
502
503 def test_linkbw_handling_options():
504 "Test #7: Test different options for processing link-bandwidth on the receiver"
505 logger.info(
506 "\nTest #7: Test different options for processing link-bandwidth on the receiver"
507 )
508
509 tgen = get_topogen()
510 if tgen.routers_have_failure():
511 pytest.skip("skipped because of router(s) failure")
512
513 r1 = tgen.gears["r1"]
514
515 # Configure super-spine r1 to skip multipaths without link-bandwidth
516 logger.info("Configure super-spine r1 to skip multipaths without link-bandwidth")
517
518 tgen.net["r1"].cmd(
519 'vtysh -c "conf t" -c "router bgp 65101" -c "bgp bestpath bandwidth skip-missing"'
520 )
521
522 # Check super-spine router r1 resorts to only one path as other path is skipped
523 logger.info(
524 "Check super-spine router r1 resorts to only one path as other path is skipped"
525 )
526
527 json_file = "{}/r1/ip-route-6.json".format(CWD)
528 expected = json.loads(open(json_file).read())
529 test_func = partial(
530 topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected
531 )
532 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
533 assertmsg = "JSON output mismatch on super-spine router r1"
534 assert result is None, assertmsg
535
536 json_file = "{}/r1/ip-route-7.json".format(CWD)
537 expected = json.loads(open(json_file).read())
538 test_func = partial(
539 topotest.router_json_cmp, r1, "show ip route 198.10.1.11/32 json", expected
540 )
541 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
542 assertmsg = "JSON output mismatch on super-spine router r1"
543 assert result is None, assertmsg
544
545 # Configure super-spine r1 to use default-weight for multipaths without link-bandwidth
546 logger.info(
547 "Configure super-spine r1 to use default-weight for multipaths without link-bandwidth"
548 )
549
550 tgen.net["r1"].cmd(
551 'vtysh -c "conf t" -c "router bgp 65101" -c "bgp bestpath bandwidth default-weight-for-missing"'
552 )
553
554 # Check super-spine router r1 uses ECMP with weight 1 for path without link-bandwidth
555 logger.info(
556 "Check super-spine router r1 uses ECMP with weight 1 for path without link-bandwidth"
557 )
558
559 json_file = "{}/r1/ip-route-8.json".format(CWD)
560 expected = json.loads(open(json_file).read())
561 test_func = partial(
562 topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected
563 )
564 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
565 assertmsg = "JSON output mismatch on super-spine router r1"
566 assert result is None, assertmsg
567
568 json_file = "{}/r1/ip-route-9.json".format(CWD)
569 expected = json.loads(open(json_file).read())
570 test_func = partial(
571 topotest.router_json_cmp, r1, "show ip route 198.10.1.11/32 json", expected
572 )
573 _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5)
574 assertmsg = "JSON output mismatch on super-spine router r1"
575 assert result is None, assertmsg
576
577
578 if __name__ == "__main__":
579 args = ["-s"] + sys.argv[1:]
580 sys.exit(pytest.main(args))