]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py
tests: cleanup: rerun changed files through black
[mirror_frr.git] / tests / topotests / bgp_evpn_vxlan_topo1 / test_bgp_evpn_vxlan.py
CommitLineData
6f77a974
PR
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"""
25test_bgp_evpn_vxlan.py: Test VXLAN EVPN MAC a route signalling over BGP.
26"""
27
28import os
29import sys
30import json
31from functools import partial
578c52e5 32from time import sleep
6f77a974
PR
33import pytest
34
35# Save the Current Working Directory to find configuration files.
36CWD = os.path.dirname(os.path.realpath(__file__))
37sys.path.append(os.path.join(CWD, "../"))
38
39# pylint: disable=C0413
40# Import topogen and topotest helpers
41from lib import topotest
42from lib.topogen import Topogen, TopoRouter, get_topogen
43from lib.topolog import logger
44
45# Required to instantiate the topology builder class.
6f77a974 46
3dedee4f 47pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd]
6f77a974 48
5980ad0a 49
e82b531d
CH
50def 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"])
6f77a974
PR
83
84
85def setup_module(mod):
86 "Sets up the pytest environment"
87 # This function initiates the topology build with Topogen...
e82b531d 88 tgen = Topogen(build_topo, mod.__name__)
6f77a974
PR
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")
578c52e5 98 pe1.run("ip addr add 10.10.1.1/24 dev br101")
6f77a974
PR
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")
578c52e5 108 pe2.run("ip addr add 10.10.1.3/24 dev br101")
6f77a974
PR
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
e5f0ed14 122 for rname, router in router_list.items():
6f77a974
PR
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
137def 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
2d706c4e
PR
145def 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
bc51ce68
CH
154def 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
6f77a974
PR
165def 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
2d706c4e 177 test_func = partial(show_vni_json_elide_ifindex, pe1, 101, expected)
bc51ce68 178 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
6f77a974 179 assertmsg = '"{}" JSON output mismatches'.format(pe1.name)
bc51ce68 180
a53c08bc
CH
181 test_func = partial(
182 check_vni_macs_present,
183 tgen,
184 pe1,
185 101,
186 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
187 )
bc51ce68
CH
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)
6f77a974
PR
192
193
194def 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
2d706c4e 206 test_func = partial(show_vni_json_elide_ifindex, pe2, 101, expected)
bc51ce68 207 _, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
6f77a974
PR
208 assertmsg = '"{}" JSON output mismatches'.format(pe2.name)
209 assert result is None, assertmsg
bc51ce68 210
a53c08bc
CH
211 test_func = partial(
212 check_vni_macs_present,
213 tgen,
214 pe2,
215 101,
216 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
217 )
bc51ce68
CH
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)
6f77a974
PR
222
223
224def 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()
7dbe42ce
PR
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
6f77a974
PR
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)
578c52e5 238 assert mac_output_json[mac]["type"] == "local", assertmsg
6f77a974
PR
239
240
241def 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
265def 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
278def 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
291def test_local_remote_mac_pe1():
a53c08bc 292 "Test MAC transfer PE1 local and PE2 remote"
6f77a974
PR
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
304def test_local_remote_mac_pe2():
a53c08bc 305 "Test MAC transfer PE2 local and PE1 remote"
6f77a974
PR
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
578c52e5
PR
319def 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()
7dbe42ce
PR
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
578c52e5
PR
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
9fa6ec14 342 assertmsg = (
343 "learned address mismatch with configured address host: {} learned: {}".format(
344 ip_addr, learned_ip
345 )
578c52e5
PR
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
391def test_ip_pe1_learn():
392 "run the IP learn test for PE1"
393
394 tgen = get_topogen()
b747851a
MS
395 # Don't run this test if we have any failure.
396 if tgen.routers_have_failure():
397 pytest.skip(tgen.errors)
398
578c52e5
PR
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
410def test_ip_pe2_learn():
411 "run the IP learn test for PE2"
412
413 tgen = get_topogen()
b747851a
MS
414 # Don't run this test if we have any failure.
415 if tgen.routers_have_failure():
416 pytest.skip(tgen.errors)
417
578c52e5
PR
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
6f77a974
PR
429def 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
438if __name__ == "__main__":
439 args = ["-s"] + sys.argv[1:]
440 sys.exit(pytest.main(args))