]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py
Merge pull request #13060 from opensourcerouting/feature/allow_peering_with_127.0.0.1
[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
86def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf):
87 pe = tgen.gears[pe_name]
88
89 # configure vlan aware bridge
90 pe.run("ip link add name bridge type bridge stp_state 0")
91 pe.run("ip link set dev bridge type bridge vlan_filtering 1")
92 pe.run("bridge vlan add vid 1 dev bridge self")
93 pe.run("ip link set dev bridge up")
94
95 # setup svi
96 pe.run("ip link add link bridge name vlan1 type vlan id 1 protocol 802.1q")
97 pe.run("ip link set dev vlan1 up")
98 pe.run("ip addr add {0} dev vlan1".format(svi_ip))
99 pe.run("/sbin/sysctl net.ipv4.conf.vlan1.arp_accept=1")
100
101 # setup single vxlan device
102 pe.run(
103 "ip link add dev vxlan0 type vxlan dstport 4789 local {0} nolearning external".format(tunnel_local_ip)
104 )
105 pe.run("ip link set dev vxlan0 master bridge")
106 pe.run("bridge link set dev vxlan0 vlan_tunnel on")
107 pe.run("bridge link set dev vxlan0 neigh_suppress on")
108 pe.run("bridge link set dev vxlan0 learning off")
109 pe.run("bridge vlan add dev vxlan0 vid 1")
110 pe.run("bridge vlan add dev vxlan0 vid 1 tunnel_info id 101")
111 pe.run("ip link set up dev vxlan0")
112
113 # setup PE interface
114 pe.run("ip link set dev {0}-{1} master bridge".format(pe_name, intf))
115 pe.run("bridge vlan del vid 1 dev {0}-{1}".format(pe_name, intf))
116 pe.run("bridge vlan del vid 1 untagged pvid dev {0}-{1}".format(pe_name, intf))
117 pe.run("bridge vlan add vid 1 dev {0}-{1}".format(pe_name, intf))
118 pe.run("bridge vlan add vid 1 pvid untagged dev {0}-{1}".format(pe_name, intf))
119
ef944129
SW
120 # l3vni 100
121 pe.run("ip link add vrf-red type vrf table 1400")
122 pe.run("ip link add link bridge name vlan100 type vlan id 100 protocol 802.1q")
123 pe.run("ip link set dev vlan100 master vrf-blue")
124 pe.run("ip link set dev vlan100 up")
125 pe.run("bridge vlan add vid 100 dev bridge self")
126 pe.run("bridge vlan add dev vxlan0 vid 100")
127 pe.run("bridge vlan add dev vxlan0 vid 100 tunnel_info id 100")
128
129 # add a vrf for testing DVNI
130 if pe_name == "PE2":
131 pe.run("ip link add vrf-blue type vrf table 2400")
132 pe.run("ip link add link bridge name vlan300 type vlan id 300 protocol 802.1q")
133 pe.run("ip link set dev vlan300 master vrf-blue")
134 pe.run("ip link set dev vlan300 up")
135 pe.run("bridge vlan add vid 300 dev bridge self")
136 pe.run("bridge vlan add dev vxlan0 vid 300")
137 pe.run("bridge vlan add dev vxlan0 vid 300 tunnel_info id 300")
1ca713b3
SR
138
139def setup_p_router(tgen, p_name):
140 p1 = tgen.gears[p_name]
141 p1.run("sysctl -w net.ipv4.ip_forward=1")
1ca713b3
SR
142
143def setup_module(mod):
144 "Sets up the pytest environment"
145
568d4324
SW
146 result = required_linux_kernel_version("5.7")
147 if result is not True:
148 pytest.skip("Kernel requirements are not met, kernel version should be >= 5.7")
149
1ca713b3
SR
150 # This function initiates the topology build with Topogen...
151 tgen = Topogen(build_topo, mod.__name__)
152
153 # ... and here it calls Mininet initialization functions.
154 tgen.start_topology()
155
156 setup_pe_router(tgen, "PE1", "10.10.10.10", "10.10.1.1/24", "eth0")
157 setup_pe_router(tgen, "PE2", "10.30.30.30", "10.10.1.3/24", "eth1")
158 setup_p_router(tgen, "P1")
159
160 # This is a sample of configuration loading.
161 router_list = tgen.routers()
162
163 # For all registred routers, load the zebra configuration file
164 for rname, router in router_list.items():
165 router.load_config(
166 TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
167 )
168 router.load_config(
169 TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname))
170 )
171 router.load_config(
172 TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
173 )
174
175 # After loading the configurations, this function loads configured daemons.
176 tgen.start_router()
177
178
179def teardown_module(mod):
180 "Teardown the pytest environment"
181 tgen = get_topogen()
182
ef944129 183 #tgen.mininet_cli()
1ca713b3
SR
184 # This function tears down the whole topology.
185 tgen.stop_topology()
186
187
188def show_vni_json_elide_ifindex(pe, vni, expected):
189 output_json = pe.vtysh_cmd("show evpn vni {} json".format(vni), isjson=True)
190
191 if "ifindex" in output_json:
192 output_json.pop("ifindex")
193
194 return topotest.json_cmp(output_json, expected)
195
196
197def check_vni_macs_present(tgen, router, vni, maclist):
198 result = router.vtysh_cmd("show evpn mac vni {} json".format(vni), isjson=True)
199 for rname, ifname in maclist:
200 m = tgen.net.macs[(rname, ifname)]
201 if m not in result["macs"]:
202 return "MAC ({}) for interface {} on {} missing on {} from {}".format(
203 m, ifname, rname, router.name, json.dumps(result, indent=4)
204 )
205 return None
206
6246b57e 207def check_flood_entry_present(pe, vni, vtep):
51c33a57
SW
208 if not topotest.iproute2_is_fdb_get_capable():
209 return None
210
6246b57e
SW
211 output = pe.run("bridge fdb get 00:00:00:00:00:00 dev vxlan0 vni {} self".format(vni))
212
213 if str(vtep) not in output:
214 return output
215
216 return None
1ca713b3
SR
217
218def test_pe1_converge_evpn():
219 "Wait for protocol convergence"
220
221 tgen = get_topogen()
222 # Don't run this test if we have any failure.
223 if tgen.routers_have_failure():
224 pytest.skip(tgen.errors)
225
226 pe1 = tgen.gears["PE1"]
227 json_file = "{}/{}/evpn.vni.json".format(CWD, pe1.name)
228 expected = json.loads(open(json_file).read())
229
230 test_func = partial(show_vni_json_elide_ifindex, pe1, 101, expected)
231 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
232 assertmsg = '"{}" JSON output mismatches'.format(pe1.name)
233
234 test_func = partial(
235 check_vni_macs_present,
236 tgen,
237 pe1,
238 101,
239 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
240 )
241 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
242 if result:
243 logger.warning("%s", result)
244 assert None, '"{}" missing expected MACs'.format(pe1.name)
245
6246b57e
SW
246 vtep = "10.30.30.30"
247 test_func = partial(check_flood_entry_present, pe1, 101, vtep)
248 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
249 assertmsg = '"{}" Flood FDB Entry for VTEP {} not found'.format(pe1.name, vtep)
250 assert result is None, assertmsg
1ca713b3
SR
251
252def test_pe2_converge_evpn():
253 "Wait for protocol convergence"
254
255 tgen = get_topogen()
ef944129 256#Don't run this test if we have any failure.
1ca713b3
SR
257 if tgen.routers_have_failure():
258 pytest.skip(tgen.errors)
259
260 pe2 = tgen.gears["PE2"]
261 json_file = "{}/{}/evpn.vni.json".format(CWD, pe2.name)
262 expected = json.loads(open(json_file).read())
263
264 test_func = partial(show_vni_json_elide_ifindex, pe2, 101, expected)
265 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
266 assertmsg = '"{}" JSON output mismatches'.format(pe2.name)
267 assert result is None, assertmsg
268
269 test_func = partial(
270 check_vni_macs_present,
271 tgen,
272 pe2,
273 101,
274 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
275 )
276 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
277 if result:
278 logger.warning("%s", result)
279 assert None, '"{}" missing expected MACs'.format(pe2.name)
280
6246b57e
SW
281 vtep = "10.10.10.10"
282 test_func = partial(check_flood_entry_present, pe2, 101, vtep)
283 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
284 assertmsg = '"{}" Flood FDB Entry for VTEP {} not found'.format(pe2.name, vtep)
285 assert result is None, assertmsg
1ca713b3
SR
286
287def mac_learn_test(host, local):
288 "check the host MAC gets learned by the VNI"
289
290 host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
291 int_lines = host_output.splitlines()
292 for line in int_lines:
293 line_items = line.split(": ")
294 if "HWaddr" in line_items[0]:
295 mac = line_items[1]
296 break
297
298 mac_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
299 mac_output_json = json.loads(mac_output)
300 assertmsg = "Local MAC output does not match interface mac {}".format(mac)
301 assert mac_output_json[mac]["type"] == "local", assertmsg
302
303
304def mac_test_local_remote(local, remote):
305 "test MAC transfer between local and remote"
306
307 local_output = local.vtysh_cmd("show evpn mac vni all json")
308 remote_output = remote.vtysh_cmd("show evpn mac vni all json")
309 local_output_vni = local.vtysh_cmd("show evpn vni detail json")
310 local_output_json = json.loads(local_output)
311 remote_output_json = json.loads(remote_output)
312 local_output_vni_json = json.loads(local_output_vni)
313
314 for vni in local_output_json:
ef944129
SW
315 if vni not in remote_output_json:
316 continue
317
1ca713b3
SR
318 mac_list = local_output_json[vni]["macs"]
319 for mac in mac_list:
320 if mac_list[mac]["type"] == "local" and mac_list[mac]["intf"] != "br101":
321 assertmsg = "JSON output mismatches local: {} remote: {}".format(
322 local_output_vni_json[0]["vtepIp"],
323 remote_output_json[vni]["macs"][mac]["remoteVtep"],
324 )
325 assert (
326 remote_output_json[vni]["macs"][mac]["remoteVtep"]
327 == local_output_vni_json[0]["vtepIp"]
328 ), assertmsg
329
330
331def test_learning_pe1():
332 "test MAC learning on PE1"
333
334 tgen = get_topogen()
335 # Don't run this test if we have any failure.
336 if tgen.routers_have_failure():
337 pytest.skip(tgen.errors)
338
339 host1 = tgen.gears["host1"]
340 pe1 = tgen.gears["PE1"]
341 mac_learn_test(host1, pe1)
342
343
344def test_learning_pe2():
345 "test MAC learning on PE2"
346
347 tgen = get_topogen()
348 # Don't run this test if we have any failure.
349 if tgen.routers_have_failure():
350 pytest.skip(tgen.errors)
351
352 host2 = tgen.gears["host2"]
353 pe2 = tgen.gears["PE2"]
354 mac_learn_test(host2, pe2)
355
356
357def test_local_remote_mac_pe1():
358 "Test MAC transfer PE1 local and PE2 remote"
359
360 tgen = get_topogen()
361 # Don't run this test if we have any failure.
362 if tgen.routers_have_failure():
363 pytest.skip(tgen.errors)
364
365 pe1 = tgen.gears["PE1"]
366 pe2 = tgen.gears["PE2"]
367 mac_test_local_remote(pe1, pe2)
368
369
370def test_local_remote_mac_pe2():
371 "Test MAC transfer PE2 local and PE1 remote"
372
373 tgen = get_topogen()
374 # Don't run this test if we have any failure.
375 if tgen.routers_have_failure():
376 pytest.skip(tgen.errors)
377
378 pe1 = tgen.gears["PE1"]
379 pe2 = tgen.gears["PE2"]
380 mac_test_local_remote(pe2, pe1)
381
1ca713b3
SR
382
383def ip_learn_test(tgen, host, local, remote, ip_addr):
384 "check the host IP gets learned by the VNI"
385 host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
386 int_lines = host_output.splitlines()
387 for line in int_lines:
388 line_items = line.split(": ")
389 if "HWaddr" in line_items[0]:
390 mac = line_items[1]
391 break
ef944129 392 #print(host_output)
1ca713b3
SR
393
394 # check we have a local association between the MAC and IP
395 local_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
ef944129 396 #print(local_output)
1ca713b3
SR
397 local_output_json = json.loads(local_output)
398 mac_type = local_output_json[mac]["type"]
399 assertmsg = "Failed to learn local IP address on host {}".format(host.name)
400 assert local_output_json[mac]["neighbors"] != "none", assertmsg
401 learned_ip = local_output_json[mac]["neighbors"]["active"][0]
402
403 assertmsg = "local learned mac wrong type: {} ".format(mac_type)
404 assert mac_type == "local", assertmsg
405
406 assertmsg = (
407 "learned address mismatch with configured address host: {} learned: {}".format(
408 ip_addr, learned_ip
409 )
410 )
411 assert ip_addr == learned_ip, assertmsg
412
413 # now lets check the remote
414 count = 0
415 converged = False
416 while count < 30:
417 remote_output = remote.vtysh_cmd(
418 "show evpn mac vni 101 mac {} json".format(mac)
419 )
ef944129 420 #print(remote_output)
1ca713b3
SR
421 remote_output_json = json.loads(remote_output)
422 type = remote_output_json[mac]["type"]
423 if not remote_output_json[mac]["neighbors"] == "none":
424 # due to a kernel quirk, learned IPs can be inactive
425 if (
426 remote_output_json[mac]["neighbors"]["active"]
427 or remote_output_json[mac]["neighbors"]["inactive"]
428 ):
429 converged = True
430 break
431 count += 1
432 sleep(1)
433
ef944129 434 #print("tries: {}".format(count))
1ca713b3
SR
435 assertmsg = "{} remote learned mac no address: {} ".format(host.name, mac)
436 # some debug for this failure
437 if not converged == True:
438 log_output = remote.run("cat zebra.log")
ef944129 439 #print(log_output)
1ca713b3
SR
440
441 assert converged == True, assertmsg
442 if remote_output_json[mac]["neighbors"]["active"]:
443 learned_ip = remote_output_json[mac]["neighbors"]["active"][0]
444 else:
445 learned_ip = remote_output_json[mac]["neighbors"]["inactive"][0]
446 assertmsg = "remote learned mac wrong type: {} ".format(type)
447 assert type == "remote", assertmsg
448
449 assertmsg = "remote learned address mismatch with configured address host: {} learned: {}".format(
450 ip_addr, learned_ip
451 )
452 assert ip_addr == learned_ip, assertmsg
453
454
455def test_ip_pe1_learn():
456 "run the IP learn test for PE1"
457
458 tgen = get_topogen()
459 # Don't run this test if we have any failure.
460 if tgen.routers_have_failure():
461 pytest.skip(tgen.errors)
462
463 host1 = tgen.gears["host1"]
464 pe1 = tgen.gears["PE1"]
465 pe2 = tgen.gears["PE2"]
2007e2db
DS
466 #pe2.vtysh_cmd("debug zebra vxlan")
467 #pe2.vtysh_cmd("debug zebra kernel")
1ca713b3
SR
468 # lets populate that arp cache
469 host1.run("ping -c1 10.10.1.1")
470 ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55")
471 # tgen.mininet_cli()
472
473
474def test_ip_pe2_learn():
475 "run the IP learn test for PE2"
476
477 tgen = get_topogen()
478 # Don't run this test if we have any failure.
479 if tgen.routers_have_failure():
480 pytest.skip(tgen.errors)
481
482 host2 = tgen.gears["host2"]
483 pe1 = tgen.gears["PE1"]
484 pe2 = tgen.gears["PE2"]
2007e2db
DS
485 #pe1.vtysh_cmd("debug zebra vxlan")
486 #pe1.vtysh_cmd("debug zebra kernel")
1ca713b3
SR
487 # lets populate that arp cache
488 host2.run("ping -c1 10.10.1.3")
489 ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56")
490 # tgen.mininet_cli()
491
ef944129
SW
492def show_dvni_route(pe, vni, prefix, vrf):
493 output = pe.vtysh_cmd("show ip route vrf {} {}".format(vrf, prefix))
494
495 if str(vni) not in output:
496 return output
497
498 output = pe.run("ip route show vrf {} {}".format(vrf, prefix))
499
500 if str(vni) not in output:
501 return output
502
503 return None
504
505def test_dvni():
506 "test Downstream VNI works as expected importing into PE1"
507
508 tgen = get_topogen()
509 # Don't run this test if we have any failure.
510 if tgen.routers_have_failure():
511 pytest.skip(tgen.errors)
512
513 pe1 = tgen.gears["PE1"]
514
515 prefix = "4.4.4.1/32"
516 test_func = partial(show_dvni_route, pe1, 300, prefix, "vrf-red")
517 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
518 assertmsg = '"{}" DVNI route {} not found'.format(pe1.name, prefix)
519 assert result is None, assertmsg
520 #tgen.mininet_cli()
521
1ca713b3
SR
522
523def test_memory_leak():
524 "Run the memory leak test and report results."
525 tgen = get_topogen()
526 if not tgen.is_memleak_enabled():
527 pytest.skip("Memory leak test/report is disabled")
528
529 tgen.report_memory_leaks()
530
531
532if __name__ == "__main__":
533 args = ["-s"] + sys.argv[1:]
534 sys.exit(pytest.main(args))