]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py
zebra: fix JSON fields for show evpn vni detail
[mirror_frr.git] / tests / topotests / bgp_evpn_vxlan_topo1 / test_bgp_evpn_vxlan.py
1 #!/usr/bin/env python
2
3 #
4 # test_bgp_evpn_vxlan.py
5 # Part of NetDEF Topology Tests
6 #
7 # Copyright (c) 2020 by Volta Networks
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 """
25 test_bgp_evpn_vxlan.py: Test VXLAN EVPN MAC a route signalling over BGP.
26 """
27
28 import os
29 import sys
30 import json
31 from functools import partial
32 from time import sleep
33 import pytest
34
35 # Save the Current Working Directory to find configuration files.
36 CWD = os.path.dirname(os.path.realpath(__file__))
37 sys.path.append(os.path.join(CWD, "../"))
38
39 # pylint: disable=C0413
40 # Import topogen and topotest helpers
41 from lib import topotest
42 from lib.topogen import Topogen, TopoRouter, get_topogen
43 from lib.topolog import logger
44
45 # Required to instantiate the topology builder class.
46
47 pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd]
48
49
50 def build_topo(tgen):
51 "Build function"
52
53 # This function only purpose is to define allocation and relationship
54 # between routers, switches and hosts.
55 #
56 #
57 # Create routers
58 tgen.add_router("P1")
59 tgen.add_router("PE1")
60 tgen.add_router("PE2")
61 tgen.add_router("host1")
62 tgen.add_router("host2")
63
64 # Host1-PE1
65 switch = tgen.add_switch("s1")
66 switch.add_link(tgen.gears["host1"])
67 switch.add_link(tgen.gears["PE1"])
68
69 # PE1-P1
70 switch = tgen.add_switch("s2")
71 switch.add_link(tgen.gears["PE1"])
72 switch.add_link(tgen.gears["P1"])
73
74 # P1-PE2
75 switch = tgen.add_switch("s3")
76 switch.add_link(tgen.gears["P1"])
77 switch.add_link(tgen.gears["PE2"])
78
79 # PE2-host2
80 switch = tgen.add_switch("s4")
81 switch.add_link(tgen.gears["PE2"])
82 switch.add_link(tgen.gears["host2"])
83
84
85 def setup_module(mod):
86 "Sets up the pytest environment"
87 # This function initiates the topology build with Topogen...
88 tgen = Topogen(build_topo, mod.__name__)
89 # ... and here it calls Mininet initialization functions.
90 tgen.start_topology()
91
92 pe1 = tgen.gears["PE1"]
93 pe2 = tgen.gears["PE2"]
94 p1 = tgen.gears["P1"]
95
96 # set up PE bridges with the EVPN member interfaces facing the CE hosts
97 pe1.run("ip link add name br101 type bridge stp_state 0")
98 pe1.run("ip addr add 10.10.1.1/24 dev br101")
99 pe1.run("ip link set dev br101 up")
100 pe1.run(
101 "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.10.10.10 nolearning"
102 )
103 pe1.run("ip link set dev vxlan101 master br101")
104 pe1.run("ip link set up dev vxlan101")
105 pe1.run("ip link set dev PE1-eth0 master br101")
106
107 pe2.run("ip link add name br101 type bridge stp_state 0")
108 pe2.run("ip addr add 10.10.1.3/24 dev br101")
109 pe2.run("ip link set dev br101 up")
110 pe2.run(
111 "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.30.30.30 nolearning"
112 )
113 pe2.run("ip link set dev vxlan101 master br101")
114 pe2.run("ip link set up dev vxlan101")
115 pe2.run("ip link set dev PE2-eth1 master br101")
116 p1.run("sysctl -w net.ipv4.ip_forward=1")
117
118 # This is a sample of configuration loading.
119 router_list = tgen.routers()
120
121 # For all registered routers, load the zebra configuration file
122 for rname, router in router_list.items():
123 router.load_config(
124 TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
125 )
126 router.load_config(
127 TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname))
128 )
129 router.load_config(
130 TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
131 )
132
133 # After loading the configurations, this function loads configured daemons.
134 tgen.start_router()
135
136
137 def teardown_module(mod):
138 "Teardown the pytest environment"
139 tgen = get_topogen()
140
141 # This function tears down the whole topology.
142 tgen.stop_topology()
143
144
145 def show_vni_json_elide_ifindex(pe, vni, expected):
146 output_json = pe.vtysh_cmd("show evpn vni {} json".format(vni), isjson=True)
147 if "ifindex" in output_json:
148 output_json.pop("ifindex")
149
150 return topotest.json_cmp(output_json, expected)
151
152
153 def check_vni_macs_present(tgen, router, vni, maclist):
154 result = router.vtysh_cmd("show evpn mac vni {} json".format(vni), isjson=True)
155 for rname, ifname in maclist:
156 m = tgen.net.macs[(rname, ifname)]
157 if m not in result["macs"]:
158 return "MAC ({}) for interface {} on {} missing on {} from {}".format(
159 m, ifname, rname, router.name, json.dumps(result, indent=4)
160 )
161 return None
162
163
164 def test_pe1_converge_evpn():
165 "Wait for protocol convergence"
166
167 tgen = get_topogen()
168 # Don't run this test if we have any failure.
169 if tgen.routers_have_failure():
170 pytest.skip(tgen.errors)
171
172 pe1 = tgen.gears["PE1"]
173 json_file = "{}/{}/evpn.vni.json".format(CWD, pe1.name)
174 expected = json.loads(open(json_file).read())
175
176 test_func = partial(show_vni_json_elide_ifindex, pe1, 101, expected)
177 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
178 assertmsg = '"{}" JSON output mismatches'.format(pe1.name)
179
180 test_func = partial(
181 check_vni_macs_present,
182 tgen,
183 pe1,
184 101,
185 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
186 )
187 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
188 if result:
189 logger.warning("%s", result)
190 assert None, '"{}" missing expected MACs'.format(pe1.name)
191
192
193 def test_pe2_converge_evpn():
194 "Wait for protocol convergence"
195
196 tgen = get_topogen()
197 # Don't run this test if we have any failure.
198 if tgen.routers_have_failure():
199 pytest.skip(tgen.errors)
200
201 pe2 = tgen.gears["PE2"]
202 json_file = "{}/{}/evpn.vni.json".format(CWD, pe2.name)
203 expected = json.loads(open(json_file).read())
204
205 test_func = partial(show_vni_json_elide_ifindex, pe2, 101, expected)
206 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
207 assertmsg = '"{}" JSON output mismatches'.format(pe2.name)
208 assert result is None, assertmsg
209
210 test_func = partial(
211 check_vni_macs_present,
212 tgen,
213 pe2,
214 101,
215 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
216 )
217 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
218 if result:
219 logger.warning("%s", result)
220 assert None, '"{}" missing expected MACs'.format(pe2.name)
221
222
223 def mac_learn_test(host, local):
224 "check the host MAC gets learned by the VNI"
225
226 host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
227 int_lines = host_output.splitlines()
228 for line in int_lines:
229 line_items = line.split(": ")
230 if "HWaddr" in line_items[0]:
231 mac = line_items[1]
232 break
233
234 mac_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
235 mac_output_json = json.loads(mac_output)
236 assertmsg = "Local MAC output does not match interface mac {}".format(mac)
237 assert mac_output_json[mac]["type"] == "local", assertmsg
238
239
240 def mac_test_local_remote(local, remote):
241 "test MAC transfer between local and remote"
242
243 local_output = local.vtysh_cmd("show evpn mac vni all json")
244 remote_output = remote.vtysh_cmd("show evpn mac vni all json")
245 local_output_vni = local.vtysh_cmd("show evpn vni detail json")
246 local_output_json = json.loads(local_output)
247 remote_output_json = json.loads(remote_output)
248 local_output_vni_json = json.loads(local_output_vni)
249
250 for vni in local_output_json:
251 mac_list = local_output_json[vni]["macs"]
252 for mac in mac_list:
253 if mac_list[mac]["type"] == "local" and mac_list[mac]["intf"] != "br101":
254 assertmsg = "JSON output mismatches local: {} remote: {}".format(
255 local_output_vni_json[0]["vtepIp"],
256 remote_output_json[vni]["macs"][mac]["remoteVtep"],
257 )
258 assert (
259 remote_output_json[vni]["macs"][mac]["remoteVtep"]
260 == local_output_vni_json[0]["vtepIp"]
261 ), assertmsg
262
263
264 def test_learning_pe1():
265 "test MAC learning on PE1"
266
267 tgen = get_topogen()
268 # Don't run this test if we have any failure.
269 if tgen.routers_have_failure():
270 pytest.skip(tgen.errors)
271
272 host1 = tgen.gears["host1"]
273 pe1 = tgen.gears["PE1"]
274 mac_learn_test(host1, pe1)
275
276
277 def test_learning_pe2():
278 "test MAC learning on PE2"
279
280 tgen = get_topogen()
281 # Don't run this test if we have any failure.
282 if tgen.routers_have_failure():
283 pytest.skip(tgen.errors)
284
285 host2 = tgen.gears["host2"]
286 pe2 = tgen.gears["PE2"]
287 mac_learn_test(host2, pe2)
288
289
290 def test_local_remote_mac_pe1():
291 "Test MAC transfer PE1 local and PE2 remote"
292
293 tgen = get_topogen()
294 # Don't run this test if we have any failure.
295 if tgen.routers_have_failure():
296 pytest.skip(tgen.errors)
297
298 pe1 = tgen.gears["PE1"]
299 pe2 = tgen.gears["PE2"]
300 mac_test_local_remote(pe1, pe2)
301
302
303 def test_local_remote_mac_pe2():
304 "Test MAC transfer PE2 local and PE1 remote"
305
306 tgen = get_topogen()
307 # Don't run this test if we have any failure.
308 if tgen.routers_have_failure():
309 pytest.skip(tgen.errors)
310
311 pe1 = tgen.gears["PE1"]
312 pe2 = tgen.gears["PE2"]
313 mac_test_local_remote(pe2, pe1)
314
315 # Memory leak test template
316
317
318 def ip_learn_test(tgen, host, local, remote, ip_addr):
319 "check the host IP gets learned by the VNI"
320 host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
321 int_lines = host_output.splitlines()
322 for line in int_lines:
323 line_items = line.split(": ")
324 if "HWaddr" in line_items[0]:
325 mac = line_items[1]
326 break
327 print(host_output)
328
329 # check we have a local association between the MAC and IP
330 local_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
331 print(local_output)
332 local_output_json = json.loads(local_output)
333 mac_type = local_output_json[mac]["type"]
334 assertmsg = "Failed to learn local IP address on host {}".format(host.name)
335 assert local_output_json[mac]["neighbors"] != "none", assertmsg
336 learned_ip = local_output_json[mac]["neighbors"]["active"][0]
337
338 assertmsg = "local learned mac wrong type: {} ".format(mac_type)
339 assert mac_type == "local", assertmsg
340
341 assertmsg = (
342 "learned address mismatch with configured address host: {} learned: {}".format(
343 ip_addr, learned_ip
344 )
345 )
346 assert ip_addr == learned_ip, assertmsg
347
348 # now lets check the remote
349 count = 0
350 converged = False
351 while count < 30:
352 remote_output = remote.vtysh_cmd(
353 "show evpn mac vni 101 mac {} json".format(mac)
354 )
355 print(remote_output)
356 remote_output_json = json.loads(remote_output)
357 type = remote_output_json[mac]["type"]
358 if not remote_output_json[mac]["neighbors"] == "none":
359 # due to a kernel quirk, learned IPs can be inactive
360 if (
361 remote_output_json[mac]["neighbors"]["active"]
362 or remote_output_json[mac]["neighbors"]["inactive"]
363 ):
364 converged = True
365 break
366 count += 1
367 sleep(1)
368
369 print("tries: {}".format(count))
370 assertmsg = "{} remote learned mac no address: {} ".format(host.name, mac)
371 # some debug for this failure
372 if not converged == True:
373 log_output = remote.run("cat zebra.log")
374 print(log_output)
375
376 assert converged == True, assertmsg
377 if remote_output_json[mac]["neighbors"]["active"]:
378 learned_ip = remote_output_json[mac]["neighbors"]["active"][0]
379 else:
380 learned_ip = remote_output_json[mac]["neighbors"]["inactive"][0]
381 assertmsg = "remote learned mac wrong type: {} ".format(type)
382 assert type == "remote", assertmsg
383
384 assertmsg = "remote learned address mismatch with configured address host: {} learned: {}".format(
385 ip_addr, learned_ip
386 )
387 assert ip_addr == learned_ip, assertmsg
388
389
390 def test_ip_pe1_learn():
391 "run the IP learn test for PE1"
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 host1 = tgen.gears["host1"]
399 pe1 = tgen.gears["PE1"]
400 pe2 = tgen.gears["PE2"]
401 pe2.vtysh_cmd("debug zebra vxlan")
402 pe2.vtysh_cmd("debug zebra kernel")
403 # lets populate that arp cache
404 host1.run("ping -c1 10.10.1.1")
405 ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55")
406 # tgen.mininet_cli()
407
408
409 def test_ip_pe2_learn():
410 "run the IP learn test for PE2"
411
412 tgen = get_topogen()
413 # Don't run this test if we have any failure.
414 if tgen.routers_have_failure():
415 pytest.skip(tgen.errors)
416
417 host2 = tgen.gears["host2"]
418 pe1 = tgen.gears["PE1"]
419 pe2 = tgen.gears["PE2"]
420 pe1.vtysh_cmd("debug zebra vxlan")
421 pe1.vtysh_cmd("debug zebra kernel")
422 # lets populate that arp cache
423 host2.run("ping -c1 10.10.1.3")
424 ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56")
425 # tgen.mininet_cli()
426
427
428 def test_memory_leak():
429 "Run the memory leak test and report results."
430 tgen = get_topogen()
431 if not tgen.is_memleak_enabled():
432 pytest.skip("Memory leak test/report is disabled")
433
434 tgen.report_memory_leaks()
435
436
437 if __name__ == "__main__":
438 args = ["-s"] + sys.argv[1:]
439 sys.exit(pytest.main(args))