4 # test_bgp_evpn_vxlan.py
5 # Part of NetDEF Topology Tests
7 # Copyright (c) 2020 by Volta Networks
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
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
25 test_bgp_evpn_vxlan.py: Test VXLAN EVPN MAC a route signalling over BGP.
31 from functools
import partial
32 from time
import sleep
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
, "../"))
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
45 # Required to instantiate the topology builder class.
47 pytestmark
= [pytest
.mark
.bgpd
, pytest
.mark
.ospfd
]
53 # This function only purpose is to define allocation and relationship
54 # between routers, switches and hosts.
59 tgen
.add_router("PE1")
60 tgen
.add_router("PE2")
61 tgen
.add_router("host1")
62 tgen
.add_router("host2")
65 switch
= tgen
.add_switch("s1")
66 switch
.add_link(tgen
.gears
["host1"])
67 switch
.add_link(tgen
.gears
["PE1"])
70 switch
= tgen
.add_switch("s2")
71 switch
.add_link(tgen
.gears
["PE1"])
72 switch
.add_link(tgen
.gears
["P1"])
75 switch
= tgen
.add_switch("s3")
76 switch
.add_link(tgen
.gears
["P1"])
77 switch
.add_link(tgen
.gears
["PE2"])
80 switch
= tgen
.add_switch("s4")
81 switch
.add_link(tgen
.gears
["PE2"])
82 switch
.add_link(tgen
.gears
["host2"])
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.
92 pe1
= tgen
.gears
["PE1"]
93 pe2
= tgen
.gears
["PE2"]
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")
101 "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.10.10.10 nolearning"
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")
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")
111 "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.30.30.30 nolearning"
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")
118 # This is a sample of configuration loading.
119 router_list
= tgen
.routers()
121 # For all registred routers, load the zebra configuration file
122 for rname
, router
in router_list
.items():
124 TopoRouter
.RD_ZEBRA
, os
.path
.join(CWD
, "{}/zebra.conf".format(rname
))
127 TopoRouter
.RD_OSPF
, os
.path
.join(CWD
, "{}/ospfd.conf".format(rname
))
130 TopoRouter
.RD_BGP
, os
.path
.join(CWD
, "{}/bgpd.conf".format(rname
))
133 # After loading the configurations, this function loads configured daemons.
137 def teardown_module(mod
):
138 "Teardown the pytest environment"
141 # This function tears down the whole topology.
145 def show_vni_json_elide_ifindex(pe
, vni
, expected
):
146 output_json
= pe
.vtysh_cmd("show evpn vni {} json".format(vni
), isjson
=True)
148 if "ifindex" in output_json
:
149 output_json
.pop("ifindex")
151 return topotest
.json_cmp(output_json
, expected
)
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)
165 def test_pe1_converge_evpn():
166 "Wait for protocol convergence"
169 # Don't run this test if we have any failure.
170 if tgen
.routers_have_failure():
171 pytest
.skip(tgen
.errors
)
173 pe1
= tgen
.gears
["PE1"]
174 json_file
= "{}/{}/evpn.vni.json".format(CWD
, pe1
.name
)
175 expected
= json
.loads(open(json_file
).read())
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
)
182 check_vni_macs_present
,
186 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
188 _
, result
= topotest
.run_and_expect(test_func
, None, count
=30, wait
=1)
190 logger
.warning("%s", result
)
191 assert None, '"{}" missing expected MACs'.format(pe1
.name
)
194 def test_pe2_converge_evpn():
195 "Wait for protocol convergence"
198 # Don't run this test if we have any failure.
199 if tgen
.routers_have_failure():
200 pytest
.skip(tgen
.errors
)
202 pe2
= tgen
.gears
["PE2"]
203 json_file
= "{}/{}/evpn.vni.json".format(CWD
, pe2
.name
)
204 expected
= json
.loads(open(json_file
).read())
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
212 check_vni_macs_present
,
216 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
218 _
, result
= topotest
.run_and_expect(test_func
, None, count
=30, wait
=1)
220 logger
.warning("%s", result
)
221 assert None, '"{}" missing expected MACs'.format(pe2
.name
)
224 def mac_learn_test(host
, local
):
225 "check the host MAC gets learned by the VNI"
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]:
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
241 def mac_test_local_remote(local
, remote
):
242 "test MAC transfer between local and remote"
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
)
251 for vni
in local_output_json
:
252 mac_list
= local_output_json
[vni
]["macs"]
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"],
260 remote_output_json
[vni
]["macs"][mac
]["remoteVtep"]
261 == local_output_vni_json
[0]["vtepIp"]
265 def test_learning_pe1():
266 "test MAC learning on PE1"
269 # Don't run this test if we have any failure.
270 if tgen
.routers_have_failure():
271 pytest
.skip(tgen
.errors
)
273 host1
= tgen
.gears
["host1"]
274 pe1
= tgen
.gears
["PE1"]
275 mac_learn_test(host1
, pe1
)
278 def test_learning_pe2():
279 "test MAC learning on PE2"
282 # Don't run this test if we have any failure.
283 if tgen
.routers_have_failure():
284 pytest
.skip(tgen
.errors
)
286 host2
= tgen
.gears
["host2"]
287 pe2
= tgen
.gears
["PE2"]
288 mac_learn_test(host2
, pe2
)
291 def test_local_remote_mac_pe1():
292 "Test MAC transfer PE1 local and PE2 remote"
295 # Don't run this test if we have any failure.
296 if tgen
.routers_have_failure():
297 pytest
.skip(tgen
.errors
)
299 pe1
= tgen
.gears
["PE1"]
300 pe2
= tgen
.gears
["PE2"]
301 mac_test_local_remote(pe1
, pe2
)
304 def test_local_remote_mac_pe2():
305 "Test MAC transfer PE2 local and PE1 remote"
308 # Don't run this test if we have any failure.
309 if tgen
.routers_have_failure():
310 pytest
.skip(tgen
.errors
)
312 pe1
= tgen
.gears
["PE1"]
313 pe2
= tgen
.gears
["PE2"]
314 mac_test_local_remote(pe2
, pe1
)
316 # Memory leak test template
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]:
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
))
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]
339 assertmsg
= "local learned mac wrong type: {} ".format(mac_type
)
340 assert mac_type
== "local", assertmsg
343 "learned address mismatch with configured address host: {} learned: {}".format(
347 assert ip_addr
== learned_ip
, assertmsg
349 # now lets check the remote
353 remote_output
= remote
.vtysh_cmd(
354 "show evpn mac vni 101 mac {} json".format(mac
)
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
362 remote_output_json
[mac
]["neighbors"]["active"]
363 or remote_output_json
[mac
]["neighbors"]["inactive"]
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")
377 assert converged
== True, assertmsg
378 if remote_output_json
[mac
]["neighbors"]["active"]:
379 learned_ip
= remote_output_json
[mac
]["neighbors"]["active"][0]
381 learned_ip
= remote_output_json
[mac
]["neighbors"]["inactive"][0]
382 assertmsg
= "remote learned mac wrong type: {} ".format(type)
383 assert type == "remote", assertmsg
385 assertmsg
= "remote learned address mismatch with configured address host: {} learned: {}".format(
388 assert ip_addr
== learned_ip
, assertmsg
391 def test_ip_pe1_learn():
392 "run the IP learn test for PE1"
395 # Don't run this test if we have any failure.
396 if tgen
.routers_have_failure():
397 pytest
.skip(tgen
.errors
)
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")
410 def test_ip_pe2_learn():
411 "run the IP learn test for PE2"
414 # Don't run this test if we have any failure.
415 if tgen
.routers_have_failure():
416 pytest
.skip(tgen
.errors
)
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")
429 def test_memory_leak():
430 "Run the memory leak test and report results."
432 if not tgen
.is_memleak_enabled():
433 pytest
.skip("Memory leak test/report is disabled")
435 tgen
.report_memory_leaks()
438 if __name__
== "__main__":
439 args
= ["-s"] + sys
.argv
[1:]
440 sys
.exit(pytest
.main(args
))