]>
git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/bgp_link_bw_ip/test_bgp_linkbw_ip.py
2 # SPDX-License-Identifier: ISC
5 # test_bgp_linkbw_ip.py
7 # Copyright (c) 2020 by
8 # Cumulus Networks, Inc
13 test_bgp_linkbw_ip.py: Test weighted ECMP using BGP link-bandwidth
18 from functools
import partial
22 # Save the Current Working Directory to find configuration files.
23 CWD
= os
.path
.dirname(os
.path
.realpath(__file__
))
24 sys
.path
.append(os
.path
.join(CWD
, "../"))
26 # pylint: disable=C0413
27 # Import topogen and topotest helpers
28 from lib
import topotest
29 from lib
.topogen
import Topogen
, TopoRouter
, get_topogen
30 from lib
.topolog
import logger
32 # Required to instantiate the topology builder class.
34 pytestmark
= [pytest
.mark
.bgpd
]
38 This topology is for validating one of the primary use cases for
39 weighted ECMP (a.k.a. Unequal cost multipath) using BGP link-bandwidth:
40 https://tools.ietf.org/html/draft-mohanty-bess-ebgp-dmz
42 The topology consists of two PODs. Pod-1 consists of a spine switch
43 and two leaf switches, with two servers attached to the first leaf and
44 one to the second leaf. Pod-2 consists of one spine and one leaf, with
45 one server connected to the leaf. The PODs are connected by a super-spine
48 Note that the use of the term "switch" above is in keeping with common
49 data-center terminology. These devices are all regular routers; for
50 this scenario, the servers are also routers as they have to announce
51 anycast IP (VIP) addresses via BGP.
58 # Create 10 routers - 1 super-spine, 2 spines, 3 leafs
61 for i
in range(1, 11):
62 routers
[i
] = tgen
.add_router("r{}".format(i
))
64 # Create 13 "switches" - to interconnect the above routers
66 for i
in range(1, 14):
67 switches
[i
] = tgen
.add_switch("s{}".format(i
))
69 # Interconnect R1 (super-spine) to R2 and R3 (the two spines)
70 switches
[1].add_link(tgen
.gears
["r1"])
71 switches
[1].add_link(tgen
.gears
["r2"])
72 switches
[2].add_link(tgen
.gears
["r1"])
73 switches
[2].add_link(tgen
.gears
["r3"])
75 # Interconnect R2 (spine in pod-1) to R4 and R5 (the associated
77 switches
[3].add_link(tgen
.gears
["r2"])
78 switches
[3].add_link(tgen
.gears
["r4"])
79 switches
[4].add_link(tgen
.gears
["r2"])
80 switches
[4].add_link(tgen
.gears
["r5"])
82 # Interconnect R3 (spine in pod-2) to R6 (associated leaf)
83 switches
[5].add_link(tgen
.gears
["r3"])
84 switches
[5].add_link(tgen
.gears
["r6"])
86 # Interconnect leaf switches to servers
87 switches
[6].add_link(tgen
.gears
["r4"])
88 switches
[6].add_link(tgen
.gears
["r7"])
89 switches
[7].add_link(tgen
.gears
["r4"])
90 switches
[7].add_link(tgen
.gears
["r8"])
91 switches
[8].add_link(tgen
.gears
["r5"])
92 switches
[8].add_link(tgen
.gears
["r9"])
93 switches
[9].add_link(tgen
.gears
["r6"])
94 switches
[9].add_link(tgen
.gears
["r10"])
96 # Create empty networks for the servers
97 switches
[10].add_link(tgen
.gears
["r7"])
98 switches
[11].add_link(tgen
.gears
["r8"])
99 switches
[12].add_link(tgen
.gears
["r9"])
100 switches
[13].add_link(tgen
.gears
["r10"])
103 def setup_module(mod
):
104 "Sets up the pytest environment"
105 tgen
= Topogen(build_topo
, mod
.__name
__)
106 tgen
.start_topology()
108 router_list
= tgen
.routers()
109 for rname
, router
in router_list
.items():
111 TopoRouter
.RD_ZEBRA
, os
.path
.join(CWD
, "{}/zebra.conf".format(rname
))
114 TopoRouter
.RD_BGP
, os
.path
.join(CWD
, "{}/bgpd.conf".format(rname
))
117 # Initialize all routers.
123 def teardown_module(mod
):
124 "Teardown the pytest environment"
129 def test_bgp_linkbw_adv():
130 "Test #1: Test BGP link-bandwidth advertisement based on number of multipaths"
132 "\nTest #1: Test BGP link-bandwidth advertisement based on number of multipaths"
136 if tgen
.routers_have_failure():
137 pytest
.skip("skipped because of router(s) failure")
139 r1
= tgen
.gears
["r1"]
140 r2
= tgen
.gears
["r2"]
142 # Configure anycast IP on server r7
143 logger
.info("Configure anycast IP on server r7")
145 tgen
.net
["r7"].cmd("ip addr add 198.10.1.1/32 dev r7-eth1")
147 # Check on spine router r2 for link-bw advertisement by leaf router r4
148 logger
.info("Check on spine router r2 for link-bw advertisement by leaf router r4")
150 json_file
= "{}/r2/bgp-route-1.json".format(CWD
)
151 expected
= json
.loads(open(json_file
).read())
153 topotest
.router_json_cmp
, r2
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
155 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
156 assertmsg
= "JSON output mismatch on spine router r2"
157 assert result
is None, assertmsg
159 # Check on spine router r2 that default weight is used as there is no multipath
161 "Check on spine router r2 that default weight is used as there is no multipath"
164 json_file
= "{}/r2/ip-route-1.json".format(CWD
)
165 expected
= json
.loads(open(json_file
).read())
167 topotest
.router_json_cmp
, r2
, "show ip route 198.10.1.1/32 json", expected
169 _
, result
= topotest
.run_and_expect(test_func
, None, count
=50, wait
=0.5)
170 assertmsg
= "JSON output mismatch on spine router r2"
171 assert result
is None, assertmsg
173 # Check on super-spine router r1 that link-bw has been propagated by spine router r2
175 "Check on super-spine router r1 that link-bw has been propagated by spine router r2"
178 json_file
= "{}/r1/bgp-route-1.json".format(CWD
)
179 expected
= json
.loads(open(json_file
).read())
181 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
183 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
184 assertmsg
= "JSON output mismatch on super-spine router r1"
185 assert result
is None, assertmsg
188 def test_bgp_cumul_linkbw():
189 "Test #2: Test cumulative link-bandwidth propagation"
190 logger
.info("\nTest #2: Test cumulative link-bandwidth propagation")
193 if tgen
.routers_have_failure():
194 pytest
.skip("skipped because of router(s) failure")
196 r1
= tgen
.gears
["r1"]
197 r2
= tgen
.gears
["r2"]
198 r4
= tgen
.gears
["r4"]
200 # Configure anycast IP on additional server r8
201 logger
.info("Configure anycast IP on server r8")
203 tgen
.net
["r8"].cmd("ip addr add 198.10.1.1/32 dev r8-eth1")
205 # Check multipath on leaf router r4
206 logger
.info("Check multipath on leaf router r4")
208 json_file
= "{}/r4/bgp-route-1.json".format(CWD
)
209 expected
= json
.loads(open(json_file
).read())
211 topotest
.router_json_cmp
, r4
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
213 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
214 assertmsg
= "JSON output mismatch on leaf router r4"
215 assert result
is None, assertmsg
217 # Check regular ECMP is in effect on leaf router r4
218 logger
.info("Check regular ECMP is in effect on leaf router r4")
220 json_file
= "{}/r4/ip-route-1.json".format(CWD
)
221 expected
= json
.loads(open(json_file
).read())
223 topotest
.router_json_cmp
, r4
, "show ip route 198.10.1.1/32 json", expected
225 _
, result
= topotest
.run_and_expect(test_func
, None, count
=50, wait
=0.5)
226 assertmsg
= "JSON output mismatch on leaf router r4"
227 assert result
is None, assertmsg
229 # Check on spine router r2 that leaf has propagated the cumulative link-bw based on num-multipaths
231 "Check on spine router r2 that leaf has propagated the cumulative link-bw based on num-multipaths"
234 json_file
= "{}/r2/bgp-route-2.json".format(CWD
)
235 expected
= json
.loads(open(json_file
).read())
237 topotest
.router_json_cmp
, r2
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
239 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
240 assertmsg
= "JSON output mismatch on spine router r2"
241 assert result
is None, assertmsg
244 def test_weighted_ecmp():
245 "Test #3: Test weighted ECMP - multipath with next hop weights"
246 logger
.info("\nTest #3: Test weighted ECMP - multipath with next hop weights")
249 if tgen
.routers_have_failure():
250 pytest
.skip("skipped because of router(s) failure")
252 r1
= tgen
.gears
["r1"]
253 r2
= tgen
.gears
["r2"]
254 r3
= tgen
.gears
["r3"]
256 # Configure anycast IP on additional server r9
257 logger
.info("Configure anycast IP on server r9")
259 tgen
.net
["r9"].cmd("ip addr add 198.10.1.1/32 dev r9-eth1")
261 # Check multipath on spine router r2
262 logger
.info("Check multipath on spine router r2")
263 json_file
= "{}/r2/bgp-route-3.json".format(CWD
)
264 expected
= json
.loads(open(json_file
).read())
266 topotest
.router_json_cmp
, r2
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
268 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
269 assertmsg
= "JSON output mismatch on spine router r2"
270 assert result
is None, assertmsg
272 # Check weighted ECMP is in effect on the spine router r2
273 logger
.info("Check weighted ECMP is in effect on the spine router r2")
275 json_file
= "{}/r2/ip-route-2.json".format(CWD
)
276 expected
= json
.loads(open(json_file
).read())
278 topotest
.router_json_cmp
, r2
, "show ip route 198.10.1.1/32 json", expected
280 _
, result
= topotest
.run_and_expect(test_func
, None, count
=50, wait
=0.5)
281 assertmsg
= "JSON output mismatch on spine router r2"
282 assert result
is None, assertmsg
284 # Configure anycast IP on additional server r10
285 logger
.info("Configure anycast IP on server r10")
287 tgen
.net
["r10"].cmd("ip addr add 198.10.1.1/32 dev r10-eth1")
289 # Check if bandwidth is properly encoded with non IEEE floatig-point (uint32) format on r3
291 "Check if bandwidth is properly encoded with non IEEE floatig-point (uint32) format on r3"
293 json_file
= "{}/r3/bgp-route-1.json".format(CWD
)
294 expected
= json
.loads(open(json_file
).read())
296 topotest
.router_json_cmp
, r3
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
298 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
299 assertmsg
= "JSON output mismatch on r3"
300 assert result
is None, assertmsg
302 # Check multipath on super-spine router r1
303 logger
.info("Check multipath on super-spine router r1")
304 json_file
= "{}/r1/bgp-route-2.json".format(CWD
)
305 expected
= json
.loads(open(json_file
).read())
307 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
309 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
310 assertmsg
= "JSON output mismatch on super-spine router r1"
311 assert result
is None, assertmsg
313 # Check weighted ECMP is in effect on the super-spine router r1
314 logger
.info("Check weighted ECMP is in effect on the super-spine router r1")
315 json_file
= "{}/r1/ip-route-1.json".format(CWD
)
316 expected
= json
.loads(open(json_file
).read())
318 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
320 _
, result
= topotest
.run_and_expect(test_func
, None, count
=50, wait
=0.5)
321 assertmsg
= "JSON output mismatch on super-spine router r1"
322 assert result
is None, assertmsg
325 def test_weighted_ecmp_link_flap():
326 "Test #4: Test weighted ECMP rebalancing upon change (link flap)"
327 logger
.info("\nTest #4: Test weighted ECMP rebalancing upon change (link flap)")
330 if tgen
.routers_have_failure():
331 pytest
.skip("skipped because of router(s) failure")
333 r1
= tgen
.gears
["r1"]
334 r2
= tgen
.gears
["r2"]
336 # Bring down link on server r9
337 logger
.info("Bring down link on server r9")
339 tgen
.net
["r9"].cmd("ip link set dev r9-eth1 down")
341 # Check spine router r2 has only one path
342 logger
.info("Check spine router r2 has only one path")
344 json_file
= "{}/r2/ip-route-3.json".format(CWD
)
345 expected
= json
.loads(open(json_file
).read())
347 topotest
.router_json_cmp
, r2
, "show ip route 198.10.1.1/32 json", expected
349 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
350 assertmsg
= "JSON output mismatch on spine router r2"
351 assert result
is None, assertmsg
353 # Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1
355 "Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1"
358 json_file
= "{}/r1/bgp-route-3.json".format(CWD
)
359 expected
= json
.loads(open(json_file
).read())
361 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
363 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
364 assertmsg
= "JSON output mismatch on super-spine router r1"
365 assert result
is None, assertmsg
367 json_file
= "{}/r1/ip-route-2.json".format(CWD
)
368 expected
= json
.loads(open(json_file
).read())
370 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
372 _
, result
= topotest
.run_and_expect(test_func
, None, count
=50, wait
=0.5)
373 assertmsg
= "JSON output mismatch on super-spine router r1"
374 assert result
is None, assertmsg
376 # Bring up link on server r9
377 logger
.info("Bring up link on server r9")
379 tgen
.net
["r9"].cmd("ip link set dev r9-eth1 up")
381 # Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1
383 "Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1"
386 json_file
= "{}/r1/bgp-route-2.json".format(CWD
)
387 expected
= json
.loads(open(json_file
).read())
389 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
391 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
392 assertmsg
= "JSON output mismatch on super-spine router r1"
393 assert result
is None, assertmsg
395 json_file
= "{}/r1/ip-route-1.json".format(CWD
)
396 expected
= json
.loads(open(json_file
).read())
398 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
400 _
, result
= topotest
.run_and_expect(test_func
, None, count
=50, wait
=0.5)
401 assertmsg
= "JSON output mismatch on super-spine router r1"
402 assert result
is None, assertmsg
405 def test_weighted_ecmp_second_anycast_ip():
406 "Test #5: Test weighted ECMP for a second anycast IP"
407 logger
.info("\nTest #5: Test weighted ECMP for a second anycast IP")
410 if tgen
.routers_have_failure():
411 pytest
.skip("skipped because of router(s) failure")
413 r1
= tgen
.gears
["r1"]
414 r2
= tgen
.gears
["r2"]
416 # Configure anycast IP on additional server r7, r9 and r10
417 logger
.info("Configure anycast IP on server r7, r9 and r10")
419 tgen
.net
["r7"].cmd("ip addr add 198.10.1.11/32 dev r7-eth1")
420 tgen
.net
["r9"].cmd("ip addr add 198.10.1.11/32 dev r9-eth1")
421 tgen
.net
["r10"].cmd("ip addr add 198.10.1.11/32 dev r10-eth1")
423 # Check link-bandwidth and weighted ECMP on super-spine router r1
424 logger
.info("Check link-bandwidth and weighted ECMP on super-spine router r1")
426 json_file
= "{}/r1/bgp-route-4.json".format(CWD
)
427 expected
= json
.loads(open(json_file
).read())
429 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.11/32 json", expected
431 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
432 assertmsg
= "JSON output mismatch on super-spine router r1"
433 assert result
is None, assertmsg
435 json_file
= "{}/r1/ip-route-3.json".format(CWD
)
436 expected
= json
.loads(open(json_file
).read())
438 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.11/32 json", expected
440 _
, result
= topotest
.run_and_expect(test_func
, None, count
=50, wait
=0.5)
441 assertmsg
= "JSON output mismatch on super-spine router r1"
442 assert result
is None, assertmsg
445 def test_paths_with_and_without_linkbw():
446 "Test #6: Test paths with and without link-bandwidth - receiver should resort to regular ECMP"
448 "\nTest #6: Test paths with and without link-bandwidth - receiver should resort to regular ECMP"
452 if tgen
.routers_have_failure():
453 pytest
.skip("skipped because of router(s) failure")
455 r1
= tgen
.gears
["r1"]
457 # Configure leaf router r6 to not advertise any link-bandwidth
458 logger
.info("Configure leaf router r6 to not advertise any link-bandwidth")
461 '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"'
464 # Check link-bandwidth change on super-spine router r1
465 logger
.info("Check link-bandwidth change on super-spine router r1")
467 json_file
= "{}/r1/bgp-route-5.json".format(CWD
)
468 expected
= json
.loads(open(json_file
).read())
470 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
472 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
473 assertmsg
= "JSON output mismatch on super-spine router r1"
474 assert result
is None, assertmsg
476 # Check super-spine router r1 resorts to regular ECMP
477 logger
.info("Check super-spine router r1 resorts to regular ECMP")
479 json_file
= "{}/r1/ip-route-4.json".format(CWD
)
480 expected
= json
.loads(open(json_file
).read())
482 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
484 _
, result
= topotest
.run_and_expect(test_func
, None, count
=50, wait
=0.5)
485 assertmsg
= "JSON output mismatch on super-spine router r1"
486 assert result
is None, assertmsg
488 json_file
= "{}/r1/ip-route-5.json".format(CWD
)
489 expected
= json
.loads(open(json_file
).read())
491 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.11/32 json", expected
493 _
, result
= topotest
.run_and_expect(test_func
, None, count
=50, wait
=0.5)
494 assertmsg
= "JSON output mismatch on super-spine router r1"
495 assert result
is None, assertmsg
498 def test_linkbw_handling_options():
499 "Test #7: Test different options for processing link-bandwidth on the receiver"
501 "\nTest #7: Test different options for processing link-bandwidth on the receiver"
505 if tgen
.routers_have_failure():
506 pytest
.skip("skipped because of router(s) failure")
508 r1
= tgen
.gears
["r1"]
510 # Configure super-spine r1 to skip multipaths without link-bandwidth
511 logger
.info("Configure super-spine r1 to skip multipaths without link-bandwidth")
514 'vtysh -c "conf t" -c "router bgp 65101" -c "bgp bestpath bandwidth skip-missing"'
517 # Check super-spine router r1 resorts to only one path as other path is skipped
519 "Check super-spine router r1 resorts to only one path as other path is skipped"
522 json_file
= "{}/r1/ip-route-6.json".format(CWD
)
523 expected
= json
.loads(open(json_file
).read())
525 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
527 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
528 assertmsg
= "JSON output mismatch on super-spine router r1"
529 assert result
is None, assertmsg
531 json_file
= "{}/r1/ip-route-7.json".format(CWD
)
532 expected
= json
.loads(open(json_file
).read())
534 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.11/32 json", expected
536 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
537 assertmsg
= "JSON output mismatch on super-spine router r1"
538 assert result
is None, assertmsg
540 # Configure super-spine r1 to use default-weight for multipaths without link-bandwidth
542 "Configure super-spine r1 to use default-weight for multipaths without link-bandwidth"
546 'vtysh -c "conf t" -c "router bgp 65101" -c "bgp bestpath bandwidth default-weight-for-missing"'
549 # Check super-spine router r1 uses ECMP with weight 1 for path without link-bandwidth
551 "Check super-spine router r1 uses ECMP with weight 1 for path without link-bandwidth"
554 json_file
= "{}/r1/ip-route-8.json".format(CWD
)
555 expected
= json
.loads(open(json_file
).read())
557 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
559 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
560 assertmsg
= "JSON output mismatch on super-spine router r1"
561 assert result
is None, assertmsg
563 json_file
= "{}/r1/ip-route-9.json".format(CWD
)
564 expected
= json
.loads(open(json_file
).read())
566 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.11/32 json", expected
568 _
, result
= topotest
.run_and_expect(test_func
, None, count
=200, wait
=0.5)
569 assertmsg
= "JSON output mismatch on super-spine router r1"
570 assert result
is None, assertmsg
573 if __name__
== "__main__":
574 args
= ["-s"] + sys
.argv
[1:]
575 sys
.exit(pytest
.main(args
))