]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py
Merge pull request #13455 from sri-mohan1/srib-ldpd
[mirror_frr.git] / tests / topotests / bgp_evpn_vxlan_svd_topo1 / test_bgp_evpn_vxlan_svd.py
CommitLineData
1ca713b3
SR
1#!/usr/bin/env python
2
3#
4# test_bgp_evpn_vxlan_svd.py
5# Part of NetDEF Topology Tests
6#
7# Copyright (c) 2020 NVIDIA Corporation
8#
9# Permission to use, copy, modify, and/or distribute this software
10# for any purpose with or without fee is hereby granted, provided
11# that the above copyright notice and this permission notice appear
12# in all copies.
13#
14# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
15# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
17# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
18# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
19# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
20# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
21# OF THIS SOFTWARE.
22#
23
24"""
25test_bgp_evpn_vxlan.py: Test VXLAN EVPN MAC and route signalling over BGP
26using Single Vxlan Device Configurtion
27"""
28
29import os
30import sys
31import json
32from functools import partial
33from time import sleep
34import pytest
35
36# Save the Current Working Directory to find configuration files.
37CWD = os.path.dirname(os.path.realpath(__file__))
38sys.path.append(os.path.join(CWD, "../"))
39
40# pylint: disable=C0413
41# Import topogen and topotest helpers
42from lib import topotest
43from lib.topogen import Topogen, TopoRouter, get_topogen
44from lib.topolog import logger
568d4324 45from lib.common_config import required_linux_kernel_version
1ca713b3
SR
46
47# Required to instantiate the topology builder class.
48
49pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd]
50
51
52def build_topo(tgen):
53 "Build function"
54
55 # This function only purpose is to define allocation and relationship
56 # between routers, switches and hosts.
57 #
58 #
59 # Create routers
60 tgen.add_router("P1")
61 tgen.add_router("PE1")
62 tgen.add_router("PE2")
63 tgen.add_router("host1")
64 tgen.add_router("host2")
65
66 # Host1-PE1
67 switch = tgen.add_switch("s1")
68 switch.add_link(tgen.gears["host1"])
69 switch.add_link(tgen.gears["PE1"])
70
71 # PE1-P1
72 switch = tgen.add_switch("s2")
73 switch.add_link(tgen.gears["PE1"])
74 switch.add_link(tgen.gears["P1"])
75
76 # P1-PE2
77 switch = tgen.add_switch("s3")
78 switch.add_link(tgen.gears["P1"])
79 switch.add_link(tgen.gears["PE2"])
80
81 # PE2-host2
82 switch = tgen.add_switch("s4")
83 switch.add_link(tgen.gears["PE2"])
84 switch.add_link(tgen.gears["host2"])
85
d6f34434 86
1ca713b3
SR
87def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf):
88 pe = tgen.gears[pe_name]
89
90 # configure vlan aware bridge
91 pe.run("ip link add name bridge type bridge stp_state 0")
92 pe.run("ip link set dev bridge type bridge vlan_filtering 1")
93 pe.run("bridge vlan add vid 1 dev bridge self")
94 pe.run("ip link set dev bridge up")
95
96 # setup svi
97 pe.run("ip link add link bridge name vlan1 type vlan id 1 protocol 802.1q")
98 pe.run("ip link set dev vlan1 up")
99 pe.run("ip addr add {0} dev vlan1".format(svi_ip))
100 pe.run("/sbin/sysctl net.ipv4.conf.vlan1.arp_accept=1")
101
102 # setup single vxlan device
103 pe.run(
d6f34434
DS
104 "ip link add dev vxlan0 type vxlan dstport 4789 local {0} nolearning external".format(
105 tunnel_local_ip
106 )
1ca713b3
SR
107 )
108 pe.run("ip link set dev vxlan0 master bridge")
109 pe.run("bridge link set dev vxlan0 vlan_tunnel on")
110 pe.run("bridge link set dev vxlan0 neigh_suppress on")
111 pe.run("bridge link set dev vxlan0 learning off")
112 pe.run("bridge vlan add dev vxlan0 vid 1")
113 pe.run("bridge vlan add dev vxlan0 vid 1 tunnel_info id 101")
114 pe.run("ip link set up dev vxlan0")
115
116 # setup PE interface
117 pe.run("ip link set dev {0}-{1} master bridge".format(pe_name, intf))
118 pe.run("bridge vlan del vid 1 dev {0}-{1}".format(pe_name, intf))
119 pe.run("bridge vlan del vid 1 untagged pvid dev {0}-{1}".format(pe_name, intf))
120 pe.run("bridge vlan add vid 1 dev {0}-{1}".format(pe_name, intf))
121 pe.run("bridge vlan add vid 1 pvid untagged dev {0}-{1}".format(pe_name, intf))
122
ef944129
SW
123 # l3vni 100
124 pe.run("ip link add vrf-red type vrf table 1400")
125 pe.run("ip link add link bridge name vlan100 type vlan id 100 protocol 802.1q")
126 pe.run("ip link set dev vlan100 master vrf-blue")
127 pe.run("ip link set dev vlan100 up")
128 pe.run("bridge vlan add vid 100 dev bridge self")
129 pe.run("bridge vlan add dev vxlan0 vid 100")
130 pe.run("bridge vlan add dev vxlan0 vid 100 tunnel_info id 100")
131
132 # add a vrf for testing DVNI
133 if pe_name == "PE2":
134 pe.run("ip link add vrf-blue type vrf table 2400")
135 pe.run("ip link add link bridge name vlan300 type vlan id 300 protocol 802.1q")
136 pe.run("ip link set dev vlan300 master vrf-blue")
137 pe.run("ip link set dev vlan300 up")
138 pe.run("bridge vlan add vid 300 dev bridge self")
139 pe.run("bridge vlan add dev vxlan0 vid 300")
140 pe.run("bridge vlan add dev vxlan0 vid 300 tunnel_info id 300")
1ca713b3 141
d6f34434 142
1ca713b3
SR
143def setup_p_router(tgen, p_name):
144 p1 = tgen.gears[p_name]
145 p1.run("sysctl -w net.ipv4.ip_forward=1")
1ca713b3 146
d6f34434 147
1ca713b3
SR
148def setup_module(mod):
149 "Sets up the pytest environment"
150
568d4324
SW
151 result = required_linux_kernel_version("5.7")
152 if result is not True:
153 pytest.skip("Kernel requirements are not met, kernel version should be >= 5.7")
154
1ca713b3
SR
155 # This function initiates the topology build with Topogen...
156 tgen = Topogen(build_topo, mod.__name__)
157
158 # ... and here it calls Mininet initialization functions.
159 tgen.start_topology()
160
161 setup_pe_router(tgen, "PE1", "10.10.10.10", "10.10.1.1/24", "eth0")
162 setup_pe_router(tgen, "PE2", "10.30.30.30", "10.10.1.3/24", "eth1")
163 setup_p_router(tgen, "P1")
164
165 # This is a sample of configuration loading.
166 router_list = tgen.routers()
167
168 # For all registred routers, load the zebra configuration file
169 for rname, router in router_list.items():
170 router.load_config(
171 TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
172 )
173 router.load_config(
174 TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname))
175 )
176 router.load_config(
177 TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
178 )
179
180 # After loading the configurations, this function loads configured daemons.
181 tgen.start_router()
182
183
184def teardown_module(mod):
185 "Teardown the pytest environment"
186 tgen = get_topogen()
187
d6f34434 188 # tgen.mininet_cli()
1ca713b3
SR
189 # This function tears down the whole topology.
190 tgen.stop_topology()
191
192
193def show_vni_json_elide_ifindex(pe, vni, expected):
194 output_json = pe.vtysh_cmd("show evpn vni {} json".format(vni), isjson=True)
195
196 if "ifindex" in output_json:
197 output_json.pop("ifindex")
198
199 return topotest.json_cmp(output_json, expected)
200
201
202def check_vni_macs_present(tgen, router, vni, maclist):
203 result = router.vtysh_cmd("show evpn mac vni {} json".format(vni), isjson=True)
204 for rname, ifname in maclist:
205 m = tgen.net.macs[(rname, ifname)]
206 if m not in result["macs"]:
207 return "MAC ({}) for interface {} on {} missing on {} from {}".format(
208 m, ifname, rname, router.name, json.dumps(result, indent=4)
209 )
210 return None
211
d6f34434 212
6246b57e 213def check_flood_entry_present(pe, vni, vtep):
51c33a57
SW
214 if not topotest.iproute2_is_fdb_get_capable():
215 return None
216
d6f34434
DS
217 output = pe.run(
218 "bridge fdb get 00:00:00:00:00:00 dev vxlan0 vni {} self".format(vni)
219 )
6246b57e
SW
220
221 if str(vtep) not in output:
222 return output
223
224 return None
1ca713b3 225
d6f34434 226
1ca713b3
SR
227def test_pe1_converge_evpn():
228 "Wait for protocol convergence"
229
230 tgen = get_topogen()
231 # Don't run this test if we have any failure.
232 if tgen.routers_have_failure():
233 pytest.skip(tgen.errors)
234
235 pe1 = tgen.gears["PE1"]
236 json_file = "{}/{}/evpn.vni.json".format(CWD, pe1.name)
237 expected = json.loads(open(json_file).read())
238
239 test_func = partial(show_vni_json_elide_ifindex, pe1, 101, expected)
240 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
241 assertmsg = '"{}" JSON output mismatches'.format(pe1.name)
242
18a8f407
DS
243 # Let's ensure that the hosts have actually tried talking to
244 # each other. Otherwise under certain startup conditions
245 # they may not actually do any l2 arp'ing and as such
246 # the bridges won't know about the hosts on their networks
247 host1 = tgen.gears["host1"]
248 host1.run("ping -c 1 10.10.1.56")
249 host2 = tgen.gears["host2"]
250 host2.run("ping -c 1 10.10.1.55")
251
1ca713b3
SR
252 test_func = partial(
253 check_vni_macs_present,
254 tgen,
255 pe1,
256 101,
257 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
258 )
259 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
260 if result:
261 logger.warning("%s", result)
262 assert None, '"{}" missing expected MACs'.format(pe1.name)
263
6246b57e
SW
264 vtep = "10.30.30.30"
265 test_func = partial(check_flood_entry_present, pe1, 101, vtep)
266 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
267 assertmsg = '"{}" Flood FDB Entry for VTEP {} not found'.format(pe1.name, vtep)
268 assert result is None, assertmsg
1ca713b3 269
d6f34434 270
1ca713b3
SR
271def test_pe2_converge_evpn():
272 "Wait for protocol convergence"
273
274 tgen = get_topogen()
d6f34434 275 # Don't run this test if we have any failure.
1ca713b3
SR
276 if tgen.routers_have_failure():
277 pytest.skip(tgen.errors)
278
279 pe2 = tgen.gears["PE2"]
280 json_file = "{}/{}/evpn.vni.json".format(CWD, pe2.name)
281 expected = json.loads(open(json_file).read())
282
283 test_func = partial(show_vni_json_elide_ifindex, pe2, 101, expected)
284 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
285 assertmsg = '"{}" JSON output mismatches'.format(pe2.name)
286 assert result is None, assertmsg
287
288 test_func = partial(
289 check_vni_macs_present,
290 tgen,
291 pe2,
292 101,
293 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
294 )
295 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
296 if result:
297 logger.warning("%s", result)
298 assert None, '"{}" missing expected MACs'.format(pe2.name)
299
6246b57e
SW
300 vtep = "10.10.10.10"
301 test_func = partial(check_flood_entry_present, pe2, 101, vtep)
302 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
303 assertmsg = '"{}" Flood FDB Entry for VTEP {} not found'.format(pe2.name, vtep)
304 assert result is None, assertmsg
1ca713b3 305
d6f34434 306
1ca713b3
SR
307def mac_learn_test(host, local):
308 "check the host MAC gets learned by the VNI"
309
310 host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
311 int_lines = host_output.splitlines()
312 for line in int_lines:
313 line_items = line.split(": ")
314 if "HWaddr" in line_items[0]:
315 mac = line_items[1]
316 break
317
318 mac_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
319 mac_output_json = json.loads(mac_output)
320 assertmsg = "Local MAC output does not match interface mac {}".format(mac)
321 assert mac_output_json[mac]["type"] == "local", assertmsg
322
323
324def mac_test_local_remote(local, remote):
325 "test MAC transfer between local and remote"
326
327 local_output = local.vtysh_cmd("show evpn mac vni all json")
328 remote_output = remote.vtysh_cmd("show evpn mac vni all json")
329 local_output_vni = local.vtysh_cmd("show evpn vni detail json")
330 local_output_json = json.loads(local_output)
331 remote_output_json = json.loads(remote_output)
332 local_output_vni_json = json.loads(local_output_vni)
333
334 for vni in local_output_json:
ef944129
SW
335 if vni not in remote_output_json:
336 continue
337
1ca713b3
SR
338 mac_list = local_output_json[vni]["macs"]
339 for mac in mac_list:
340 if mac_list[mac]["type"] == "local" and mac_list[mac]["intf"] != "br101":
341 assertmsg = "JSON output mismatches local: {} remote: {}".format(
342 local_output_vni_json[0]["vtepIp"],
343 remote_output_json[vni]["macs"][mac]["remoteVtep"],
344 )
345 assert (
346 remote_output_json[vni]["macs"][mac]["remoteVtep"]
347 == local_output_vni_json[0]["vtepIp"]
348 ), assertmsg
349
350
351def test_learning_pe1():
352 "test MAC learning on PE1"
353
354 tgen = get_topogen()
355 # Don't run this test if we have any failure.
356 if tgen.routers_have_failure():
357 pytest.skip(tgen.errors)
358
359 host1 = tgen.gears["host1"]
360 pe1 = tgen.gears["PE1"]
361 mac_learn_test(host1, pe1)
362
363
364def test_learning_pe2():
365 "test MAC learning on PE2"
366
367 tgen = get_topogen()
368 # Don't run this test if we have any failure.
369 if tgen.routers_have_failure():
370 pytest.skip(tgen.errors)
371
372 host2 = tgen.gears["host2"]
373 pe2 = tgen.gears["PE2"]
374 mac_learn_test(host2, pe2)
375
376
377def test_local_remote_mac_pe1():
378 "Test MAC transfer PE1 local and PE2 remote"
379
380 tgen = get_topogen()
381 # Don't run this test if we have any failure.
382 if tgen.routers_have_failure():
383 pytest.skip(tgen.errors)
384
385 pe1 = tgen.gears["PE1"]
386 pe2 = tgen.gears["PE2"]
387 mac_test_local_remote(pe1, pe2)
388
389
390def test_local_remote_mac_pe2():
391 "Test MAC transfer PE2 local and PE1 remote"
392
393 tgen = get_topogen()
394 # Don't run this test if we have any failure.
395 if tgen.routers_have_failure():
396 pytest.skip(tgen.errors)
397
398 pe1 = tgen.gears["PE1"]
399 pe2 = tgen.gears["PE2"]
400 mac_test_local_remote(pe2, pe1)
401
1ca713b3
SR
402
403def ip_learn_test(tgen, host, local, remote, ip_addr):
404 "check the host IP gets learned by the VNI"
405 host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
406 int_lines = host_output.splitlines()
407 for line in int_lines:
408 line_items = line.split(": ")
409 if "HWaddr" in line_items[0]:
410 mac = line_items[1]
411 break
d6f34434 412 # print(host_output)
1ca713b3
SR
413
414 # check we have a local association between the MAC and IP
415 local_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
d6f34434 416 # print(local_output)
1ca713b3
SR
417 local_output_json = json.loads(local_output)
418 mac_type = local_output_json[mac]["type"]
419 assertmsg = "Failed to learn local IP address on host {}".format(host.name)
420 assert local_output_json[mac]["neighbors"] != "none", assertmsg
421 learned_ip = local_output_json[mac]["neighbors"]["active"][0]
422
423 assertmsg = "local learned mac wrong type: {} ".format(mac_type)
424 assert mac_type == "local", assertmsg
425
426 assertmsg = (
427 "learned address mismatch with configured address host: {} learned: {}".format(
428 ip_addr, learned_ip
429 )
430 )
431 assert ip_addr == learned_ip, assertmsg
432
433 # now lets check the remote
434 count = 0
435 converged = False
436 while count < 30:
437 remote_output = remote.vtysh_cmd(
438 "show evpn mac vni 101 mac {} json".format(mac)
439 )
d6f34434 440 # print(remote_output)
1ca713b3
SR
441 remote_output_json = json.loads(remote_output)
442 type = remote_output_json[mac]["type"]
443 if not remote_output_json[mac]["neighbors"] == "none":
444 # due to a kernel quirk, learned IPs can be inactive
445 if (
446 remote_output_json[mac]["neighbors"]["active"]
447 or remote_output_json[mac]["neighbors"]["inactive"]
448 ):
449 converged = True
450 break
451 count += 1
452 sleep(1)
453
d6f34434 454 # print("tries: {}".format(count))
1ca713b3
SR
455 assertmsg = "{} remote learned mac no address: {} ".format(host.name, mac)
456 # some debug for this failure
457 if not converged == True:
458 log_output = remote.run("cat zebra.log")
d6f34434 459 # print(log_output)
1ca713b3
SR
460
461 assert converged == True, assertmsg
462 if remote_output_json[mac]["neighbors"]["active"]:
463 learned_ip = remote_output_json[mac]["neighbors"]["active"][0]
464 else:
465 learned_ip = remote_output_json[mac]["neighbors"]["inactive"][0]
466 assertmsg = "remote learned mac wrong type: {} ".format(type)
467 assert type == "remote", assertmsg
468
469 assertmsg = "remote learned address mismatch with configured address host: {} learned: {}".format(
470 ip_addr, learned_ip
471 )
472 assert ip_addr == learned_ip, assertmsg
473
474
475def test_ip_pe1_learn():
476 "run the IP learn test for PE1"
477
478 tgen = get_topogen()
479 # Don't run this test if we have any failure.
480 if tgen.routers_have_failure():
481 pytest.skip(tgen.errors)
482
483 host1 = tgen.gears["host1"]
484 pe1 = tgen.gears["PE1"]
485 pe2 = tgen.gears["PE2"]
d6f34434
DS
486 # pe2.vtysh_cmd("debug zebra vxlan")
487 # pe2.vtysh_cmd("debug zebra kernel")
1ca713b3
SR
488 # lets populate that arp cache
489 host1.run("ping -c1 10.10.1.1")
490 ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55")
491 # tgen.mininet_cli()
492
493
494def test_ip_pe2_learn():
495 "run the IP learn test for PE2"
496
497 tgen = get_topogen()
498 # Don't run this test if we have any failure.
499 if tgen.routers_have_failure():
500 pytest.skip(tgen.errors)
501
502 host2 = tgen.gears["host2"]
503 pe1 = tgen.gears["PE1"]
504 pe2 = tgen.gears["PE2"]
d6f34434
DS
505 # pe1.vtysh_cmd("debug zebra vxlan")
506 # pe1.vtysh_cmd("debug zebra kernel")
1ca713b3
SR
507 # lets populate that arp cache
508 host2.run("ping -c1 10.10.1.3")
509 ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56")
510 # tgen.mininet_cli()
511
d6f34434 512
ef944129
SW
513def show_dvni_route(pe, vni, prefix, vrf):
514 output = pe.vtysh_cmd("show ip route vrf {} {}".format(vrf, prefix))
515
516 if str(vni) not in output:
517 return output
518
519 output = pe.run("ip route show vrf {} {}".format(vrf, prefix))
520
521 if str(vni) not in output:
522 return output
523
524 return None
525
d6f34434 526
ef944129
SW
527def test_dvni():
528 "test Downstream VNI works as expected importing into PE1"
529
530 tgen = get_topogen()
531 # Don't run this test if we have any failure.
532 if tgen.routers_have_failure():
533 pytest.skip(tgen.errors)
534
535 pe1 = tgen.gears["PE1"]
536
537 prefix = "4.4.4.1/32"
538 test_func = partial(show_dvni_route, pe1, 300, prefix, "vrf-red")
539 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
540 assertmsg = '"{}" DVNI route {} not found'.format(pe1.name, prefix)
541 assert result is None, assertmsg
d6f34434 542 # tgen.mininet_cli()
ef944129 543
1ca713b3
SR
544
545def test_memory_leak():
546 "Run the memory leak test and report results."
547 tgen = get_topogen()
548 if not tgen.is_memleak_enabled():
549 pytest.skip("Memory leak test/report is disabled")
550
551 tgen.report_memory_leaks()
552
553
554if __name__ == "__main__":
555 args = ["-s"] + sys.argv[1:]
556 sys.exit(pytest.main(args))