]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py
Merge pull request #9410 from idryzhov/static-show-run-nb
[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 registred 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
148 if "ifindex" in output_json:
149 output_json.pop("ifindex")
150
151 return topotest.json_cmp(output_json, expected)
152
153
154 def check_vni_macs_present(tgen, router, vni, maclist):
155 result = router.vtysh_cmd("show evpn mac vni {} json".format(vni), isjson=True)
156 for rname, ifname in maclist:
157 m = tgen.net.macs[(rname, ifname)]
158 if m not in result["macs"]:
159 return "MAC ({}) for interface {} on {} missing on {} from {}".format(
160 m, ifname, rname, router.name, json.dumps(result, indent=4)
161 )
162 return None
163
164
165 def test_pe1_converge_evpn():
166 "Wait for protocol convergence"
167
168 tgen = get_topogen()
169 # Don't run this test if we have any failure.
170 if tgen.routers_have_failure():
171 pytest.skip(tgen.errors)
172
173 pe1 = tgen.gears["PE1"]
174 json_file = "{}/{}/evpn.vni.json".format(CWD, pe1.name)
175 expected = json.loads(open(json_file).read())
176
177 test_func = partial(show_vni_json_elide_ifindex, pe1, 101, expected)
178 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
179 assertmsg = '"{}" JSON output mismatches'.format(pe1.name)
180
181 test_func = partial(
182 check_vni_macs_present,
183 tgen,
184 pe1,
185 101,
186 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
187 )
188 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
189 if result:
190 logger.warning("%s", result)
191 assert None, '"{}" missing expected MACs'.format(pe1.name)
192
193
194 def test_pe2_converge_evpn():
195 "Wait for protocol convergence"
196
197 tgen = get_topogen()
198 # Don't run this test if we have any failure.
199 if tgen.routers_have_failure():
200 pytest.skip(tgen.errors)
201
202 pe2 = tgen.gears["PE2"]
203 json_file = "{}/{}/evpn.vni.json".format(CWD, pe2.name)
204 expected = json.loads(open(json_file).read())
205
206 test_func = partial(show_vni_json_elide_ifindex, pe2, 101, expected)
207 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
208 assertmsg = '"{}" JSON output mismatches'.format(pe2.name)
209 assert result is None, assertmsg
210
211 test_func = partial(
212 check_vni_macs_present,
213 tgen,
214 pe2,
215 101,
216 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
217 )
218 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
219 if result:
220 logger.warning("%s", result)
221 assert None, '"{}" missing expected MACs'.format(pe2.name)
222
223
224 def mac_learn_test(host, local):
225 "check the host MAC gets learned by the VNI"
226
227 host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
228 int_lines = host_output.splitlines()
229 for line in int_lines:
230 line_items = line.split(": ")
231 if "HWaddr" in line_items[0]:
232 mac = line_items[1]
233 break
234
235 mac_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
236 mac_output_json = json.loads(mac_output)
237 assertmsg = "Local MAC output does not match interface mac {}".format(mac)
238 assert mac_output_json[mac]["type"] == "local", assertmsg
239
240
241 def mac_test_local_remote(local, remote):
242 "test MAC transfer between local and remote"
243
244 local_output = local.vtysh_cmd("show evpn mac vni all json")
245 remote_output = remote.vtysh_cmd("show evpn mac vni all json")
246 local_output_vni = local.vtysh_cmd("show evpn vni detail json")
247 local_output_json = json.loads(local_output)
248 remote_output_json = json.loads(remote_output)
249 local_output_vni_json = json.loads(local_output_vni)
250
251 for vni in local_output_json:
252 mac_list = local_output_json[vni]["macs"]
253 for mac in mac_list:
254 if mac_list[mac]["type"] == "local" and mac_list[mac]["intf"] != "br101":
255 assertmsg = "JSON output mismatches local: {} remote: {}".format(
256 local_output_vni_json[0]["vtepIp"],
257 remote_output_json[vni]["macs"][mac]["remoteVtep"],
258 )
259 assert (
260 remote_output_json[vni]["macs"][mac]["remoteVtep"]
261 == local_output_vni_json[0]["vtepIp"]
262 ), assertmsg
263
264
265 def test_learning_pe1():
266 "test MAC learning on PE1"
267
268 tgen = get_topogen()
269 # Don't run this test if we have any failure.
270 if tgen.routers_have_failure():
271 pytest.skip(tgen.errors)
272
273 host1 = tgen.gears["host1"]
274 pe1 = tgen.gears["PE1"]
275 mac_learn_test(host1, pe1)
276
277
278 def test_learning_pe2():
279 "test MAC learning on PE2"
280
281 tgen = get_topogen()
282 # Don't run this test if we have any failure.
283 if tgen.routers_have_failure():
284 pytest.skip(tgen.errors)
285
286 host2 = tgen.gears["host2"]
287 pe2 = tgen.gears["PE2"]
288 mac_learn_test(host2, pe2)
289
290
291 def test_local_remote_mac_pe1():
292 "Test MAC transfer PE1 local and PE2 remote"
293
294 tgen = get_topogen()
295 # Don't run this test if we have any failure.
296 if tgen.routers_have_failure():
297 pytest.skip(tgen.errors)
298
299 pe1 = tgen.gears["PE1"]
300 pe2 = tgen.gears["PE2"]
301 mac_test_local_remote(pe1, pe2)
302
303
304 def test_local_remote_mac_pe2():
305 "Test MAC transfer PE2 local and PE1 remote"
306
307 tgen = get_topogen()
308 # Don't run this test if we have any failure.
309 if tgen.routers_have_failure():
310 pytest.skip(tgen.errors)
311
312 pe1 = tgen.gears["PE1"]
313 pe2 = tgen.gears["PE2"]
314 mac_test_local_remote(pe2, pe1)
315
316 # Memory leak test template
317
318
319 def ip_learn_test(tgen, host, local, remote, ip_addr):
320 "check the host IP gets learned by the VNI"
321 host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name))
322 int_lines = host_output.splitlines()
323 for line in int_lines:
324 line_items = line.split(": ")
325 if "HWaddr" in line_items[0]:
326 mac = line_items[1]
327 break
328 print(host_output)
329
330 # check we have a local association between the MAC and IP
331 local_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
332 print(local_output)
333 local_output_json = json.loads(local_output)
334 mac_type = local_output_json[mac]["type"]
335 assertmsg = "Failed to learn local IP address on host {}".format(host.name)
336 assert local_output_json[mac]["neighbors"] != "none", assertmsg
337 learned_ip = local_output_json[mac]["neighbors"]["active"][0]
338
339 assertmsg = "local learned mac wrong type: {} ".format(mac_type)
340 assert mac_type == "local", assertmsg
341
342 assertmsg = (
343 "learned address mismatch with configured address host: {} learned: {}".format(
344 ip_addr, learned_ip
345 )
346 )
347 assert ip_addr == learned_ip, assertmsg
348
349 # now lets check the remote
350 count = 0
351 converged = False
352 while count < 30:
353 remote_output = remote.vtysh_cmd(
354 "show evpn mac vni 101 mac {} json".format(mac)
355 )
356 print(remote_output)
357 remote_output_json = json.loads(remote_output)
358 type = remote_output_json[mac]["type"]
359 if not remote_output_json[mac]["neighbors"] == "none":
360 # due to a kernel quirk, learned IPs can be inactive
361 if (
362 remote_output_json[mac]["neighbors"]["active"]
363 or remote_output_json[mac]["neighbors"]["inactive"]
364 ):
365 converged = True
366 break
367 count += 1
368 sleep(1)
369
370 print("tries: {}".format(count))
371 assertmsg = "{} remote learned mac no address: {} ".format(host.name, mac)
372 # some debug for this failure
373 if not converged == True:
374 log_output = remote.run("cat zebra.log")
375 print(log_output)
376
377 assert converged == True, assertmsg
378 if remote_output_json[mac]["neighbors"]["active"]:
379 learned_ip = remote_output_json[mac]["neighbors"]["active"][0]
380 else:
381 learned_ip = remote_output_json[mac]["neighbors"]["inactive"][0]
382 assertmsg = "remote learned mac wrong type: {} ".format(type)
383 assert type == "remote", assertmsg
384
385 assertmsg = "remote learned address mismatch with configured address host: {} learned: {}".format(
386 ip_addr, learned_ip
387 )
388 assert ip_addr == learned_ip, assertmsg
389
390
391 def test_ip_pe1_learn():
392 "run the IP learn test for PE1"
393
394 tgen = get_topogen()
395 # Don't run this test if we have any failure.
396 if tgen.routers_have_failure():
397 pytest.skip(tgen.errors)
398
399 host1 = tgen.gears["host1"]
400 pe1 = tgen.gears["PE1"]
401 pe2 = tgen.gears["PE2"]
402 pe2.vtysh_cmd("debug zebra vxlan")
403 pe2.vtysh_cmd("debug zebra kernel")
404 # lets populate that arp cache
405 host1.run("ping -c1 10.10.1.1")
406 ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55")
407 # tgen.mininet_cli()
408
409
410 def test_ip_pe2_learn():
411 "run the IP learn test for PE2"
412
413 tgen = get_topogen()
414 # Don't run this test if we have any failure.
415 if tgen.routers_have_failure():
416 pytest.skip(tgen.errors)
417
418 host2 = tgen.gears["host2"]
419 pe1 = tgen.gears["PE1"]
420 pe2 = tgen.gears["PE2"]
421 pe1.vtysh_cmd("debug zebra vxlan")
422 pe1.vtysh_cmd("debug zebra kernel")
423 # lets populate that arp cache
424 host2.run("ping -c1 10.10.1.3")
425 ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56")
426 # tgen.mininet_cli()
427
428
429 def test_memory_leak():
430 "Run the memory leak test and report results."
431 tgen = get_topogen()
432 if not tgen.is_memleak_enabled():
433 pytest.skip("Memory leak test/report is disabled")
434
435 tgen.report_memory_leaks()
436
437
438 if __name__ == "__main__":
439 args = ["-s"] + sys.argv[1:]
440 sys.exit(pytest.main(args))