]>
Commit | Line | Data |
---|---|---|
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 | """ | |
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 | |
578c52e5 | 32 | from time import sleep |
6f77a974 PR |
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. | |
6f77a974 | 46 | |
3dedee4f | 47 | pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] |
6f77a974 | 48 | |
5980ad0a | 49 | |
e82b531d CH |
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"]) | |
6f77a974 PR |
83 | |
84 | ||
85 | def 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 | ||
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 | ||
2d706c4e PR |
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 | ||
bc51ce68 CH |
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 | ||
6f77a974 PR |
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 | ||
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 | ||
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 | ||
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 | ||
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() | |
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 | ||
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(): | |
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 | ||
304 | def 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 |
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() | |
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 | ||
391 | def 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 | ||
410 | def 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 |
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)) |