]>
git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/bgp_link_bw_ip/test_bgp_linkbw_ip.py
4 # test_bgp_linkbw_ip.py
6 # Copyright (c) 2020 by
7 # Cumulus Networks, Inc
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
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
26 test_bgp_linkbw_ip.py: Test weighted ECMP using BGP link-bandwidth
32 from functools
import partial
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
, "../"))
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
46 # Required to instantiate the topology builder class.
47 from mininet
.topo
import Topo
49 pytestmark
= [pytest
.mark
.bgpd
]
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
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
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.
70 class BgpLinkBwTopo(Topo
):
71 "Test topology builder"
73 def build(self
, *_args
, **_opts
):
75 tgen
= get_topogen(self
)
77 # Create 10 routers - 1 super-spine, 2 spines, 3 leafs
80 for i
in range(1, 11):
81 routers
[i
] = tgen
.add_router("r{}".format(i
))
83 # Create 13 "switches" - to interconnect the above routers
85 for i
in range(1, 14):
86 switches
[i
] = tgen
.add_switch("s{}".format(i
))
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"])
94 # Interconnect R2 (spine in pod-1) to R4 and R5 (the associated
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"])
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"])
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"])
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"])
122 def setup_module(mod
):
123 "Sets up the pytest environment"
124 tgen
= Topogen(BgpLinkBwTopo
, mod
.__name
__)
125 tgen
.start_topology()
127 router_list
= tgen
.routers()
128 for rname
, router
in router_list
.items():
130 TopoRouter
.RD_ZEBRA
, os
.path
.join(CWD
, "{}/zebra.conf".format(rname
))
133 TopoRouter
.RD_BGP
, os
.path
.join(CWD
, "{}/bgpd.conf".format(rname
))
136 # Initialize all routers.
142 def teardown_module(mod
):
143 "Teardown the pytest environment"
148 def test_bgp_linkbw_adv():
149 "Test #1: Test BGP link-bandwidth advertisement based on number of multipaths"
151 "\nTest #1: Test BGP link-bandwidth advertisement based on number of multipaths"
155 if tgen
.routers_have_failure():
156 pytest
.skip("skipped because of router(s) failure")
158 r1
= tgen
.gears
["r1"]
159 r2
= tgen
.gears
["r2"]
161 # Configure anycast IP on server r7
162 logger
.info("Configure anycast IP on server r7")
164 tgen
.net
["r7"].cmd("ip addr add 198.10.1.1/32 dev r7-eth1")
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")
169 json_file
= "{}/r2/bgp-route-1.json".format(CWD
)
170 expected
= json
.loads(open(json_file
).read())
172 topotest
.router_json_cmp
, r2
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
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
178 # Check on spine router r2 that default weight is used as there is no multipath
180 "Check on spine router r2 that default weight is used as there is no multipath"
183 json_file
= "{}/r2/ip-route-1.json".format(CWD
)
184 expected
= json
.loads(open(json_file
).read())
186 topotest
.router_json_cmp
, r2
, "show ip route 198.10.1.1/32 json", expected
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
192 # Check on super-spine router r1 that link-bw has been propagated by spine router r2
194 "Check on super-spine router r1 that link-bw has been propagated by spine router r2"
197 json_file
= "{}/r1/bgp-route-1.json".format(CWD
)
198 expected
= json
.loads(open(json_file
).read())
200 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
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
207 def test_bgp_cumul_linkbw():
208 "Test #2: Test cumulative link-bandwidth propagation"
209 logger
.info("\nTest #2: Test cumulative link-bandwidth propagation")
212 if tgen
.routers_have_failure():
213 pytest
.skip("skipped because of router(s) failure")
215 r1
= tgen
.gears
["r1"]
216 r2
= tgen
.gears
["r2"]
217 r4
= tgen
.gears
["r4"]
219 # Configure anycast IP on additional server r8
220 logger
.info("Configure anycast IP on server r8")
222 tgen
.net
["r8"].cmd("ip addr add 198.10.1.1/32 dev r8-eth1")
224 # Check multipath on leaf router r4
225 logger
.info("Check multipath on leaf router r4")
227 json_file
= "{}/r4/bgp-route-1.json".format(CWD
)
228 expected
= json
.loads(open(json_file
).read())
230 topotest
.router_json_cmp
, r4
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
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
236 # Check regular ECMP is in effect on leaf router r4
237 logger
.info("Check regular ECMP is in effect on leaf router r4")
239 json_file
= "{}/r4/ip-route-1.json".format(CWD
)
240 expected
= json
.loads(open(json_file
).read())
242 topotest
.router_json_cmp
, r4
, "show ip route 198.10.1.1/32 json", expected
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
248 # Check on spine router r2 that leaf has propagated the cumulative link-bw based on num-multipaths
250 "Check on spine router r2 that leaf has propagated the cumulative link-bw based on num-multipaths"
253 json_file
= "{}/r2/bgp-route-2.json".format(CWD
)
254 expected
= json
.loads(open(json_file
).read())
256 topotest
.router_json_cmp
, r2
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
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
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")
268 if tgen
.routers_have_failure():
269 pytest
.skip("skipped because of router(s) failure")
271 r1
= tgen
.gears
["r1"]
272 r2
= tgen
.gears
["r2"]
274 # Configure anycast IP on additional server r9
275 logger
.info("Configure anycast IP on server r9")
277 tgen
.net
["r9"].cmd("ip addr add 198.10.1.1/32 dev r9-eth1")
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())
284 topotest
.router_json_cmp
, r2
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
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
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")
293 json_file
= "{}/r2/ip-route-2.json".format(CWD
)
294 expected
= json
.loads(open(json_file
).read())
296 topotest
.router_json_cmp
, r2
, "show ip route 198.10.1.1/32 json", expected
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
302 # Configure anycast IP on additional server r10
303 logger
.info("Configure anycast IP on server r10")
305 tgen
.net
["r10"].cmd("ip addr add 198.10.1.1/32 dev r10-eth1")
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())
312 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
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
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())
323 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
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
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)")
335 if tgen
.routers_have_failure():
336 pytest
.skip("skipped because of router(s) failure")
338 r1
= tgen
.gears
["r1"]
339 r2
= tgen
.gears
["r2"]
341 # Bring down link on server r9
342 logger
.info("Bring down link on server r9")
344 tgen
.net
["r9"].cmd("ip link set dev r9-eth1 down")
346 # Check spine router r2 has only one path
347 logger
.info("Check spine router r2 has only one path")
349 json_file
= "{}/r2/ip-route-3.json".format(CWD
)
350 expected
= json
.loads(open(json_file
).read())
352 topotest
.router_json_cmp
, r2
, "show ip route 198.10.1.1/32 json", expected
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
358 # Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1
360 "Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1"
363 json_file
= "{}/r1/bgp-route-3.json".format(CWD
)
364 expected
= json
.loads(open(json_file
).read())
366 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
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
372 json_file
= "{}/r1/ip-route-2.json".format(CWD
)
373 expected
= json
.loads(open(json_file
).read())
375 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
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
381 # Bring up link on server r9
382 logger
.info("Bring up link on server r9")
384 tgen
.net
["r9"].cmd("ip link set dev r9-eth1 up")
386 # Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1
388 "Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1"
391 json_file
= "{}/r1/bgp-route-2.json".format(CWD
)
392 expected
= json
.loads(open(json_file
).read())
394 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
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
400 json_file
= "{}/r1/ip-route-1.json".format(CWD
)
401 expected
= json
.loads(open(json_file
).read())
403 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
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
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")
415 if tgen
.routers_have_failure():
416 pytest
.skip("skipped because of router(s) failure")
418 r1
= tgen
.gears
["r1"]
419 r2
= tgen
.gears
["r2"]
421 # Configure anycast IP on additional server r7, r9 and r10
422 logger
.info("Configure anycast IP on server r7, r9 and r10")
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")
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")
431 json_file
= "{}/r1/bgp-route-4.json".format(CWD
)
432 expected
= json
.loads(open(json_file
).read())
434 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.11/32 json", expected
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
440 json_file
= "{}/r1/ip-route-3.json".format(CWD
)
441 expected
= json
.loads(open(json_file
).read())
443 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.11/32 json", expected
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
450 def test_paths_with_and_without_linkbw():
451 "Test #6: Test paths with and without link-bandwidth - receiver should resort to regular ECMP"
453 "\nTest #6: Test paths with and without link-bandwidth - receiver should resort to regular ECMP"
457 if tgen
.routers_have_failure():
458 pytest
.skip("skipped because of router(s) failure")
460 r1
= tgen
.gears
["r1"]
462 # Configure leaf router r6 to not advertise any link-bandwidth
463 logger
.info("Configure leaf router r6 to not advertise any link-bandwidth")
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"'
469 # Check link-bandwidth change on super-spine router r1
470 logger
.info("Check link-bandwidth change on super-spine router r1")
472 json_file
= "{}/r1/bgp-route-5.json".format(CWD
)
473 expected
= json
.loads(open(json_file
).read())
475 topotest
.router_json_cmp
, r1
, "show bgp ipv4 uni 198.10.1.1/32 json", expected
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
481 # Check super-spine router r1 resorts to regular ECMP
482 logger
.info("Check super-spine router r1 resorts to regular ECMP")
484 json_file
= "{}/r1/ip-route-4.json".format(CWD
)
485 expected
= json
.loads(open(json_file
).read())
487 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
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
493 json_file
= "{}/r1/ip-route-5.json".format(CWD
)
494 expected
= json
.loads(open(json_file
).read())
496 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.11/32 json", expected
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
503 def test_linkbw_handling_options():
504 "Test #7: Test different options for processing link-bandwidth on the receiver"
506 "\nTest #7: Test different options for processing link-bandwidth on the receiver"
510 if tgen
.routers_have_failure():
511 pytest
.skip("skipped because of router(s) failure")
513 r1
= tgen
.gears
["r1"]
515 # Configure super-spine r1 to skip multipaths without link-bandwidth
516 logger
.info("Configure super-spine r1 to skip multipaths without link-bandwidth")
519 'vtysh -c "conf t" -c "router bgp 65101" -c "bgp bestpath bandwidth skip-missing"'
522 # Check super-spine router r1 resorts to only one path as other path is skipped
524 "Check super-spine router r1 resorts to only one path as other path is skipped"
527 json_file
= "{}/r1/ip-route-6.json".format(CWD
)
528 expected
= json
.loads(open(json_file
).read())
530 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
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
536 json_file
= "{}/r1/ip-route-7.json".format(CWD
)
537 expected
= json
.loads(open(json_file
).read())
539 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.11/32 json", expected
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
545 # Configure super-spine r1 to use default-weight for multipaths without link-bandwidth
547 "Configure super-spine r1 to use default-weight for multipaths without link-bandwidth"
551 'vtysh -c "conf t" -c "router bgp 65101" -c "bgp bestpath bandwidth default-weight-for-missing"'
554 # Check super-spine router r1 uses ECMP with weight 1 for path without link-bandwidth
556 "Check super-spine router r1 uses ECMP with weight 1 for path without link-bandwidth"
559 json_file
= "{}/r1/ip-route-8.json".format(CWD
)
560 expected
= json
.loads(open(json_file
).read())
562 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.1/32 json", expected
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
568 json_file
= "{}/r1/ip-route-9.json".format(CWD
)
569 expected
= json
.loads(open(json_file
).read())
571 topotest
.router_json_cmp
, r1
, "show ip route 198.10.1.11/32 json", expected
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
578 if __name__
== "__main__":
579 args
= ["-s"] + sys
.argv
[1:]
580 sys
.exit(pytest
.main(args
))