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