]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/bgp_evpn_mh/test_evpn_mh.py
tests: micronet: adapt tests
[mirror_frr.git] / tests / topotests / bgp_evpn_mh / test_evpn_mh.py
CommitLineData
df98b92c
AK
1#!/usr/bin/env python
2
3#
4# test_evpn_mh.py
5#
6# Copyright (c) 2020 by
7# Cumulus Networks, Inc.
8# Anuradha Karuppiah
9#
10# Permission to use, copy, modify, and/or distribute this software
11# for any purpose with or without fee is hereby granted, provided
12# that the above copyright notice and this permission notice appear
13# in all copies.
14#
15# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
16# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
18# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
19# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
20# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
21# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
22# OF THIS SOFTWARE.
23#
24
25"""
26test_evpn_mh.py: Testing EVPN multihoming
27
28"""
29
30import os
31import re
32import sys
49581587
CH
33import subprocess
34from functools import partial
35
df98b92c
AK
36import pytest
37import json
38import platform
39from functools import partial
40
98ca91e1 41pytestmark = [pytest.mark.bgpd, pytest.mark.pimd]
7ed8fcff 42
df98b92c
AK
43# Save the Current Working Directory to find configuration files.
44CWD = os.path.dirname(os.path.realpath(__file__))
45sys.path.append(os.path.join(CWD, "../"))
46
47# pylint: disable=C0413
48# Import topogen and topotest helpers
49from lib import topotest
8db751b8
CH
50# Required to instantiate the topology builder class.
51from lib.micronet_compat import Topo
df98b92c
AK
52from lib.topogen import Topogen, TopoRouter, get_topogen
53from lib.topolog import logger
54
bf3a0a9a
DS
55pytestmark = [pytest.mark.bgpd, pytest.mark.pimd]
56
df98b92c
AK
57#####################################################
58##
59## Network Topology Definition
60##
61## See topology picture at evpn-mh-topo-tests.pdf
62#####################################################
63
64
65class NetworkTopo(Topo):
701a0192 66 """
df98b92c
AK
67 EVPN Multihoming Topology -
68 1. Two level CLOS
69 2. Two spine switches - spine1, spine2
70 3. Two racks with Top-of-Rack switches per rack - tormx1, tormx2
71 4. Two dual attached hosts per-rack - hostdx1, hostdx2
701a0192 72 """
df98b92c
AK
73
74 def build(self, **_opts):
75 "Build function"
76
77 tgen = get_topogen(self)
78
79 tgen.add_router("spine1")
80 tgen.add_router("spine2")
81 tgen.add_router("torm11")
82 tgen.add_router("torm12")
83 tgen.add_router("torm21")
84 tgen.add_router("torm22")
85 tgen.add_router("hostd11")
86 tgen.add_router("hostd12")
87 tgen.add_router("hostd21")
88 tgen.add_router("hostd22")
89
90 # On main router
91 # First switch is for a dummy interface (for local network)
92
df98b92c
AK
93 ##################### spine1 ########################
94 # spine1-eth0 is connected to torm11-eth0
95 switch = tgen.add_switch("sw1")
96 switch.add_link(tgen.gears["spine1"])
97 switch.add_link(tgen.gears["torm11"])
98
99 # spine1-eth1 is connected to torm12-eth0
100 switch = tgen.add_switch("sw2")
101 switch.add_link(tgen.gears["spine1"])
102 switch.add_link(tgen.gears["torm12"])
103
104 # spine1-eth2 is connected to torm21-eth0
105 switch = tgen.add_switch("sw3")
106 switch.add_link(tgen.gears["spine1"])
107 switch.add_link(tgen.gears["torm21"])
108
109 # spine1-eth3 is connected to torm22-eth0
110 switch = tgen.add_switch("sw4")
111 switch.add_link(tgen.gears["spine1"])
112 switch.add_link(tgen.gears["torm22"])
113
114 ##################### spine2 ########################
115 # spine2-eth0 is connected to torm11-eth1
116 switch = tgen.add_switch("sw5")
117 switch.add_link(tgen.gears["spine2"])
118 switch.add_link(tgen.gears["torm11"])
119
120 # spine2-eth1 is connected to torm12-eth1
121 switch = tgen.add_switch("sw6")
122 switch.add_link(tgen.gears["spine2"])
123 switch.add_link(tgen.gears["torm12"])
124
125 # spine2-eth2 is connected to torm21-eth1
126 switch = tgen.add_switch("sw7")
127 switch.add_link(tgen.gears["spine2"])
128 switch.add_link(tgen.gears["torm21"])
129
130 # spine2-eth3 is connected to torm22-eth1
131 switch = tgen.add_switch("sw8")
132 switch.add_link(tgen.gears["spine2"])
133 switch.add_link(tgen.gears["torm22"])
134
135 ##################### torm11 ########################
136 # torm11-eth2 is connected to hostd11-eth0
137 switch = tgen.add_switch("sw9")
138 switch.add_link(tgen.gears["torm11"])
139 switch.add_link(tgen.gears["hostd11"])
140
141 # torm11-eth3 is connected to hostd12-eth0
142 switch = tgen.add_switch("sw10")
143 switch.add_link(tgen.gears["torm11"])
144 switch.add_link(tgen.gears["hostd12"])
145
146 ##################### torm12 ########################
147 # torm12-eth2 is connected to hostd11-eth1
148 switch = tgen.add_switch("sw11")
149 switch.add_link(tgen.gears["torm12"])
150 switch.add_link(tgen.gears["hostd11"])
151
152 # torm12-eth3 is connected to hostd12-eth1
153 switch = tgen.add_switch("sw12")
154 switch.add_link(tgen.gears["torm12"])
155 switch.add_link(tgen.gears["hostd12"])
156
157 ##################### torm21 ########################
158 # torm21-eth2 is connected to hostd21-eth0
159 switch = tgen.add_switch("sw13")
160 switch.add_link(tgen.gears["torm21"])
161 switch.add_link(tgen.gears["hostd21"])
162
163 # torm21-eth3 is connected to hostd22-eth0
164 switch = tgen.add_switch("sw14")
165 switch.add_link(tgen.gears["torm21"])
166 switch.add_link(tgen.gears["hostd22"])
167
168 ##################### torm22 ########################
169 # torm22-eth2 is connected to hostd21-eth1
170 switch = tgen.add_switch("sw15")
171 switch.add_link(tgen.gears["torm22"])
172 switch.add_link(tgen.gears["hostd21"])
173
174 # torm22-eth3 is connected to hostd22-eth1
175 switch = tgen.add_switch("sw16")
176 switch.add_link(tgen.gears["torm22"])
177 switch.add_link(tgen.gears["hostd22"])
178
179
180#####################################################
181##
182## Tests starting
183##
184#####################################################
185
701a0192 186tor_ips = {
187 "torm11": "192.168.100.15",
188 "torm12": "192.168.100.16",
189 "torm21": "192.168.100.17",
190 "torm22": "192.168.100.18",
191}
192
193svi_ips = {
194 "torm11": "45.0.0.2",
195 "torm12": "45.0.0.3",
196 "torm21": "45.0.0.4",
197 "torm22": "45.0.0.5",
198}
df98b92c 199
701a0192 200tor_ips_rack_1 = {"torm11": "192.168.100.15", "torm12": "192.168.100.16"}
df98b92c 201
701a0192 202tor_ips_rack_2 = {"torm21": "192.168.100.17", "torm22": "192.168.100.18"}
df98b92c 203
701a0192 204host_es_map = {
205 "hostd11": "03:44:38:39:ff:ff:01:00:00:01",
206 "hostd12": "03:44:38:39:ff:ff:01:00:00:02",
207 "hostd21": "03:44:38:39:ff:ff:02:00:00:01",
208 "hostd22": "03:44:38:39:ff:ff:02:00:00:02",
209}
df98b92c 210
df98b92c
AK
211
212def config_bond(node, bond_name, bond_members, bond_ad_sys_mac, br):
701a0192 213 """
df98b92c 214 Used to setup bonds on the TORs and hosts for MH
701a0192 215 """
df98b92c
AK
216 node.run("ip link add dev %s type bond mode 802.3ad" % bond_name)
217 node.run("ip link set dev %s type bond lacp_rate 1" % bond_name)
218 node.run("ip link set dev %s type bond miimon 100" % bond_name)
219 node.run("ip link set dev %s type bond xmit_hash_policy layer3+4" % bond_name)
220 node.run("ip link set dev %s type bond min_links 1" % bond_name)
701a0192 221 node.run(
222 "ip link set dev %s type bond ad_actor_system %s" % (bond_name, bond_ad_sys_mac)
223 )
df98b92c
AK
224
225 for bond_member in bond_members:
226 node.run("ip link set dev %s down" % bond_member)
227 node.run("ip link set dev %s master %s" % (bond_member, bond_name))
228 node.run("ip link set dev %s up" % bond_member)
229
230 node.run("ip link set dev %s up" % bond_name)
231
232 # if bridge is specified add the bond as a bridge member
233 if br:
234 node.run(" ip link set dev %s master bridge" % bond_name)
235 node.run("/sbin/bridge link set dev %s priority 8" % bond_name)
236 node.run("/sbin/bridge vlan del vid 1 dev %s" % bond_name)
237 node.run("/sbin/bridge vlan del vid 1 untagged pvid dev %s" % bond_name)
238 node.run("/sbin/bridge vlan add vid 1000 dev %s" % bond_name)
701a0192 239 node.run("/sbin/bridge vlan add vid 1000 untagged pvid dev %s" % bond_name)
df98b92c
AK
240
241
242def config_mcast_tunnel_termination_device(node):
701a0192 243 """
df98b92c
AK
244 The kernel requires a device to terminate VxLAN multicast tunnels
245 when EVPN-PIM is used for flooded traffic
701a0192 246 """
df98b92c
AK
247 node.run("ip link add dev ipmr-lo type dummy")
248 node.run("ip link set dev ipmr-lo mtu 16000")
249 node.run("ip link set dev ipmr-lo mode dormant")
250 node.run("ip link set dev ipmr-lo up")
251
252
253def config_bridge(node):
701a0192 254 """
df98b92c 255 Create a VLAN aware bridge
701a0192 256 """
df98b92c
AK
257 node.run("ip link add dev bridge type bridge stp_state 0")
258 node.run("ip link set dev bridge type bridge vlan_filtering 1")
259 node.run("ip link set dev bridge mtu 9216")
260 node.run("ip link set dev bridge type bridge ageing_time 1800")
261 node.run("ip link set dev bridge type bridge mcast_snooping 0")
262 node.run("ip link set dev bridge type bridge vlan_stats_enabled 1")
263 node.run("ip link set dev bridge up")
264 node.run("/sbin/bridge vlan add vid 1000 dev bridge")
265
266
267def config_vxlan(node, node_ip):
701a0192 268 """
df98b92c
AK
269 Create a VxLAN device for VNI 1000 and add it to the bridge.
270 VLAN-1000 is mapped to VNI-1000.
701a0192 271 """
df98b92c
AK
272 node.run("ip link add dev vx-1000 type vxlan id 1000 dstport 4789")
273 node.run("ip link set dev vx-1000 type vxlan nolearning")
274 node.run("ip link set dev vx-1000 type vxlan local %s" % node_ip)
275 node.run("ip link set dev vx-1000 type vxlan ttl 64")
276 node.run("ip link set dev vx-1000 mtu 9152")
277 node.run("ip link set dev vx-1000 type vxlan dev ipmr-lo group 239.1.1.100")
278 node.run("ip link set dev vx-1000 up")
279
280 # bridge attrs
281 node.run("ip link set dev vx-1000 master bridge")
282 node.run("/sbin/bridge link set dev vx-1000 neigh_suppress on")
283 node.run("/sbin/bridge link set dev vx-1000 learning off")
284 node.run("/sbin/bridge link set dev vx-1000 priority 8")
285 node.run("/sbin/bridge vlan del vid 1 dev vx-1000")
286 node.run("/sbin/bridge vlan del vid 1 untagged pvid dev vx-1000")
287 node.run("/sbin/bridge vlan add vid 1000 dev vx-1000")
288 node.run("/sbin/bridge vlan add vid 1000 untagged pvid dev vx-1000")
289
290
291def config_svi(node, svi_pip):
701a0192 292 """
df98b92c 293 Create an SVI for VLAN 1000
701a0192 294 """
df98b92c
AK
295 node.run("ip link add link bridge name vlan1000 type vlan id 1000 protocol 802.1q")
296 node.run("ip addr add %s/24 dev vlan1000" % svi_pip)
297 node.run("ip link set dev vlan1000 up")
298 node.run("/sbin/sysctl net.ipv4.conf.vlan1000.arp_accept=1")
299 node.run("ip link add link vlan1000 name vlan1000-v0 type macvlan mode private")
300 node.run("/sbin/sysctl net.ipv6.conf.vlan1000-v0.accept_dad=0")
301 node.run("/sbin/sysctl net.ipv6.conf.vlan1000-v0.dad_transmits")
302 node.run("/sbin/sysctl net.ipv6.conf.vlan1000-v0.dad_transmits=0")
303 node.run("ip link set dev vlan1000-v0 address 00:00:5e:00:01:01")
304 node.run("ip link set dev vlan1000-v0 up")
305 # metric 1024 is not working
306 node.run("ip addr add 45.0.0.1/24 dev vlan1000-v0")
307
308
309def config_tor(tor_name, tor, tor_ip, svi_pip):
701a0192 310 """
df98b92c 311 Create the bond/vxlan-bridge on the TOR which acts as VTEP and EPN-PE
701a0192 312 """
df98b92c
AK
313 # create a device for terminating VxLAN multicast tunnels
314 config_mcast_tunnel_termination_device(tor)
315
316 # create a vlan aware bridge
317 config_bridge(tor)
318
319 # create vxlan device and add it to bridge
320 config_vxlan(tor, tor_ip)
321
322 # create hostbonds and add them to the bridge
323 if "torm1" in tor_name:
324 sys_mac = "44:38:39:ff:ff:01"
325 else:
326 sys_mac = "44:38:39:ff:ff:02"
327 bond_member = tor_name + "-eth2"
328 config_bond(tor, "hostbond1", [bond_member], sys_mac, "bridge")
329
330 bond_member = tor_name + "-eth3"
331 config_bond(tor, "hostbond2", [bond_member], sys_mac, "bridge")
332
333 # create SVI
334 config_svi(tor, svi_pip)
335
336
337def config_tors(tgen, tors):
338 for tor_name in tors:
339 tor = tgen.gears[tor_name]
340 config_tor(tor_name, tor, tor_ips.get(tor_name), svi_ips.get(tor_name))
341
701a0192 342
df98b92c
AK
343def compute_host_ip_mac(host_name):
344 host_id = host_name.split("hostd")[1]
701a0192 345 host_ip = "45.0.0." + host_id + "/24"
df98b92c
AK
346 host_mac = "00:00:00:00:00:" + host_id
347
348 return host_ip, host_mac
349
701a0192 350
df98b92c 351def config_host(host_name, host):
701a0192 352 """
df98b92c 353 Create the dual-attached bond on host nodes for MH
701a0192 354 """
df98b92c
AK
355 bond_members = []
356 bond_members.append(host_name + "-eth0")
357 bond_members.append(host_name + "-eth1")
358 bond_name = "torbond"
359 config_bond(host, bond_name, bond_members, "00:00:00:00:00:00", None)
360
361 host_ip, host_mac = compute_host_ip_mac(host_name)
362 host.run("ip addr add %s dev %s" % (host_ip, bond_name))
363 host.run("ip link set dev %s address %s" % (bond_name, host_mac))
364
365
366def config_hosts(tgen, hosts):
367 for host_name in hosts:
368 host = tgen.gears[host_name]
369 config_host(host_name, host)
370
7ed8fcff 371
df98b92c
AK
372def setup_module(module):
373 "Setup topology"
374 tgen = Topogen(NetworkTopo, module.__name__)
375 tgen.start_topology()
376
377 krel = platform.release()
378 if topotest.version_cmp(krel, "4.19") < 0:
379 tgen.errors = "kernel 4.19 needed for multihoming tests"
380 pytest.skip(tgen.errors)
381
382 tors = []
383 tors.append("torm11")
384 tors.append("torm12")
385 tors.append("torm21")
386 tors.append("torm22")
387 config_tors(tgen, tors)
388
389 hosts = []
390 hosts.append("hostd11")
391 hosts.append("hostd12")
392 hosts.append("hostd21")
393 hosts.append("hostd22")
394 config_hosts(tgen, hosts)
395
396 # tgen.mininet_cli()
397 # This is a sample of configuration loading.
398 router_list = tgen.routers()
e5f0ed14 399 for rname, router in router_list.items():
df98b92c
AK
400 router.load_config(
401 TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
402 )
403 router.load_config(
404 TopoRouter.RD_PIM, os.path.join(CWD, "{}/pim.conf".format(rname))
405 )
406 router.load_config(
407 TopoRouter.RD_BGP, os.path.join(CWD, "{}/evpn.conf".format(rname))
408 )
409 tgen.start_router()
410 # tgen.mininet_cli()
411
412
413def teardown_module(_mod):
414 "Teardown the pytest environment"
415 tgen = get_topogen()
416
417 # This function tears down the whole topology.
418 tgen.stop_topology()
419
420
421def check_local_es(esi, vtep_ips, dut_name, down_vteps):
701a0192 422 """
df98b92c 423 Check if ES peers are setup correctly on local ESs
701a0192 424 """
df98b92c
AK
425 peer_ips = []
426 if "torm1" in dut_name:
427 tor_ips_rack = tor_ips_rack_1
428 else:
429 tor_ips_rack = tor_ips_rack_2
430
e5f0ed14 431 for tor_name, tor_ip in tor_ips_rack.items():
df98b92c
AK
432 if dut_name not in tor_name:
433 peer_ips.append(tor_ip)
434
435 # remove down VTEPs from the peer check list
436 peer_set = set(peer_ips)
437 down_vtep_set = set(down_vteps)
438 peer_set = peer_set - down_vtep_set
439
440 vtep_set = set(vtep_ips)
441 diff = peer_set.symmetric_difference(vtep_set)
442
443 return (esi, diff) if diff else None
444
445
446def check_remote_es(esi, vtep_ips, dut_name, down_vteps):
701a0192 447 """
df98b92c 448 Verify list of PEs associated with a remote ES
701a0192 449 """
df98b92c
AK
450 remote_ips = []
451
452 if "torm1" in dut_name:
453 tor_ips_rack = tor_ips_rack_2
454 else:
455 tor_ips_rack = tor_ips_rack_1
456
e5f0ed14 457 for tor_name, tor_ip in tor_ips_rack.items():
df98b92c
AK
458 remote_ips.append(tor_ip)
459
460 # remove down VTEPs from the remote check list
461 remote_set = set(remote_ips)
462 down_vtep_set = set(down_vteps)
463 remote_set = remote_set - down_vtep_set
464
465 vtep_set = set(vtep_ips)
466 diff = remote_set.symmetric_difference(vtep_set)
467
468 return (esi, diff) if diff else None
469
701a0192 470
df98b92c 471def check_es(dut):
701a0192 472 """
df98b92c 473 Verify list of PEs associated all ESs, local and remote
701a0192 474 """
df98b92c
AK
475 bgp_es = dut.vtysh_cmd("show bgp l2vp evpn es json")
476 bgp_es_json = json.loads(bgp_es)
477
478 result = None
479
e5f0ed14 480 expected_es_set = set([v for k, v in host_es_map.items()])
df98b92c
AK
481 curr_es_set = []
482
483 # check is ES content is correct
484 for es in bgp_es_json:
485 esi = es["esi"]
486 curr_es_set.append(esi)
487 types = es["type"]
488 vtep_ips = []
6bf6282d 489 for vtep in es.get("vteps", []):
df98b92c
AK
490 vtep_ips.append(vtep["vtep_ip"])
491
492 if "local" in types:
11761ab0 493 result = check_local_es(esi, vtep_ips, dut.name, [])
df98b92c 494 else:
11761ab0 495 result = check_remote_es(esi, vtep_ips, dut.name, [])
df98b92c
AK
496
497 if result:
498 return result
499
500 # check if all ESs are present
501 curr_es_set = set(curr_es_set)
502 result = curr_es_set.symmetric_difference(expected_es_set)
503
504 return result if result else None
505
701a0192 506
df98b92c 507def check_one_es(dut, esi, down_vteps):
701a0192 508 """
df98b92c 509 Verify list of PEs associated all ESs, local and remote
701a0192 510 """
df98b92c
AK
511 bgp_es = dut.vtysh_cmd("show bgp l2vp evpn es %s json" % esi)
512 es = json.loads(bgp_es)
513
514 if not es:
515 return "esi %s not found" % esi
516
517 esi = es["esi"]
518 types = es["type"]
519 vtep_ips = []
6bf6282d 520 for vtep in es.get("vteps", []):
df98b92c
AK
521 vtep_ips.append(vtep["vtep_ip"])
522
523 if "local" in types:
524 result = check_local_es(esi, vtep_ips, dut.name, down_vteps)
525 else:
526 result = check_remote_es(esi, vtep_ips, dut.name, down_vteps)
527
528 return result
529
701a0192 530
df98b92c 531def test_evpn_es():
701a0192 532 """
df98b92c
AK
533 Two ES are setup on each rack. This test checks if -
534 1. ES peer has been added to the local ES (via Type-1/EAD route)
535 2. The remote ESs are setup with the right list of PEs (via Type-1)
701a0192 536 """
df98b92c
AK
537
538 tgen = get_topogen()
539
540 if tgen.routers_have_failure():
541 pytest.skip(tgen.errors)
542
543 dut_name = "torm11"
544 dut = tgen.gears[dut_name]
545 test_fn = partial(check_es, dut)
546 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
547
548 assertmsg = '"{}" ES content incorrect'.format(dut_name)
549 assert result is None, assertmsg
550 # tgen.mininet_cli()
551
701a0192 552
df98b92c 553def test_evpn_ead_update():
701a0192 554 """
df98b92c
AK
555 Flap a host link one the remote rack and check if the EAD updates
556 are sent/processed for the corresponding ESI
701a0192 557 """
df98b92c
AK
558 tgen = get_topogen()
559
560 if tgen.routers_have_failure():
561 pytest.skip(tgen.errors)
562
563 # dut on rack1 and host link flap on rack2
564 dut_name = "torm11"
565 dut = tgen.gears[dut_name]
566
567 remote_tor_name = "torm21"
568 remote_tor = tgen.gears[remote_tor_name]
569
570 host_name = "hostd21"
571 host = tgen.gears[host_name]
572 esi = host_es_map.get(host_name)
573
574 # check if the VTEP list is right to start with
575 down_vteps = []
576 test_fn = partial(check_one_es, dut, esi, down_vteps)
577 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
578 assertmsg = '"{}" ES content incorrect'.format(dut_name)
579 assert result is None, assertmsg
580
581 # down a remote host link and check if the EAD withdraw is rxed
582 # Note: LACP is not working as expected so I am temporarily shutting
583 # down the link on the remote TOR instead of the remote host
584 remote_tor.run("ip link set dev %s-%s down" % (remote_tor_name, "eth2"))
585 down_vteps.append(tor_ips.get(remote_tor_name))
586 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
587 assertmsg = '"{}" ES incorrect after remote link down'.format(dut_name)
588 assert result is None, assertmsg
589
590 # bring up remote host link and check if the EAD update is rxed
591 down_vteps.remove(tor_ips.get(remote_tor_name))
592 remote_tor.run("ip link set dev %s-%s up" % (remote_tor_name, "eth2"))
593 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
594 assertmsg = '"{}" ES incorrect after remote link flap'.format(dut_name)
595 assert result is None, assertmsg
596
597 # tgen.mininet_cli()
598
701a0192 599
6bf6282d 600def ping_anycast_gw(tgen):
6bf6282d
AK
601 # ping the anycast gw from the local and remote hosts to populate
602 # the mac address on the PEs
49581587 603 python3_path = tgen.net.get_exec_path(["python3", "python"])
002e6825
CH
604 script_path = os.path.abspath(os.path.join(CWD, "../lib/scapy_sendpkt.py"))
605 intf = "torbond"
606 ipaddr = "45.0.0.1"
607 ping_cmd = [
49581587 608 python3_path,
002e6825
CH
609 script_path,
610 "--imports=Ether,ARP",
611 "--interface=" + intf,
49581587 612 'Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="{}")'.format(ipaddr)
002e6825
CH
613 ]
614 for name in ("hostd11", "hostd21"):
49581587
CH
615 host = tgen.net.hosts[name]
616 _, stdout, _ = host.cmd_status(ping_cmd, warn=False, stderr=subprocess.STDOUT)
002e6825
CH
617 stdout = stdout.strip()
618 if stdout:
619 host.logger.debug("%s: arping on %s for %s returned: %s", name, intf, ipaddr, stdout)
6bf6282d 620
5980ad0a 621
6bf6282d 622def check_mac(dut, vni, mac, m_type, esi, intf, ping_gw=False, tgen=None):
701a0192 623 """
df98b92c 624 checks if mac is present and if desination matches the one provided
701a0192 625 """
df98b92c 626
6bf6282d
AK
627 if ping_gw:
628 ping_anycast_gw(tgen)
629
df98b92c
AK
630 out = dut.vtysh_cmd("show evpn mac vni %d mac %s json" % (vni, mac))
631
632 mac_js = json.loads(out)
e5f0ed14 633 for mac, info in mac_js.items():
df98b92c 634 tmp_esi = info.get("esi", "")
701a0192 635 tmp_m_type = info.get("type", "")
df98b92c
AK
636 tmp_intf = info.get("intf", "") if tmp_m_type == "local" else ""
637 if tmp_esi == esi and tmp_m_type == m_type and intf == intf:
638 return None
639
640 return "invalid vni %d mac %s out %s" % (vni, mac, mac_js)
641
701a0192 642
df98b92c 643def test_evpn_mac():
701a0192 644 """
df98b92c
AK
645 1. Add a MAC on hostd11 and check if the MAC is synced between
646 torm11 and torm12. And installed as a local MAC.
647 2. Add a MAC on hostd21 and check if the MAC is installed as a
648 remote MAC on torm11 and torm12
701a0192 649 """
df98b92c
AK
650
651 tgen = get_topogen()
652
653 local_host = tgen.gears["hostd11"]
654 remote_host = tgen.gears["hostd21"]
655 tors = []
656 tors.append(tgen.gears["torm11"])
657 tors.append(tgen.gears["torm12"])
658
df98b92c
AK
659 vni = 1000
660
661 # check if the rack-1 host MAC is present on all rack-1 PEs
662 # and points to local access port
663 m_type = "local"
664 _, mac = compute_host_ip_mac(local_host.name)
665 esi = host_es_map.get(local_host.name)
666 intf = "hostbond1"
667
668 for tor in tors:
6bf6282d 669 test_fn = partial(check_mac, tor, vni, mac, m_type, esi, intf, True, tgen)
df98b92c
AK
670 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
671 assertmsg = '"{}" local MAC content incorrect'.format(tor.name)
672 assert result is None, assertmsg
673
674 # check if the rack-2 host MAC is present on all rack-1 PEs
675 # and points to the remote ES destination
676 m_type = "remote"
677 _, mac = compute_host_ip_mac(remote_host.name)
678 esi = host_es_map.get(remote_host.name)
679 intf = ""
680
681 for tor in tors:
682 test_fn = partial(check_mac, tor, vni, mac, m_type, esi, intf)
683 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
684 assertmsg = '"{}" remote MAC content incorrect'.format(tor.name)
685 assert result is None, assertmsg
686
9fa6ec14 687
732a1ac9 688def check_df_role(dut, esi, role):
9fa6ec14 689 """
732a1ac9 690 Return error string if the df role on the dut is different
9fa6ec14 691 """
732a1ac9
AK
692 es_json = dut.vtysh_cmd("show evpn es %s json" % esi)
693 es = json.loads(es_json)
694
695 if not es:
696 return "esi %s not found" % esi
697
698 flags = es.get("flags", [])
699 curr_role = "nonDF" if "nonDF" in flags else "DF"
700
701 if curr_role != role:
702 return "%s is %s for %s" % (dut.name, curr_role, esi)
703
704 return None
705
9fa6ec14 706
732a1ac9 707def test_evpn_df():
9fa6ec14 708 """
732a1ac9
AK
709 1. Check the DF role on all the PEs on rack-1.
710 2. Increase the DF preference on the non-DF and check if it becomes
711 the DF winner.
9fa6ec14 712 """
732a1ac9
AK
713
714 tgen = get_topogen()
715
716 if tgen.routers_have_failure():
717 pytest.skip(tgen.errors)
718
719 # We will run the tests on just one ES
720 esi = host_es_map.get("hostd11")
721 intf = "hostbond1"
722
723 tors = []
724 tors.append(tgen.gears["torm11"])
725 tors.append(tgen.gears["torm12"])
726 df_node = "torm11"
727
728 # check roles on rack-1
729 for tor in tors:
730 role = "DF" if tor.name == df_node else "nonDF"
731 test_fn = partial(check_df_role, tor, esi, role)
732 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
733 assertmsg = '"{}" DF role incorrect'.format(tor.name)
734 assert result is None, assertmsg
735
736 # change df preference on the nonDF to make it the df
737 torm12 = tgen.gears["torm12"]
738 torm12.vtysh_cmd("conf\ninterface %s\nevpn mh es-df-pref %d" % (intf, 60000))
739 df_node = "torm12"
740
741 # re-check roles on rack-1; we should have a new winner
742 for tor in tors:
743 role = "DF" if tor.name == df_node else "nonDF"
744 test_fn = partial(check_df_role, tor, esi, role)
745 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
746 assertmsg = '"{}" DF role incorrect'.format(tor.name)
747 assert result is None, assertmsg
748
749 # tgen.mininet_cli()
701a0192 750
9fa6ec14 751
f93333e9 752def check_protodown_rc(dut, protodown_rc):
9fa6ec14 753 """
f93333e9 754 check if specified protodown reason code is set
9fa6ec14 755 """
f93333e9
AK
756
757 out = dut.vtysh_cmd("show evpn json")
758
759 evpn_js = json.loads(out)
760 tmp_rc = evpn_js.get("protodownReasons", [])
761
762 if protodown_rc:
763 if protodown_rc not in tmp_rc:
764 return "protodown %s missing in %s" % (protodown_rc, tmp_rc)
765 else:
766 if tmp_rc:
767 return "unexpected protodown rc %s" % (tmp_rc)
768
769 return None
770
9fa6ec14 771
f93333e9 772def test_evpn_uplink_tracking():
9fa6ec14 773 """
f93333e9
AK
774 1. Wait for access ports to come out of startup-delay
775 2. disable uplinks and check if access ports have been protodowned
776 3. enable uplinks and check if access ports have been moved out
777 of protodown
9fa6ec14 778 """
f93333e9
AK
779
780 tgen = get_topogen()
781
782 dut_name = "torm11"
783 dut = tgen.gears[dut_name]
784
785 # wait for protodown rc to clear after startup
786 test_fn = partial(check_protodown_rc, dut, None)
787 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
788 assertmsg = '"{}" protodown rc incorrect'.format(dut_name)
789 assert result is None, assertmsg
790
791 # disable the uplinks
792 dut.run("ip link set %s-eth0 down" % dut_name)
793 dut.run("ip link set %s-eth1 down" % dut_name)
794
795 # check if the access ports have been protodowned
796 test_fn = partial(check_protodown_rc, dut, "uplinkDown")
797 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
798 assertmsg = '"{}" protodown rc incorrect'.format(dut_name)
799 assert result is None, assertmsg
800
801 # enable the uplinks
802 dut.run("ip link set %s-eth0 up" % dut_name)
803 dut.run("ip link set %s-eth1 up" % dut_name)
804
805 # check if the access ports have been moved out of protodown
806 test_fn = partial(check_protodown_rc, dut, None)
807 _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3)
808 assertmsg = '"{}" protodown rc incorrect'.format(dut_name)
809 assert result is None, assertmsg
810
9fa6ec14 811
df98b92c
AK
812if __name__ == "__main__":
813 args = ["-s"] + sys.argv[1:]
814 sys.exit(pytest.main(args))