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 registered 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)
147 if "ifindex" in output_json
:
148 output_json
.pop("ifindex")
150 return topotest
.json_cmp(output_json
, expected
)
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)
164 def test_pe1_converge_evpn():
165 "Wait for protocol convergence"
168 # Don't run this test if we have any failure.
169 if tgen
.routers_have_failure():
170 pytest
.skip(tgen
.errors
)
172 pe1
= tgen
.gears
["PE1"]
173 json_file
= "{}/{}/evpn.vni.json".format(CWD
, pe1
.name
)
174 expected
= json
.loads(open(json_file
).read())
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
)
181 check_vni_macs_present
,
185 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
187 _
, result
= topotest
.run_and_expect(test_func
, None, count
=30, wait
=1)
189 logger
.warning("%s", result
)
190 assert None, '"{}" missing expected MACs'.format(pe1
.name
)
193 def test_pe2_converge_evpn():
194 "Wait for protocol convergence"
197 # Don't run this test if we have any failure.
198 if tgen
.routers_have_failure():
199 pytest
.skip(tgen
.errors
)
201 pe2
= tgen
.gears
["PE2"]
202 json_file
= "{}/{}/evpn.vni.json".format(CWD
, pe2
.name
)
203 expected
= json
.loads(open(json_file
).read())
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
211 check_vni_macs_present
,
215 (("host1", "host1-eth0"), ("host2", "host2-eth0")),
217 _
, result
= topotest
.run_and_expect(test_func
, None, count
=30, wait
=1)
219 logger
.warning("%s", result
)
220 assert None, '"{}" missing expected MACs'.format(pe2
.name
)
223 def mac_learn_test(host
, local
):
224 "check the host MAC gets learned by the VNI"
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]:
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
240 def mac_test_local_remote(local
, remote
):
241 "test MAC transfer between local and remote"
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
)
250 for vni
in local_output_json
:
251 mac_list
= local_output_json
[vni
]["macs"]
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"],
259 remote_output_json
[vni
]["macs"][mac
]["remoteVtep"]
260 == local_output_vni_json
[0]["vtepIp"]
264 def test_learning_pe1():
265 "test MAC learning on PE1"
268 # Don't run this test if we have any failure.
269 if tgen
.routers_have_failure():
270 pytest
.skip(tgen
.errors
)
272 host1
= tgen
.gears
["host1"]
273 pe1
= tgen
.gears
["PE1"]
274 mac_learn_test(host1
, pe1
)
277 def test_learning_pe2():
278 "test MAC learning on PE2"
281 # Don't run this test if we have any failure.
282 if tgen
.routers_have_failure():
283 pytest
.skip(tgen
.errors
)
285 host2
= tgen
.gears
["host2"]
286 pe2
= tgen
.gears
["PE2"]
287 mac_learn_test(host2
, pe2
)
290 def test_local_remote_mac_pe1():
291 "Test MAC transfer PE1 local and PE2 remote"
294 # Don't run this test if we have any failure.
295 if tgen
.routers_have_failure():
296 pytest
.skip(tgen
.errors
)
298 pe1
= tgen
.gears
["PE1"]
299 pe2
= tgen
.gears
["PE2"]
300 mac_test_local_remote(pe1
, pe2
)
303 def test_local_remote_mac_pe2():
304 "Test MAC transfer PE2 local and PE1 remote"
307 # Don't run this test if we have any failure.
308 if tgen
.routers_have_failure():
309 pytest
.skip(tgen
.errors
)
311 pe1
= tgen
.gears
["PE1"]
312 pe2
= tgen
.gears
["PE2"]
313 mac_test_local_remote(pe2
, pe1
)
315 # Memory leak test template
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]:
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
))
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]
338 assertmsg
= "local learned mac wrong type: {} ".format(mac_type
)
339 assert mac_type
== "local", assertmsg
342 "learned address mismatch with configured address host: {} learned: {}".format(
346 assert ip_addr
== learned_ip
, assertmsg
348 # now lets check the remote
352 remote_output
= remote
.vtysh_cmd(
353 "show evpn mac vni 101 mac {} json".format(mac
)
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
361 remote_output_json
[mac
]["neighbors"]["active"]
362 or remote_output_json
[mac
]["neighbors"]["inactive"]
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")
376 assert converged
== True, assertmsg
377 if remote_output_json
[mac
]["neighbors"]["active"]:
378 learned_ip
= remote_output_json
[mac
]["neighbors"]["active"][0]
380 learned_ip
= remote_output_json
[mac
]["neighbors"]["inactive"][0]
381 assertmsg
= "remote learned mac wrong type: {} ".format(type)
382 assert type == "remote", assertmsg
384 assertmsg
= "remote learned address mismatch with configured address host: {} learned: {}".format(
387 assert ip_addr
== learned_ip
, assertmsg
390 def test_ip_pe1_learn():
391 "run the IP learn test for PE1"
394 # Don't run this test if we have any failure.
395 if tgen
.routers_have_failure():
396 pytest
.skip(tgen
.errors
)
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")
409 def test_ip_pe2_learn():
410 "run the IP learn test for PE2"
413 # Don't run this test if we have any failure.
414 if tgen
.routers_have_failure():
415 pytest
.skip(tgen
.errors
)
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")
428 def test_memory_leak():
429 "Run the memory leak test and report results."
431 if not tgen
.is_memleak_enabled():
432 pytest
.skip("Memory leak test/report is disabled")
434 tgen
.report_memory_leaks()
437 if __name__
== "__main__":
438 args
= ["-s"] + sys
.argv
[1:]
439 sys
.exit(pytest
.main(args
))