diff --git a/tests/topotests/evpn_type5_test_topo1/__init__.py b/tests/topotests/evpn_type5_test_topo1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/evpn_type5_test_topo1/evpn_type5_chaos_topo1.json b/tests/topotests/evpn_type5_test_topo1/evpn_type5_chaos_topo1.json new file mode 100644 index 0000000000..14842da326 --- /dev/null +++ b/tests/topotests/evpn_type5_test_topo1/evpn_type5_chaos_topo1.json @@ -0,0 +1,887 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "e1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "1", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network":"10.1.1.1/32", + "next_hop":"Null0", + "vrf": "RED" + }, + { + "network":"10::1/128", + "next_hop":"Null0", + "vrf": "RED" + } + ] + }, + "r2": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "e1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + }, + { + "name": "GREEN", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "2", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link2": {} + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network":"20.1.1.1/32", + "next_hop":"Null0", + "vrf": "BLUE" + }, + { + "network":"20::1/128", + "next_hop":"Null0", + "vrf": "BLUE" + }, + { + "network":"30.1.1.1/32", + "next_hop":"Null0", + "vrf": "GREEN" + }, + { + "network":"30::1/128", + "next_hop":"Null0", + "vrf": "GREEN" + } + ] + }, + "e1": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "d1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "d2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "e1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "e1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "e1-link1": { + "deactivate": "ipv4" + } + } + }, + "d2": { + "dest_link": { + "e1-link1": { + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "d1": { + "ipv4":{ + "e1-link1": "activate" + } + }, + "d2": { + "ipv4":{ + "e1-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + } + ] + }, + "d1": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "e1": { + "dest_link": { + "d1-link1": { + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4":{ + "d1-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + }, + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + } + ] + }, + "d2": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "e1": { + "dest_link": { + "d2-link1": { + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4":{ + "d2-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + }, + { + "local_as": "200", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + } + ] + }, + "r3": { + "links": { + "d1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "d2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r3": {} + } + }, + "d2": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r3": {} + } + }, + "d2": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + ] + }, + "r4": { + "links": { + "d1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "d1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "d2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "d2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + }, + { + "name": "GREEN", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "4", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link1": {} + } + }, + "d2": { + "dest_link": { + "r4-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link1": {} + } + }, + "d2": { + "dest_link": { + "r4-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link2": {} + } + }, + "d2": { + "dest_link": { + "r4-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link2": {} + } + }, + "d2": { + "dest_link": { + "r4-link2": {} + } + } + } + } + } + } + } + ] + } + } +} diff --git a/tests/topotests/evpn_type5_test_topo1/evpn_type5_topo1.json b/tests/topotests/evpn_type5_test_topo1/evpn_type5_topo1.json new file mode 100644 index 0000000000..14842da326 --- /dev/null +++ b/tests/topotests/evpn_type5_test_topo1/evpn_type5_topo1.json @@ -0,0 +1,887 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "e1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "1", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network":"10.1.1.1/32", + "next_hop":"Null0", + "vrf": "RED" + }, + { + "network":"10::1/128", + "next_hop":"Null0", + "vrf": "RED" + } + ] + }, + "r2": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "e1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + }, + { + "name": "GREEN", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "2", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link2": {} + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network":"20.1.1.1/32", + "next_hop":"Null0", + "vrf": "BLUE" + }, + { + "network":"20::1/128", + "next_hop":"Null0", + "vrf": "BLUE" + }, + { + "network":"30.1.1.1/32", + "next_hop":"Null0", + "vrf": "GREEN" + }, + { + "network":"30::1/128", + "next_hop":"Null0", + "vrf": "GREEN" + } + ] + }, + "e1": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "d1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "d2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "e1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "e1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "e1-link1": { + "deactivate": "ipv4" + } + } + }, + "d2": { + "dest_link": { + "e1-link1": { + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "d1": { + "ipv4":{ + "e1-link1": "activate" + } + }, + "d2": { + "ipv4":{ + "e1-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + } + ] + }, + "d1": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "e1": { + "dest_link": { + "d1-link1": { + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4":{ + "d1-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + }, + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + } + ] + }, + "d2": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "e1": { + "dest_link": { + "d2-link1": { + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4":{ + "d2-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + }, + { + "local_as": "200", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + } + ] + }, + "r3": { + "links": { + "d1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "d2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r3": {} + } + }, + "d2": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r3": {} + } + }, + "d2": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + ] + }, + "r4": { + "links": { + "d1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "d1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "d2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "d2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + }, + { + "name": "GREEN", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "4", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link1": {} + } + }, + "d2": { + "dest_link": { + "r4-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link1": {} + } + }, + "d2": { + "dest_link": { + "r4-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link2": {} + } + }, + "d2": { + "dest_link": { + "r4-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link2": {} + } + }, + "d2": { + "dest_link": { + "r4-link2": {} + } + } + } + } + } + } + } + ] + } + } +} diff --git a/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_chaos_topo1.py b/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_chaos_topo1.py new file mode 100755 index 0000000000..e160264cad --- /dev/null +++ b/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_chaos_topo1.py @@ -0,0 +1,1048 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +Following tests are covered to test EVPN-Type5 functionality: + +1. In absence of an overlay index all IP-Prefixes(RT-5) + are advertised with default values for below parameters: + --> Ethernet Tag ID = GW IP address = ESI=0 +2. EVPN CLI output and JSON format validation. +3. RT verification(auto) +""" + +import os +import re +import sys +import json +import time +import pytest +import platform +from copy import deepcopy +from time import sleep + + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topotest import version_cmp +from lib.topogen import Topogen, get_topogen +from mininet.topo import Topo + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + start_router_daemons, + kill_router_daemons, + create_static_routes, + create_vrf_cfg, + create_route_maps, + create_interface_in_kernel, + check_router_status, + configure_vxlan, + configure_brctl, + apply_raw_config, + verify_vrf_vni, + verify_cli_json +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + clear_bgp, + verify_best_path_as_per_bgp_attribute, + verify_attributes_for_evpn_routes, + verify_evpn_routes +) +from lib.topojson import build_topo_from_json, build_config_from_json + +# Reading the data from JSON File for topology creation +jsonFile = "{}/evpn_type5_chaos_topo1.json".format(CWD) +try: + with open(jsonFile, "r") as topoJson: + topo = json.load(topoJson) +except IOError: + assert False, "Could not read file {}".format(jsonFile) + +# Reading the data from JSON File for topology creation +# Global variables +TCPDUMP_FILE = "evpn_log.txt" +LOGDIR = "/tmp/topotests/" +NETWORK1_1 = {"ipv4": "10.1.1.1/32", "ipv6": "10::1/128"} +NETWORK1_2 = {"ipv4": "40.1.1.1/32", "ipv6": "40::1/128"} +NETWORK1_3 = {"ipv4": "40.1.1.2/32", "ipv6": "40::2/128"} +NETWORK1_4 = {"ipv4": "40.1.1.3/32", "ipv6": "40::3/128"} +NETWORK2_1 = {"ipv4": "20.1.1.1/32", "ipv6": "20::1/128"} +NETWORK3_1 = {"ipv4": "30.1.1.1/32", "ipv6": "30::1/128"} +NETWORK4_1 = {"ipv4": "100.1.1.1/32 ", "ipv6": "100::100/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} +VNI_1 = 75100 +VNI_2 = 75200 +VNI_3 = 75300 +MAC_1 = "00:80:48:ba:d1:00" +MAC_2 = "00:80:48:ba:d1:01" +MAC_3 = "00:80:48:ba:d1:02" +BRCTL_1 = "br100" +BRCTL_2 = "br200" +BRCTL_3 = "br300" +VXLAN_1 = "vxlan75100" +VXLAN_2 = "vxlan75200" +VXLAN_3 = "vxlan75300" +BRIDGE_INTF1 = "120.0.0.1" +BRIDGE_INTF2 = "120.0.0.2" +BRIDGE_INTF3 = "120.0.0.3" +MULTICAST_MAC1 = "01:00:5e:00:52:02" + +VXLAN = { + "vxlan_name": [VXLAN_1, VXLAN_2, VXLAN_3], + "vxlan_id": [75100, 75200, 75300], + "dstport": 4789, + "local_addr": {"e1": BRIDGE_INTF1, "d1": BRIDGE_INTF2, "d2": BRIDGE_INTF3}, + "learning": "no", +} +BRCTL = { + "brctl_name": [BRCTL_1, BRCTL_2, BRCTL_3], + "addvxlan": [VXLAN_1, VXLAN_2, VXLAN_3], + "vrf": ["RED", "BLUE", "GREEN"], + "stp": [0, 0, 0], +} + + +class CreateTopo(Topo): + """ + Test BasicTopo - topology 1 + + * `Topo`: Topology object + """ + + def build(self, *_args, **_opts): + """Build function""" + tgen = get_topogen(self) + + # Building topology from json file + build_topo_from_json(tgen, topo) + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global topo + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + tgen = Topogen(CreateTopo, mod.__name__) + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start deamons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + if version_cmp(platform.release(), '4.19') < 0: + error_msg = ('EVPN tests will not run (have kernel "{}", ' + 'but it requires >= 4.19)'.format(platform.release())) + pytest.skip(error_msg) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Pre-requisite config for testsuite") + prerequisite_config_for_test_suite(tgen) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def prerequisite_config_for_test_suite(tgen): + """ + API to do prerequisite config for testsuite + + parameters: + ----------- + * `tgen`: topogen object + """ + + step("Configure vxlan, bridge interface") + for dut in ["e1", "d1", "d2"]: + step("[DUT: ]Configure vxlan") + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + } + ] + } + } + + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure bridge interface") + brctl_input = { + dut: { + "brctl": [ + { + "brctl_name": BRCTL["brctl_name"], + "addvxlan": BRCTL["addvxlan"], + "vrf": BRCTL["vrf"], + "stp": BRCTL["stp"], + } + ] + } + } + result = configure_brctl(tgen, topo, brctl_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure default routes") + add_default_routes(tgen) + + +def add_default_routes(tgen): + """ + API to do prerequisite config for testsuite + + parameters: + ----------- + * `tgen`: topogen object + """ + + step("Add default routes..") + + default_routes = { + "e1": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["d1"]), + "next_hop": topo["routers"]["d1"]["links"]["e1-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["d2"]), + "next_hop": topo["routers"]["d2"]["links"]["e1-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + "d1": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["e1"]), + "next_hop": topo["routers"]["e1"]["links"]["d1-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["d2"]), + "next_hop": topo["routers"]["e1"]["links"]["d1-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + "d2": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["d1"]), + "next_hop": topo["routers"]["e1"]["links"]["d2-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["e1"]), + "next_hop": topo["routers"]["e1"]["links"]["d2-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + } + + result = create_static_routes(tgen, default_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + +def test_verify_overlay_index_p1(request): + """ + In absence of an overlay index all IP-Prefixes(RT-5) + are advertised with default values for below parameters: + --> Ethernet Tag ID = GW IP address = ESI=0 + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Following steps are taken care in base config:") + step( + "Configure BGP neighborship for both address families" + "(IPv4 & IPv6) between Edge-1 and VFN routers(R1 and R2)" + ) + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + step("Advertise VRF routes as in EVPN address family from Edge-1 " "router.") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify: Prefixes are received in all VRFs on Edge-1 router.") + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that EVPN routes, received on DCG-1 and DCG-2 do not " + "carry any overlay index and these indexes are set to default " + "value=0. " + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + + result = verify_attributes_for_evpn_routes( + tgen, topo, "d1", input_routes, ethTag=0 + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "d2", input_routes, ethTag=0 + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_evpn_cli_json_available_p1(request): + """ + EVPN CLI output and JSON format validation. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Need to verify below CLIs and associated JSON format " "outputs:") + + input_dict = { + "e1": { + "cli": [ + "show evpn vni detail", + "show bgp l2vpn evpn all overlay", + "show bgp l2vpn evpn vni" + ] + } + } + + result = verify_cli_json(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_RT_verification_auto_p0(request): + """ + RT verification(auto) + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise overlapping prefixes from VNFs R1 and R2 in all VRFs " + "RED, GREEN and BLUE 100.1.1.1/32 and 100::100/128" + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that Edge-1 receives same prefixes in all 3 VRFs via " + "corresponding next-hop in associated VRF sh bgp vrf all" + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure 4-byte local AS number on Edge-1 and establish EVPN " + "neighborship with DCG-1 & DCG-2." + ) + + topo_local = deepcopy(topo) + + step("Delete BGP config for vrf RED.") + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "no_vni": VNI_1}, + {"name": "BLUE", "no_vni": VNI_2}, + {"name": "GREEN", "no_vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_2 = {} + for dut in ["e1"]: + temp = {dut: {"bgp": []}} + input_dict_2.update(temp) + + INDEX = [0, 1, 2, 3] + VRFS = ["RED", "BLUE", "GREEN", None] + AS_NUM = [100, 100, 100, 100] + + for index, vrf, as_num in zip(INDEX, VRFS, AS_NUM): + topo_local["routers"][dut]["bgp"][index]["local_as"] = 4294967293 + if vrf: + temp[dut]["bgp"].append( + {"local_as": as_num, "vrf": vrf, "delete": True} + ) + else: + temp[dut]["bgp"].append({"local_as": as_num, "delete": True}) + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = create_router_bgp(tgen, topo_local["routers"]) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "vni": VNI_1}, + {"name": "BLUE", "vni": VNI_2}, + {"name": "GREEN", "vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that all overlapping prefixes across different VRFs are " + "advertised in EVPN with unique RD value(auto derived)." + ) + step( + "Verify that FRR uses only the lower 2 bytes of ASN+VNI for auto " + "derived RT value." + ) + + for addr_type in ADDR_TYPES: + input_routes_1 = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + input_routes_2 = { + "r2": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "BLUE"}]} + } + input_routes_3 = { + "r2": { + "static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "GREEN"}] + } + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_1, rd="auto", rd_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_1, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_2, rd="auto", rd_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_2, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_3, rd="auto", rd_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_3, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-1(iBGP peer) automatically imports the prefixes" + " from EVPN address-family to respective VRFs." + ) + step( + "Verify if DCG-2(eBGP peer) automatically imports the prefixes " + "from EVPN address-family to respective VRFs or not." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_rib(tgen, addr_type, "d1", input_routes) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Change the VNI number for all 3 VRFs on Edge-1 as:" + "RED : 75400, GREEN: 75500, BLUE: 75600" + ) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "no_vni": VNI_1}, + {"name": "BLUE", "no_vni": VNI_2}, + {"name": "GREEN", "no_vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "vni": 75400}, + {"name": "BLUE", "vni": 75500}, + {"name": "GREEN", "vni": 75600}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Delete configured vxlan") + dut = "e1" + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + "delete": True, + } + ] + } + } + + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configured vxlan") + VXLAN["vxlan_id"] = [75400, 75500, 75600] + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + } + ] + } + } + + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure bridge interface") + brctl_input = { + dut: { + "brctl": [ + { + "brctl_name": BRCTL["brctl_name"], + "addvxlan": BRCTL["addvxlan"], + "vrf": BRCTL["vrf"], + "stp": BRCTL["stp"], + } + ] + } + } + result = configure_brctl(tgen, topo, brctl_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on Edge-1 that auto derived RT value has changed for " + "each VRF based on VNI number.." + ) + + input_dict = { + "e1": { + "vrfs": [ + {"RED": {"vni": 75400}}, + {"BLUE": {"vni": 75500}}, + {"GREEN": {"vni": 75600}}, + ] + } + } + + result = verify_vrf_vni(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on Edge-1 that auto derived RT value has changed for " + "each VRF based on VNI number." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify on DCG-2 that prefixes are not imported from EVPN " + "address-family to VRFs as RT values are different on sending(" + "edge-1) and receiving(DCG-2) end." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, "Testcase {} :Failed \n " + "Routes are still present: {}".format(tc_name, result) + logger.info("Expected Behavior: {}".format(result)) + + step( + "Revert back to original VNI number for all 3 VRFs on Edge-1 " + "as: RED : 75100, GREEN: 75200, BLUE: 75300" + ) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "no_vni": 75400}, + {"name": "BLUE", "no_vni": 75500}, + {"name": "GREEN", "no_vni": 75600}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "vni": VNI_1}, + {"name": "BLUE", "vni": VNI_2}, + {"name": "GREEN", "vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Delete configured vxlan") + dut = "e1" + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + "delete": True, + } + ] + } + } + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configured vxlan") + VXLAN["vxlan_id"] = [75100, 75200, 75300] + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + } + ] + } + } + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure bridge interface") + brctl_input = { + dut: { + "brctl": [ + { + "brctl_name": BRCTL["brctl_name"], + "addvxlan": BRCTL["addvxlan"], + "vrf": BRCTL["vrf"], + "stp": BRCTL["stp"], + } + ] + } + } + result = configure_brctl(tgen, topo, brctl_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on Edge-1 that auto derived RT value has changed for " + "each VRF based on VNI number." + ) + step( + "Verify that DCG-1(iBGP peer) automatically imports the prefixes" + " from EVPN address-family to respective VRFs." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "d1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Test with smaller VNI numbers (1-75000)") + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "no_vni": VNI_1}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "vni": 111}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that DCG-2 receives EVPN prefixes along with auto " + "derived RT values(based on smaller VNI numbers)" + ) + + for addr_type in ADDR_TYPES: + input_routes_1 = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "d2", input_routes_1, rt="auto", rt_peer="e1", expected=False + ) + assert result is not True, "Testcase {} :Failed \n " + "Malfaromed Auto-RT value accepted: {}".format(tc_name, result) + logger.info("Expected Behavior: {}".format(result)) + + step("Configure VNI number more than boundary limit (16777215)") + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "no_vni": 111}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "vni": 16777215}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("CLI error for malformed VNI.") + input_dict = { + "e1": { + "vrfs": [{"RED": {"vni": 16777215, "routerMac": "None", "state": "Down"}}] + } + } + + result = verify_vrf_vni(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_routes_1 = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "d2", input_routes_1, rt="auto", rt_peer="e1", expected=False + ) + assert result is not True, "Testcase {} :Failed \n " + "Malfaromed Auto-RT value accepted: {}".format(tc_name, result) + logger.info("Expected Behavior: {}".format(result)) + + step("Un-configure VNI number more than boundary limit (16777215)") + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "no_vni": 16777215}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_topo1.py b/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_topo1.py new file mode 100755 index 0000000000..3cdec760f7 --- /dev/null +++ b/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_topo1.py @@ -0,0 +1,2117 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +Following tests are covered to test EVPN-Type5 functionality: + +1. RD verification (manual/auto). +2. RT verification(manual) +3. In an active/standby EVPN implementation, if active DCG goes down, + secondary takes over. +4. EVPN routes are advertised/withdrawn, based on VNFs + advertising/withdrawing IP prefixes. +5. Route-map operations for EVPN address family. +6. BGP attributes for EVPN address-family. +""" + +import os +import re +import sys +import json +import time +import pytest +import platform +from copy import deepcopy +from time import sleep + + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topotest import version_cmp +from lib.topogen import Topogen, get_topogen +from mininet.topo import Topo + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + create_route_maps, + verify_cli_json, + start_router_daemons, + kill_router_daemons, + create_static_routes, + stop_router, + start_router, + create_vrf_cfg, + check_router_status, + apply_raw_config, + configure_vxlan, + configure_brctl, + verify_vrf_vni, + create_interface_in_kernel +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + clear_bgp, + verify_best_path_as_per_bgp_attribute, + verify_attributes_for_evpn_routes, + verify_evpn_routes +) +from lib.topojson import build_topo_from_json, build_config_from_json + +# Reading the data from JSON File for topology creation +jsonFile = "{}/evpn_type5_topo1.json".format(CWD) +try: + with open(jsonFile, "r") as topoJson: + topo = json.load(topoJson) +except IOError: + assert False, "Could not read file {}".format(jsonFile) + +# Global variables +NETWORK1_1 = {"ipv4": "10.1.1.1/32", "ipv6": "10::1/128"} +NETWORK1_2 = {"ipv4": "40.1.1.1/32", "ipv6": "40::1/128"} +NETWORK1_3 = {"ipv4": "40.1.1.2/32", "ipv6": "40::2/128"} +NETWORK1_4 = {"ipv4": "40.1.1.3/32", "ipv6": "40::3/128"} +NETWORK2_1 = {"ipv4": "20.1.1.1/32", "ipv6": "20::1/128"} +NETWORK3_1 = {"ipv4": "30.1.1.1/32", "ipv6": "30::1/128"} +NETWORK4_1 = {"ipv4": "100.1.1.1/32 ", "ipv6": "100::100/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} +VNI_1 = 75100 +VNI_2 = 75200 +VNI_3 = 75300 +MAC_1 = "00:80:48:ba:d1:00" +MAC_2 = "00:80:48:ba:d1:01" +MAC_3 = "00:80:48:ba:d1:02" +BRCTL_1 = "br100" +BRCTL_2 = "br200" +BRCTL_3 = "br300" +VXLAN_1 = "vxlan75100" +VXLAN_2 = "vxlan75200" +VXLAN_3 = "vxlan75300" +BRIDGE_INTF1 = "120.0.0.1" +BRIDGE_INTF2 = "120.0.0.2" +BRIDGE_INTF3 = "120.0.0.3" + +VXLAN = { + "vxlan_name": [VXLAN_1, VXLAN_2, VXLAN_3], + "vxlan_id": [75100, 75200, 75300], + "dstport": 4789, + "local_addr": {"e1": BRIDGE_INTF1, "d1": BRIDGE_INTF2, "d2": BRIDGE_INTF3}, + "learning": "no", +} +BRCTL = { + "brctl_name": [BRCTL_1, BRCTL_2, BRCTL_3], + "addvxlan": [VXLAN_1, VXLAN_2, VXLAN_3], + "vrf": ["RED", "BLUE", "GREEN"], + "stp": [0, 0, 0], +} + + +class CreateTopo(Topo): + """ + Test BasicTopo - topology 1 + + * `Topo`: Topology object + """ + + def build(self, *_args, **_opts): + """Build function""" + tgen = get_topogen(self) + + # Building topology from json file + build_topo_from_json(tgen, topo) + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global topo + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + tgen = Topogen(CreateTopo, mod.__name__) + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start deamons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + if version_cmp(platform.release(), '4.19') < 0: + error_msg = ('EVPN tests will not run (have kernel "{}", ' + 'but it requires >= 4.19)'.format(platform.release())) + pytest.skip(error_msg) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Pre-requisite config for testsuite") + prerequisite_config_for_test_suite(tgen) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def prerequisite_config_for_test_suite(tgen): + """ + API to do prerequisite config for testsuite + + parameters: + ----------- + * `tgen`: topogen object + """ + + step("Configure vxlan, bridge interface") + for dut in ["e1", "d1", "d2"]: + step("[DUT: ]Configure vxlan") + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + } + ] + } + } + + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure bridge interface") + brctl_input = { + dut: { + "brctl": [ + { + "brctl_name": BRCTL["brctl_name"], + "addvxlan": BRCTL["addvxlan"], + "vrf": BRCTL["vrf"], + "stp": BRCTL["stp"], + } + ] + } + } + result = configure_brctl(tgen, topo, brctl_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure default routes") + add_default_routes(tgen) + + +def add_default_routes(tgen): + """ + API to do prerequisite config for testsuite + + parameters: + ----------- + * `tgen`: topogen object + """ + + step("Add default routes..") + + default_routes = { + "e1": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["d1"]), + "next_hop": topo["routers"]["d1"]["links"]["e1-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["d2"]), + "next_hop": topo["routers"]["d2"]["links"]["e1-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + "d1": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["e1"]), + "next_hop": topo["routers"]["e1"]["links"]["d1-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["d2"]), + "next_hop": topo["routers"]["e1"]["links"]["d1-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + "d2": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["d1"]), + "next_hop": topo["routers"]["e1"]["links"]["d2-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["e1"]), + "next_hop": topo["routers"]["e1"]["links"]["d2-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + } + + result = create_static_routes(tgen, default_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + +def test_RD_verification_manual_and_auto_p0(request): + """ + RD verification (manual/auto). + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + step( + "Advertise vrf RED's routes in EVPN address family from Edge-1 router" + ", without manual configuration of RD." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify on DCG-1 and DCG-2:") + step("EVPN route for 10.1.1.1/32 has auto-assigned RD value.") + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rd="auto", rd_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure RD for vrf RED manually as 50.50.50.50:50 and " + "advertise vrf RED's routes in EVPN address family from " + "Edge-1 router." + ) + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": {"l2vpn": {"evpn": {"rd": "50.50.50.50:50"}}}, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("EVPN route for vrf RED has RD value as 50.50.50.50:50") + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rd="50.50.50.50:50" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure RD for vrf RED manually as 100.100.100.100:100 and " + "advertise vrf RED's routes in EVPN address family from Edge-1 " + "router." + ) + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": {"evpn": {"rd": "100.100.100.100:100"}} + } + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "EVPN route for vrf RED is overridden with RD value as " "100.100.100.100:100." + ) + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rd="100.100.100.100:100" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure RD for vrf BLUE manually same as vrf RED " + "(100.100.100.100:100) and advertise vrf RED and BLUE's routes " + "in EVPN address family from Edge-1 router." + ) + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "l2vpn": {"evpn": {"rd": "100.100.100.100:100"}} + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Delete manually configured RD and advertise vrf RED's routes " + "in EVPN address family from Edge-1 router." + ) + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": {"evpn": {"no rd": "100.100.100.100:100"}} + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Configure same RD value for vrf GREEN, as auto generated RD " + "value for vrf RED on Edge-1 router." + ) + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": {"l2vpn": {"evpn": {"rd": "10.0.0.33:1"}}}, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Delete auto configured RD value from vrf RED in EVPN " "address family.") + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": {"l2vpn": {"evpn": {"no rd": "10.0.0.33:1"}}}, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure RD value as 100.100.100:100") + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": {"l2vpn": {"evpn": {"rd": "100.100.100:100"}}}, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_RT_verification_manual_p0(request): + """ + RT verification(manual) + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + step("Advertise VRF routes as in EVPN address family from Edge-1 " "router.") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure RT for vrf RED manually as export 100:100 " + "and advertise vrf RED's routes in EVPN address family" + " from Edge-1 router." + ) + + input_dict_rt = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": {"neighbor": {"r1": {"dest_link": {"e1": {}}}}} + }, + "ipv6": { + "unicast": {"neighbor": {"r1": {"dest_link": {"e1": {}}}}} + }, + "l2vpn": { + "evpn": {"route-target": {"export": [{"value": "100:100"}]}} + }, + } + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on dcg-1 and dcg-2, EVPN route for 10.1.1.1/32" + " and 10::1/128 have RT value as 100:100." + ) + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rt="100:100" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure RT for vrf RED manually as export 500:500 and" + " advertise vrf RED's routes in EVPN address family from" + " e1 router." + ) + + input_dict_rt = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": { + "evpn": {"route-target": {"export": [{"value": "500:500"}]}} + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on dcg-1 and dcg-2, EVPN route for 10.1.1.1/32" + " and 10::1/128 have RT value as 500:500." + ) + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rt=["100:100", "500:500"] + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Import RT value 100:100 and 500:500 in vrf BLUE manually on" + " peer router DCG-1 and DCG-2." + ) + + input_dict_rt = { + "d1": { + "bgp": [ + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "import": [ + {"value": "100:100"}, + {"value": "500:500"}, + ] + } + } + } + }, + } + ] + }, + "d2": { + "bgp": [ + { + "local_as": "200", + "vrf": "BLUE", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "import": [ + {"value": "100:100"}, + {"value": "500:500"}, + ] + } + } + } + }, + } + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "EVPN route for 10.1.1.1/32 and 10::1 should be installed " + "in vrf BLUE on DCG-1 and DCG-2 and further advertised to " + "VNF router." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": { + "static_routes": [{"network": [NETWORK1_1[addr_type]], "vrf": "BLUE"}] + } + } + result = verify_rib(tgen, addr_type, "d1", input_routes) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step( + "Delete import RT value 500:500 in vrf BLUE manually on " + "peer router DCG-1 and DCG-2." + ) + + input_dict_rt = { + "d1": { + "bgp": [ + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "import": [{"value": "500:500", "delete": True}] + } + } + } + }, + } + ] + }, + "d2": { + "bgp": [ + { + "local_as": "200", + "vrf": "BLUE", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "import": [{"value": "500:500", "delete": True}] + } + } + } + }, + } + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rt=["100:100", "500:500"] + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Delete RT export value 100:100 for vrf RED on Edge-1") + + input_dict_rt = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "export": [{"value": "100:100", "delete": True}] + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "EVPN route for 10.1.1.1/32 and 10::1 should be withdrawn " + "from vrf BLUE on DCG-1,DCG-2 and VNF router." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": { + "static_routes": [{"network": [NETWORK1_1[addr_type]], "vrf": "BLUE"}] + } + } + result = verify_rib(tgen, addr_type, "d1", input_routes, expected=False) + assert result is not True, ( + "Testcase {} :Failed \n Expected Behavior: Routes are still " + "present \n Error: {}".format(tc_name, result) + ) + logger.info("Expected Behavior: {}".format(result)) + + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, ( + "Testcase {} :Failed \n Expected Behavior: Routes are still " + "present \n Error: {}".format(tc_name, result) + ) + logger.info("Expected Behavior: {}".format(result)) + + step( + "Configure RT value as 100:100000010000010000101010 to check " + "the boundary value." + ) + + input_dict_rt = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "export": [ + {"value": "100:100000010000010000101010"} + ] + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "CLI error: RT value: 100:100000010000010000101010 should not " "be configured" + ) + + dut = "e1" + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rt="100:100000010000010000101010", expected=False + ) + assert result is not True, ( + "Testcase {} :Failed \n Expected Behavior: RT value of out" + " of boundary \n Error: {}".format(tc_name, result) + ) + logger.info("Expected Behavior: {}".format(result)) + + write_test_footer(tc_name) + + +def test_active_standby_evpn_implementation_p1(request): + """ + In an active/standby EVPN implementation, if active DCG goes down, + secondary takes over. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Taken care in base config: Configure BGP neighborship for both " + "address families(IPv4 & IPv6) between DCG-1/DCG-2 and VFN routers" + "(R3 and R4)." + ) + + step( + "BGP neighborships come up within defined VRFs. Please use below " + "command: sh bgp vrf all summary" + ) + + result = verify_bgp_convergence(tgen, topo, "d1") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = verify_bgp_convergence(tgen, topo, "d2") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Advertise prefixes from VNF routers R3 and R4 in associated " + "VRFs for both address-families." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Redistribute static in (IPv4 and IPv6) address-family " + "on Edge-1 for all VRFs." + ) + + input_dict_2 = {} + for dut in ["r3", "r4"]: + temp = {dut: {"bgp": []}} + input_dict_2.update(temp) + + if dut == "r3": + VRFS = ["RED"] + AS_NUM = [3] + if dut == "r4": + VRFS = ["BLUE", "GREEN"] + AS_NUM = [4, 4] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Prefixes are received in respective VRFs on DCG-1/DCG-2.") + + for addr_type in ADDR_TYPES: + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_rib(tgen, addr_type, "d1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Taken care in base config: Advertise VRF routes in EVPN " + "address-family from DCG-1 and DCG-2 router." + ) + + step("Verify on Edge-1 that EVPN routes are installed via next-hop " "as DCG-2.") + + for addr_type in ADDR_TYPES: + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + if addr_type == "ipv4": + result = verify_rib( + tgen, addr_type, "e1", input_routes, next_hop=BRIDGE_INTF2 + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + else: + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure 'next-hop self' on DCG-1 for peer Edge-1 in EVPN " "address-family." + ) + + input_dict_3 = { + "d1": { + "bgp": [ + { + "local_as": "100", + "address_family": { + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4": {"d1-link1": {"next_hop_self": True}} + } + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + logger.info( + "Creating route-map so ipv6 glpbal ip wpuld be preferred " "as next-hop" + ) + + step( + "Verify on Edge-1 that EVPN routes are now preferred via " + "next-hop as DCG-1(iBGP) due to shortest AS-Path." + ) + + for addr_type in ADDR_TYPES: + + logger.info("Verifying only ipv4 routes") + if addr_type != "ipv4": + continue + + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + next_hop = topo["routers"]["d1"]["links"]["e1-link1"]["ipv4"].split("/")[0] + + result = verify_rib(tgen, addr_type, "e1", input_routes, next_hop=next_hop) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_evpn_routes_from_VNFs_p1(request): + """ + EVPN routes are advertised/withdrawn, based on VNFs + advertising/withdrawing IP prefixes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Taken care in base config: Advertise VNFs'(R1 and R2) " + "originated routes in EVPN address-family from Edge-1 to " + "DCG-1 and DCG-2 routers." + ) + step( + "Taken care in base config: Advertise IPv4 and IPv6 routes " + "from default vrf in EVPN address-family from Edge-1." + ) + + step( + "Verify on DCG-2 that VNF routes are received in respective " + "VRFs along with auto derived RD/RT values 'show bgp l2vpn evpn'" + ) + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_evpn_routes(tgen, topo, dut, input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_evpn_routes(tgen, topo, dut, input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify on R3 and R4 that DCG-2 further advertises all EVPN " + "routes to corresponding VRFs." + ) + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "r3", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_rib(tgen, addr_type, "r4", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-2 receives EVPN routes associated to default " + "VRF and install in default IP routing table as well." + ) + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Withdraw the IP prefixes from VFN(R1).") + dut = "r1" + input_dict_2 = {} + static_routes = topo["routers"][dut]["static_routes"] + for static_route in static_routes: + static_route["delete"] = True + temp = {dut: {"static_routes": [static_route]}} + input_dict_2.update(temp) + + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-2 removes EVPN routes corresponding to vrf RED and " + "send an withdraw to VNF(R3) as well." + ) + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, "Testcase {} :Failed \n " + "Routes are still present: {}".format(tc_name, result) + logger.info("Expected Behavior: {}".format(result)) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "r3", input_routes, expected=False) + assert result is not True, "Testcase {} :Failed \n " + "Routes are still present: {}".format(tc_name, result) + logger.info("Expected Behavior: {}".format(result)) + + step("Re-advertise IP prefixes from VFN(R1).") + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-2 receives EVPN routes corresponding to vrf RED " + "again and send an update to VNF(R3) as well." + ) + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "r3", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Delete vrf BLUE from router Edge-1") + input_dict_3 = {"e1": {"vrfs": [{"name": "BLUE", "id": "2", "delete": True}]}} + + result = create_vrf_cfg(tgen, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that DCG-2 removes EVPN routes corresponding to " + "vrf BLUE and send an withdraw to VNF(R4) as well." + ) + for addr_type in ADDR_TYPES: + input_routes = { + "r2": {"static_routes": [{"network": NETWORK2_1[addr_type], "vrf": "BLUE"}]} + } + + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, "Testcase {} :Failed \n " + "Routes are still present: {}".format(tc_name, result) + logger.info("Expected Behavior: {}".format(result)) + + result = verify_rib(tgen, addr_type, "r4", input_routes, expected=False) + assert result is not True, "Testcase {} :Failed \n " + "Routes are still present: {}".format(tc_name, result) + logger.info("Expected Behavior: {}".format(result)) + + step("Add vrf BLUE on router Edge-1 again.") + interface = topo["routers"]["e1"]["links"]["r2-link1"]["interface"] + input_dict_3 = { + "e1": { + "links": { + "r2-link1": { + "interface": interface, + "ipv4": "auto", + "ipv6": "auto", + "vrf": "BLUE", + } + }, + "vrfs": [{"name": "BLUE", "id": "2"}], + } + } + result = create_vrf_cfg(tgen, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + logger.info( + "After deleting VRFs ipv6 addresses wil be deleted " + "from kernel Adding back ipv6 addresses" + ) + dut = "e1" + vrfs = ["BLUE"] + + for vrf in vrfs: + for c_link, c_data in topo["routers"][dut]["links"].items(): + if "vrf" in c_data: + if c_data["vrf"] != vrf: + continue + + intf_name = c_data["interface"] + intf_ipv6 = c_data["ipv6"] + + create_interface_in_kernel( + tgen, dut, intf_name, intf_ipv6, vrf, create=False + ) + + logger.info("Wait for 60 sec.") + sleep(60) + + step( + "Verify that DCG-2 receives EVPN routes corresponding to " + "vrf BLUE again and send an update to VNF(R4) as well." + ) + for addr_type in ADDR_TYPES: + input_routes = { + "r2": {"static_routes": [{"network": NETWORK2_1[addr_type], "vrf": "BLUE"}]} + } + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r4", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Withdraw IPv6 address-family in EVPN advertisements for " "VRF GREEN") + addr_type = "ipv6" + input_dict_4 = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "l2vpn": { + "evpn": { + "advertise": {addr_type: {"unicast": {"delete": True}}} + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that EVPN routes (IPv6)associated with vrf GREEN are " + "withdrawn from DCG-2 and VNF R4." + ) + input_routes = { + "r2": {"static_routes": [{"network": NETWORK3_1[addr_type], "vrf": "GREEN"}]} + } + + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, "Testcase {} :Failed \n " + "Routes are still present: {}".format(tc_name, result) + logger.info("Expected Behavior: {}".format(result)) + + result = verify_rib(tgen, addr_type, "r4", input_routes, expected=False) + assert result is not True, "Testcase {} :Failed \n " + "Routes are still present: {}".format(tc_name, result) + logger.info("Expected Behavior: {}".format(result)) + + step("Advertise IPv6 address-family in EVPN advertisements " "for VRF GREEN.") + addr_type = "ipv6" + input_dict_4 = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "l2vpn": {"evpn": {"advertise": {addr_type: {"unicast": {}}}}} + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_routes = { + "r2": { + "static_routes": [{"network": NETWORK3_1[addr_type], "vrf": "GREEN"}] + } + } + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r4", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize( + "attribute", [{"route-type": "prefix"}, {"vni": VNI_1}, {"rt": "300:300"}] +) +def test_route_map_operations_for_evpn_address_family_p1(request, attribute): + """ + Route-map operations for EVPN address family. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise VRF routes in EVPN address family from Edge-1 router." + " Configure a route-map on e1 to filter EVPN routes based on" + " below keywords: route-type: prefix" + ) + + for key, value in attribute.items(): + if key == "rt": + logger.info("Creating extcommunity using raw_config") + raw_config = { + "d2": { + "raw_config": [ + "bgp extcommunity-list standard ECOMM300 permit {} {}".format( + key, value + ) + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format( + tc_name, result + ) + + input_dict_1 = { + "e1": { + "route_maps": { + "rmap_route_type": [ + {"action": "permit", "set": {"extcommunity": {key: value}}} + ] + } + }, + "d2": { + "route_maps": { + "rmap_route_type": [ + {"action": "permit", "match": {"extcommunity": "ECOMM300"}} + ] + } + }, + } + + else: + input_dict_1 = { + "e1": { + "route_maps": { + "rmap_route_type": [ + {"action": "permit", "match": {"evpn": {key: value}}} + ] + } + }, + "d2": { + "route_maps": { + "rmap_route_type": [ + {"action": "permit", "match": {"evpn": {key: value}}} + ] + } + }, + } + result = create_route_maps(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + "e1": { + "bgp": [ + { + "local_as": "100", + "address_family": { + "l2vpn": { + "evpn": { + "neighbor": { + "d2": { + "ipv4": { + "e1-link1": { + "route_maps": [ + { + "name": "rmap_route_type", + "direction": "out", + } + ] + } + } + } + } + } + } + }, + } + ] + }, + "d2": { + "bgp": [ + { + "local_as": "200", + "address_family": { + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4": { + "d2-link1": { + "route_maps": [ + { + "name": "rmap_route_type", + "direction": "in", + } + ] + } + } + } + } + } + } + }, + } + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on router DCG-2 that EVPN routes corresponding to all " + "VRFs are received. As all EVPN routes are type-5 only." + ) + + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_evpn_routes(tgen, topo, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_evpn_routes(tgen, topo, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize("attribute", ["locPrf", "weight", "path"]) +def test_bgp_attributes_for_evpn_address_family_p1(request, attribute): + """ + BGP attributes for EVPN address-family. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + topo_local = deepcopy(topo) + + logger.info("Modifying topology b/w e1 and d1 from iBGP to eBGP") + step("Delete BGP config for vrf RED.") + + if attribute == "locPrf": + input_dict_vni = { + "d1": { + "vrfs": [ + {"name": "RED", "no_vni": VNI_1}, + {"name": "BLUE", "no_vni": VNI_2}, + {"name": "GREEN", "no_vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = {} + for dut in ["d1"]: + temp = {dut: {"bgp": []}} + input_dict_2.update(temp) + + INDEX = [0, 1, 2, 3] + VRFS = ["RED", "BLUE", "GREEN", None] + AS_NUM = [100, 100, 100, 100] + + for index, vrf, as_num in zip(INDEX, VRFS, AS_NUM): + topo_local["routers"][dut]["bgp"][index]["local_as"] = 200 + if vrf: + temp[dut]["bgp"].append( + {"local_as": as_num, "vrf": vrf, "delete": True} + ) + else: + temp[dut]["bgp"].append({"local_as": as_num, "delete": True}) + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = create_router_bgp(tgen, topo_local["routers"]) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Advertise VRF routes in EVPN address-family from DCG-1 " "and DCG-2 routers.") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Redistribute static in (IPv4 and IPv6) address-family " + "on Edge-1 for all VRFs." + ) + + input_dict_2 = {} + for dut in ["r3", "r4"]: + temp = {dut: {"bgp": []}} + input_dict_2.update(temp) + + if dut == "r3": + VRFS = ["RED"] + AS_NUM = [3] + if dut == "r4": + VRFS = ["BLUE", "GREEN"] + AS_NUM = [4, 4] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on router Edge-1 that EVPN routes corresponding to " + "all VRFs are received from both routers DCG-1 and DCG-2" + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure a route-map on Edge-1 to modify below BGP attributes " + "for EVPN address-family:" + ) + + if attribute == "path": + input_dict_1 = { + "e1": { + "route_maps": { + "rmap_d1".format(addr_type): [ + { + "action": "permit", + "set": { + attribute: { + "as_num": "123 231 321", + "as_action": "prepend", + } + }, + } + ], + "rmap_d2".format(addr_type): [ + { + "action": "permit", + "set": { + attribute: {"as_num": "121", "as_action": "prepend"} + }, + } + ], + } + } + } + else: + input_dict_1 = { + "e1": { + "route_maps": { + "rmap_d1".format(addr_type): [ + {"action": "permit", "set": {attribute: 120}} + ], + "rmap_d2".format(addr_type): [ + {"action": "permit", "set": {attribute: 150}} + ], + } + } + } + result = create_route_maps(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_2 = { + "e1": { + "bgp": [ + { + "local_as": "100", + "address_family": { + "l2vpn": { + "evpn": { + "neighbor": { + "d1": { + "ipv4": { + "e1-link1": { + "route_maps": [ + { + "name": "rmap_d1", + "direction": "in", + } + ] + } + } + }, + "d2": { + "ipv4": { + "e1-link1": { + "route_maps": [ + { + "name": "rmap_d2", + "direction": "in", + } + ] + } + } + }, + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on router Edge-1 that EVPN routes are preferred via" + " DCG-1 or DCG-2 based on best path selection criteria " + "(according to the configured BGP attribute values in route-map)." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, "e1", input_routes, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/lib/bgp.py b/tests/topotests/lib/bgp.py index 971bbd0f3b..7b1eead944 100644 --- a/tests/topotests/lib/bgp.py +++ b/tests/topotests/lib/bgp.py @@ -27,6 +27,8 @@ import sys from lib import topotest from lib.topolog import logger +from lib.topogen import TopoRouter, get_topogen + # Import common_config to use commomnly used APIs from lib.common_config import ( create_common_configuration, @@ -166,6 +168,7 @@ def create_router_bgp(tgen, topo, input_dict=None, build=False, load_config=True ipv4_data = bgp_addr_data.setdefault("ipv4", {}) ipv6_data = bgp_addr_data.setdefault("ipv6", {}) + l2vpn_data = bgp_addr_data.setdefault("l2vpn", {}) neigh_unicast = ( True @@ -174,6 +177,8 @@ def create_router_bgp(tgen, topo, input_dict=None, build=False, load_config=True else False ) + l2vpn_evpn = True if l2vpn_data.setdefault("evpn", {}) else False + if neigh_unicast: data_all_bgp = __create_bgp_unicast_neighbor( tgen, @@ -184,6 +189,11 @@ def create_router_bgp(tgen, topo, input_dict=None, build=False, load_config=True config_data=data_all_bgp, ) + if l2vpn_evpn: + data_all_bgp = __create_l2vpn_evpn_address_family( + tgen, topo, bgp_data, router, config_data=data_all_bgp + ) + try: result = create_common_configuration( tgen, router, data_all_bgp, "bgp", build, load_config @@ -467,6 +477,166 @@ def __create_bgp_unicast_neighbor( return config_data +def __create_l2vpn_evpn_address_family( + tgen, topo, input_dict, router, config_data=None +): + """ + Helper API to create configuration for l2vpn evpn address-family + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `input_dict` : Input dict data, required when configuring + from testcase + * `router` : router id to be configured. + * `build` : Only for initial setup phase this is set as True. + """ + + result = False + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + bgp_data = input_dict["address_family"] + + for family_type, family_dict in bgp_data.iteritems(): + if family_type != "l2vpn": + continue + + family_data = family_dict["evpn"] + if family_data: + config_data.append("address-family l2vpn evpn") + + advertise_data = family_data.setdefault("advertise", {}) + neighbor_data = family_data.setdefault("neighbor", {}) + advertise_all_vni_data = family_data.setdefault("advertise-all-vni", None) + rd_data = family_data.setdefault("rd", None) + no_rd_data = family_data.setdefault("no rd", False) + route_target_data = family_data.setdefault("route-target", {}) + + if advertise_data: + for address_type, unicast_type in advertise_data.items(): + + if isinstance(unicast_type, dict): + for key, value in unicast_type.items(): + cmd = "advertise {} {}".format(address_type, key) + + if value: + route_map = value.setdefault("route-map", {}) + advertise_del_action = value.setdefault("delete", None) + + if route_map: + cmd = "{} route-map {}".format(cmd, route_map) + + if advertise_del_action: + cmd = "no {}".format(cmd) + + config_data.append(cmd) + + if neighbor_data: + for neighbor, neighbor_data in neighbor_data.items(): + ipv4_neighbor = neighbor_data.setdefault("ipv4", {}) + ipv6_neighbor = neighbor_data.setdefault("ipv6", {}) + + if ipv4_neighbor: + for neighbor_name, action in ipv4_neighbor.items(): + neighbor_ip = topo[neighbor]["links"][neighbor_name][ + "ipv4" + ].split("/")[0] + + if isinstance(action, dict): + next_hop_self = action.setdefault("next_hop_self", None) + route_maps = action.setdefault("route_maps", {}) + + if next_hop_self is not None: + if next_hop_self is True: + config_data.append( + "neighbor {} " + "next-hop-self".format(neighbor_ip) + ) + elif next_hop_self is False: + config_data.append( + "no neighbor {} " + "next-hop-self".format(neighbor_ip) + ) + + if route_maps: + for route_map in route_maps: + name = route_map.setdefault("name", {}) + direction = route_map.setdefault("direction", "in") + del_action = route_map.setdefault("delete", False) + + if not name: + logger.info( + "Router %s: 'name' " + "not present in " + "input_dict for BGP " + "neighbor route name", + router, + ) + else: + cmd = "neighbor {} route-map {} " "{}".format( + neighbor_ip, name, direction + ) + + if del_action: + cmd = "no {}".format(cmd) + + config_data.append(cmd) + + else: + if action == "activate": + cmd = "neighbor {} activate".format(neighbor_ip) + elif action == "deactivate": + cmd = "no neighbor {} activate".format(neighbor_ip) + + config_data.append(cmd) + + if ipv6_neighbor: + for neighbor_name, action in ipv4_neighbor.items(): + neighbor_ip = topo[neighbor]["links"][neighbor_name][ + "ipv6" + ].split("/")[0] + if action == "activate": + cmd = "neighbor {} activate".format(neighbor_ip) + elif action == "deactivate": + cmd = "no neighbor {} activate".format(neighbor_ip) + + config_data.append(cmd) + + if advertise_all_vni_data == True: + cmd = "advertise-all-vni" + config_data.append(cmd) + elif advertise_all_vni_data == False: + cmd = "no advertise-all-vni" + config_data.append(cmd) + + if rd_data: + cmd = "rd {}".format(rd_data) + config_data.append(cmd) + + if no_rd_data: + cmd = "no rd {}".format(no_rd_data) + config_data.append(cmd) + + if route_target_data: + for rt_type, rt_dict in route_target_data.items(): + for _rt_dict in rt_dict: + rt_value = _rt_dict.setdefault("value", None) + del_rt = _rt_dict.setdefault("delete", None) + + if rt_value: + cmd = "route-target {} {}".format(rt_type, rt_value) + if del_rt: + cmd = "no {}".format(cmd) + + config_data.append(cmd) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return config_data + + def __create_bgp_neighbor(topo, input_dict, router, addr_type, add_neigh=True): """ Helper API to create neighbor specific configuration @@ -489,7 +659,7 @@ def __create_bgp_neighbor(topo, input_dict, router, addr_type, add_neigh=True): for dest_link, peer in peer_dict["dest_link"].iteritems(): nh_details = topo[name] - if "vrfs" in topo[router]: + if "vrfs" in topo[router] or type(nh_details["bgp"]) is list: remote_as = nh_details["bgp"][0]["local_as"] else: remote_as = nh_details["bgp"]["local_as"] @@ -887,19 +1057,16 @@ def verify_bgp_convergence(tgen, topo, dut=None): API will verify if BGP is converged with in the given time frame. Running "show bgp summary json" command and verify bgp neighbor state is established, - Parameters ---------- * `tgen`: topogen object * `topo`: input json file data * `dut`: device under test - Usage ----- # To veriry is BGP is converged for all the routers used in topology results = verify_bgp_convergence(tgen, topo, dut="r1") - Returns ------- errormsg(str) or True @@ -3463,3 +3630,552 @@ def verify_gr_address_family(tgen, topo, addr_type, addr_family, dut): return errormsg logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + +@retry(attempts=5, wait=2, return_is_str=True, initial_wait=2) +def verify_attributes_for_evpn_routes( + tgen, + topo, + dut, + input_dict, + rd=None, + rt=None, + ethTag=None, + ipLen=None, + rd_peer=None, + rt_peer=None, +): + """ + API to verify rd and rt value using "sh bgp l2vpn evpn 10.1.1.1" + command. + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : device under test + * `input_dict`: having details like - for which route, rd value + needs to be verified + * `rd` : route distinguisher + * `rt` : route target + * `ethTag` : Ethernet Tag + * `ipLen` : IP prefix length + * `rd_peer` : Peer name from which RD will be auto-generated + * `rt_peer` : Peer name from which RT will be auto-generated + + Usage + ----- + input_dict_1 = { + "r1": { + "static_routes": [{ + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED" + }] + } + } + + result = verify_attributes_for_evpn_routes(tgen, topo, + input_dict, rd = "10.0.0.33:1") + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + for router in input_dict.keys(): + rnode = tgen.routers()[dut] + + if "static_routes" in input_dict[router]: + for static_route in input_dict[router]["static_routes"]: + network = static_route["network"] + + if "vrf" in static_route: + vrf = static_route["vrf"] + + if type(network) is not list: + network = [network] + + for route in network: + route = route.split("/")[0] + _addr_type = validate_ip_address(route) + if "v4" in _addr_type: + input_afi = "v4" + elif "v6" in _addr_type: + input_afi = "v6" + + cmd = "show bgp l2vpn evpn {} json".format(route) + evpn_rd_value_json = run_frr_cmd(rnode, cmd, isjson=True) + if not bool(evpn_rd_value_json): + errormsg = "No output for '{}' cli".format(cmd) + return errormsg + + if rd is not None and rd != "auto": + logger.info( + "[DUT: %s]: Verifying rd value for " "evpn route %s:", + dut, + route, + ) + + if rd in evpn_rd_value_json: + rd_value_json = evpn_rd_value_json[rd] + if rd_value_json["rd"] != rd: + errormsg = ( + "[DUT: %s] Failed: Verifying" + " RD value for EVPN route: %s" + "[FAILED]!!, EXPECTED : %s " + " FOUND : %s" + % (dut, route, rd, rd_value_json["rd"]) + ) + return errormsg + + else: + logger.info( + "[DUT %s]: Verifying RD value for" + " EVPN route: %s [PASSED]|| " + "Found Exprected: %s", + dut, + route, + rd, + ) + return True + + else: + errormsg = ( + "[DUT: %s] RD : %s is not present" + " in cli json output" % (dut, rd) + ) + return errormsg + + if rd == "auto": + logger.info( + "[DUT: %s]: Verifying auto-rd value for " "evpn route %s:", + dut, + route, + ) + + if rd_peer: + index = 1 + vni_dict = {} + + rnode = tgen.routers()[rd_peer] + vrfs = topo["routers"][rd_peer]["vrfs"] + for vrf_dict in vrfs: + vni_dict[vrf_dict["name"]] = index + index += 1 + + show_bgp_json = run_frr_cmd( + rnode, "show bgp vrf all summary json", isjson=True + ) + + # Verifying output dictionary show_bgp_json is empty + if not bool(show_bgp_json): + errormsg = "BGP is not running" + return errormsg + + show_bgp_json_vrf = show_bgp_json[vrf] + for afi, afi_data in show_bgp_json_vrf.items(): + if input_afi not in afi: + continue + router_id = afi_data["routerId"] + + rd = "{}:{}".format(router_id, vni_dict[vrf]) + if rd in evpn_rd_value_json: + rd_value_json = evpn_rd_value_json[rd] + if rd_value_json["rd"] != rd: + errormsg = ( + "[DUT: %s] Failed: Verifying" + " RD value for EVPN route: %s" + "[FAILED]!!, EXPECTED : %s " + " FOUND : %s" + % (dut, route, rd, rd_value_json["rd"]) + ) + return errormsg + + else: + logger.info( + "[DUT %s]: Verifying RD value for" + " EVPN route: %s [PASSED]|| " + "Found Exprected: %s", + dut, + route, + rd, + ) + return True + + else: + errormsg = ( + "[DUT: %s] RD : %s is not present" + " in cli json output" % (dut, rd) + ) + return errormsg + + if rt == "auto": + logger.info( + "[DUT: %s]: Verifying auto-rt value for " "evpn route %s:", + dut, + route, + ) + + if rt_peer: + vni_dict = {} + + rnode = tgen.routers()[rt_peer] + show_bgp_json = run_frr_cmd( + rnode, "show bgp vrf all summary json", isjson=True + ) + + # Verifying output dictionary show_bgp_json is empty + if not bool(show_bgp_json): + errormsg = "BGP is not running" + return errormsg + + show_bgp_json_vrf = show_bgp_json[vrf] + for afi, afi_data in show_bgp_json_vrf.items(): + if input_afi not in afi: + continue + as_num = afi_data["as"] + + show_vrf_vni_json = run_frr_cmd( + rnode, "show vrf vni json", isjson=True + ) + + vrfs = show_vrf_vni_json["vrfs"] + for vrf_dict in vrfs: + if vrf_dict["vrf"] == vrf: + vni_dict[vrf_dict["vrf"]] = str(vrf_dict["vni"]) + + # If AS is 4 byte, FRR uses only the lower 2 bytes of ASN+VNI + # for auto derived RT value. + if as_num > 65535: + as_bin = bin(as_num) + as_bin = as_bin[-16:] + as_num = int(as_bin, 2) + + rt = "{}:{}".format(str(as_num), vni_dict[vrf]) + for _rd, route_data in evpn_rd_value_json.items(): + if route_data["ip"] == route: + for rt_data in route_data["paths"]: + if vni_dict[vrf] == rt_data["VNI"]: + rt_string = rt_data["extendedCommunity"][ + "string" + ] + rt_input = "RT:{}".format(rt) + if rt_input not in rt_string: + errormsg = ( + "[DUT: %s] Failed:" + " Verifying RT " + "value for EVPN " + " route: %s" + "[FAILED]!!," + " EXPECTED : %s " + " FOUND : %s" + % (dut, route, rt_input, rt_string) + ) + return errormsg + + else: + logger.info( + "[DUT %s]: Verifying " + "RT value for EVPN " + "route: %s [PASSED]||" + "Found Exprected: %s", + dut, + route, + rt_input, + ) + return True + + else: + errormsg = ( + "[DUT: %s] Route : %s is not" + " present in cli json output" % (dut, route) + ) + return errormsg + + if rt is not None and rt != "auto": + logger.info( + "[DUT: %s]: Verifying rt value for " "evpn route %s:", + dut, + route, + ) + + if type(rt) is not list: + rt = [rt] + + for _rt in rt: + for _rd, route_data in evpn_rd_value_json.items(): + if route_data["ip"] == route: + for rt_data in route_data["paths"]: + rt_string = rt_data["extendedCommunity"][ + "string" + ] + rt_input = "RT:{}".format(_rt) + if rt_input not in rt_string: + errormsg = ( + "[DUT: %s] Failed: " + "Verifying RT value " + "for EVPN route: %s" + "[FAILED]!!," + " EXPECTED : %s " + " FOUND : %s" + % (dut, route, rt_input, rt_string) + ) + return errormsg + + else: + logger.info( + "[DUT %s]: Verifying RT" + " value for EVPN route:" + " %s [PASSED]|| " + "Found Exprected: %s", + dut, + route, + rt_input, + ) + return True + + else: + errormsg = ( + "[DUT: %s] Route : %s is not" + " present in cli json output" % (dut, route) + ) + return errormsg + + if ethTag is not None: + logger.info( + "[DUT: %s]: Verifying ethTag value for " "evpn route :", dut + ) + + for _rd, route_data in evpn_rd_value_json.items(): + if route_data["ip"] == route: + if route_data["ethTag"] != ethTag: + errormsg = ( + "[DUT: %s] RD: %s, Failed: " + "Verifying ethTag value " + "for EVPN route: %s" + "[FAILED]!!," + " EXPECTED : %s " + " FOUND : %s" + % ( + dut, + _rd, + route, + ethTag, + route_data["ethTag"], + ) + ) + return errormsg + + else: + logger.info( + "[DUT %s]: RD: %s, Verifying " + "ethTag value for EVPN route:" + " %s [PASSED]|| " + "Found Exprected: %s", + dut, + _rd, + route, + ethTag, + ) + return True + + else: + errormsg = ( + "[DUT: %s] RD: %s, Route : %s " + "is not present in cli json " + "output" % (dut, _rd, route) + ) + return errormsg + + if ipLen is not None: + logger.info( + "[DUT: %s]: Verifying ipLen value for " "evpn route :", dut + ) + + for _rd, route_data in evpn_rd_value_json.items(): + if route_data["ip"] == route: + if route_data["ipLen"] != int(ipLen): + errormsg = ( + "[DUT: %s] RD: %s, Failed: " + "Verifying ipLen value " + "for EVPN route: %s" + "[FAILED]!!," + " EXPECTED : %s " + " FOUND : %s" + % (dut, _rd, route, ipLen, route_data["ipLen"]) + ) + return errormsg + + else: + logger.info( + "[DUT %s]: RD: %s, Verifying " + "ipLen value for EVPN route:" + " %s [PASSED]|| " + "Found Exprected: %s", + dut, + _rd, + route, + ipLen, + ) + return True + + else: + errormsg = ( + "[DUT: %s] RD: %s, Route : %s " + "is not present in cli json " + "output " % (dut, route) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return False + + +@retry(attempts=5, wait=2, return_is_str=True, initial_wait=2) +def verify_evpn_routes( + tgen, topo, dut, input_dict, routeType=5, EthTag=0, next_hop=None +): + """ + API to verify evpn routes using "sh bgp l2vpn evpn" + command. + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : device under test + * `input_dict`: having details like - for which route, rd value + needs to be verified + * `route_type` : Route type 5 is supported as of now + * `EthTag` : Ethernet tag, by-default is 0 + * `next_hop` : Prefered nexthop for the evpn routes + + Usage + ----- + input_dict_1 = { + "r1": { + "static_routes": [{ + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED" + }] + } + } + result = verify_evpn_routes(tgen, topo, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for router in input_dict.keys(): + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying evpn routes: ", dut) + + if "static_routes" in input_dict[router]: + for static_route in input_dict[router]["static_routes"]: + network = static_route["network"] + + if type(network) is not list: + network = [network] + + missing_routes = {} + for route in network: + rd_keys = 0 + ip_len = route.split("/")[1] + route = route.split("/")[0] + + prefix = "[{}]:[{}]:[{}]:[{}]".format( + routeType, EthTag, ip_len, route + ) + + cmd = "show bgp l2vpn evpn route json" + evpn_value_json = run_frr_cmd(rnode, cmd, isjson=True) + + if not bool(evpn_value_json): + errormsg = "No output for '{}' cli".format(cmd) + return errormsg + + if evpn_value_json["numPrefix"] == 0: + errormsg = "[DUT: %s]: No EVPN prefixes exist" % (dut) + return errormsg + + for key, route_data_json in evpn_value_json.items(): + if isinstance(route_data_json, dict): + rd_keys += 1 + if prefix not in route_data_json: + missing_routes[key] = prefix + + if rd_keys == len(missing_routes.keys()): + errormsg = ( + "[DUT: %s]: " + "Missing EVPN routes: " + "%s [FAILED]!!" % (dut, list(set(missing_routes.values()))) + ) + return errormsg + + for key, route_data_json in evpn_value_json.items(): + if isinstance(route_data_json, dict): + if prefix not in route_data_json: + continue + + for paths in route_data_json[prefix]["paths"]: + for path in paths: + if path["routeType"] != routeType: + errormsg = ( + "[DUT: %s]: " + "Verifying routeType " + "for EVPN route: %s " + "[FAILED]!! " + "Expected: %s, " + "Found: %s" + % ( + dut, + prefix, + routeType, + path["routeType"], + ) + ) + return errormsg + + elif next_hop: + for nh_dict in path["nexthops"]: + if nh_dict["ip"] != next_hop: + errormsg = ( + "[DUT: %s]: " + "Verifying " + "nexthop for " + "EVPN route: %s" + "[FAILED]!! " + "Expected: %s," + " Found: %s" + % ( + dut, + prefix, + next_hop, + nh_dict["ip"], + ) + ) + return errormsg + + else: + logger.info( + "[DUT %s]: Verifying " + "EVPN route : %s, " + "routeType: %s is " + "installed " + "[PASSED]|| ", + dut, + prefix, + routeType, + ) + return True + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return False diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py index d72d0aa223..156a5f7ea4 100644 --- a/tests/topotests/lib/common_config.py +++ b/tests/topotests/lib/common_config.py @@ -933,6 +933,16 @@ def create_vrf_cfg(tgen, topo, input_dict=None, build=False): ) rnode.run(cmd) + if vni: + config_data.append("vrf {}".format(vrf["name"])) + cmd = "vni {}".format(vni) + config_data.append(cmd) + + if del_vni: + config_data.append("vrf {}".format(vrf["name"])) + cmd = "no vni {}".format(del_vni) + config_data.append(cmd) + result = create_common_configuration( tgen, c_router, config_data, "vrf", build=build ) @@ -984,6 +994,34 @@ def create_interface_in_kernel( rnode.run(cmd) +def shutdown_bringup_interface_in_kernel(tgen, dut, intf_name, ifaceaction=False): + """ + Cretae interfaces in kernel for ipv4/ipv6 + Config is done in Linux Kernel: + + Parameters + ---------- + * `tgen` : Topogen object + * `dut` : Device for which interfaces to be added + * `intf_name` : interface name + * `ifaceaction` : False to shutdown and True to bringup the + ineterface + """ + + rnode = tgen.routers()[dut] + + cmd = "ip link set dev" + if ifaceaction: + action = "up" + cmd = "{} {} {}".format(cmd, intf_name, action) + else: + action = "down" + cmd = "{} {} {}".format(cmd, intf_name, action) + + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + rnode.run(cmd) + + def validate_ip_address(ip_address): """ Validates the type of ip address @@ -1042,7 +1080,7 @@ def check_address_types(addr_type=None): return addr_types if addr_type not in addr_types: - logger.error( + logger.debug( "{} not in supported/configured address types {}".format( addr_type, addr_types ) @@ -1732,6 +1770,7 @@ def create_route_maps(tgen, input_dict, build=False): set_action = set_data.setdefault("set_action", None) nexthop = set_data.setdefault("nexthop", None) origin = set_data.setdefault("origin", None) + ext_comm_list = set_data.setdefault("extcommunity", {}) # Local Preference if local_preference: @@ -1796,6 +1835,19 @@ def create_route_maps(tgen, input_dict, build=False): logger.error("In large_comm_list 'id' not" " provided") return False + if ext_comm_list: + rt = ext_comm_list.setdefault("rt", None) + del_comm = ext_comm_list.setdefault("delete", None) + if rt: + cmd = "set extcommunity rt {}".format(rt) + if del_comm: + cmd = "{} delete".format(cmd) + + rmap_data.append(cmd) + else: + logger.debug("In ext_comm_list 'rt' not" " provided") + return False + # Weight if weight: rmap_data.append("set weight {}".format(weight)) @@ -2151,6 +2203,243 @@ def addKernelRoute( return True +def configure_vxlan(tgen, input_dict): + """ + Add and configure vxlan + + * `tgen`: tgen onject + * `input_dict` : data for vxlan config + + Usage: + ------ + input_dict= { + "dcg2":{ + "vxlan":[{ + "vxlan_name": "vxlan75100", + "vxlan_id": "75100", + "dstport": 4789, + "local_addr": "120.0.0.1", + "learning": "no", + "delete": True + }] + } + } + + configure_vxlan(tgen, input_dict) + + Returns: + ------- + True or errormsg + + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + router_list = tgen.routers() + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + if "vxlan" in input_dict[dut]: + for vxlan_dict in input_dict[dut]["vxlan"]: + cmd = "ip link " + + del_vxlan = vxlan_dict.setdefault("delete", None) + vxlan_names = vxlan_dict.setdefault("vxlan_name", []) + vxlan_ids = vxlan_dict.setdefault("vxlan_id", []) + dstport = vxlan_dict.setdefault("dstport", None) + local_addr = vxlan_dict.setdefault("local_addr", None) + learning = vxlan_dict.setdefault("learning", None) + + config_data = [] + if vxlan_names and vxlan_ids: + for vxlan_name, vxlan_id in zip(vxlan_names, vxlan_ids): + cmd = "ip link" + + if del_vxlan: + cmd = "{} del {} type vxlan id {}".format( + cmd, vxlan_name, vxlan_id + ) + else: + cmd = "{} add {} type vxlan id {}".format( + cmd, vxlan_name, vxlan_id + ) + + if dstport: + cmd = "{} dstport {}".format(cmd, dstport) + + if local_addr: + ip_cmd = "ip addr add {} dev {}".format( + local_addr, vxlan_name + ) + if del_vxlan: + ip_cmd = "ip addr del {} dev {}".format( + local_addr, vxlan_name + ) + + config_data.append(ip_cmd) + + cmd = "{} local {}".format(cmd, local_addr) + + if learning == "no": + cmd = "{} {} learning".format(cmd, learning) + + elif learning == "yes": + cmd = "{} learning".format(cmd) + + config_data.append(cmd) + + try: + for _cmd in config_data: + logger.info("[DUT: %s]: Running command: %s", dut, _cmd) + rnode.run(_cmd) + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +def configure_brctl(tgen, topo, input_dict): + """ + Add and configure brctl + + * `tgen`: tgen onject + * `input_dict` : data for brctl config + + Usage: + ------ + input_dict= { + dut:{ + "brctl": [{ + "brctl_name": "br100", + "addvxlan": "vxlan75100", + "vrf": "RED", + "stp": "off" + }] + } + } + + configure_brctl(tgen, topo, input_dict) + + Returns: + ------- + True or errormsg + + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + router_list = tgen.routers() + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + if "brctl" in input_dict[dut]: + for brctl_dict in input_dict[dut]["brctl"]: + + brctl_names = brctl_dict.setdefault("brctl_name", []) + addvxlans = brctl_dict.setdefault("addvxlan", []) + stp_values = brctl_dict.setdefault("stp", []) + vrfs = brctl_dict.setdefault("vrf", []) + + ip_cmd = "ip link set" + for brctl_name, vxlan, vrf, stp in zip( + brctl_names, addvxlans, vrfs, stp_values + ): + + ip_cmd_list = [] + cmd = "ip link add name {} type bridge stp_state {}".format(brctl_name, stp) + + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + rnode.run(cmd) + + ip_cmd_list.append("{} up dev {}".format(ip_cmd, brctl_name)) + + if vxlan: + cmd = "{} dev {} master {}".format(ip_cmd, vxlan, brctl_name) + + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + rnode.run(cmd) + + ip_cmd_list.append("{} up dev {}".format(ip_cmd, vxlan)) + + if vrf: + ip_cmd_list.append( + "{} dev {} master {}".format(ip_cmd, brctl_name, vrf) + ) + + for intf_name, data in topo["routers"][dut]["links"].items(): + if "vrf" not in data: + continue + + if data["vrf"] == vrf: + ip_cmd_list.append( + "{} up dev {}".format(ip_cmd, data["interface"]) + ) + + try: + for _ip_cmd in ip_cmd_list: + logger.info("[DUT: %s]: Running command: %s", dut, _ip_cmd) + rnode.run(_ip_cmd) + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def configure_interface_mac(tgen, input_dict): + """ + Add and configure brctl + + * `tgen`: tgen onject + * `input_dict` : data for mac config + + input_mac= { + "edge1":{ + "br75100": "00:80:48:BA:d1:00, + "br75200": "00:80:48:BA:d1:00 + } + } + + configure_interface_mac(tgen, input_mac) + + Returns: + ------- + True or errormsg + + """ + + router_list = tgen.routers() + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + for intf, mac in input_dict[dut].items(): + cmd = "ifconfig {} hw ether {}".format(intf, mac) + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + + try: + result = rnode.run(cmd) + if len(result) != 0: + return result + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + return True + + ############################################# # Verification APIs ############################################# @@ -2875,3 +3164,283 @@ def verify_create_community_list(tgen, input_dict): logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) return True + + +def verify_cli_json(tgen, input_dict): + """ + API to verify if JSON is available for clis + command. + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict`: CLIs for which JSON needs to be verified + Usage + ----- + input_dict = { + "edge1":{ + "cli": ["show evpn vni detail", show evpn rmac vni all] + } + } + + result = verify_cli_json(tgen, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + for cli in input_dict[dut]["cli"]: + logger.info( + "[DUT: %s]: Verifying JSON is available for " "CLI %s :", dut, cli + ) + + test_cli = "{} json".format(cli) + ret_json = rnode.vtysh_cmd(test_cli, isjson=True) + if not bool(ret_json): + errormsg = "CLI: %s, JSON format is not available" % (cli) + return errormsg + elif "unknown" in ret_json or "Unknown" in ret_json: + errormsg = "CLI: %s, JSON format is not available" % (cli) + return errormsg + else: + logger.info( + "CLI : %s JSON format is available: " "\n %s", cli, ret_json + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +@retry(attempts=2, wait=4, return_is_str=True, initial_wait=2) +def verify_evpn_vni(tgen, input_dict): + """ + API to verify evpn vni details using "show evpn vni detail json" + command. + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict`: having details like - for which router, evpn details + needs to be verified + Usage + ----- + input_dict = { + "edge1":{ + "vni": [ + { + "75100":{ + "vrf": "RED", + "vxlanIntf": "vxlan75100", + "localVtepIp": "120.1.1.1", + "sviIntf": "br100" + } + } + ] + } + } + + result = verify_evpn_vni(tgen, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying evpn vni details :", dut) + + cmd = "show evpn vni detail json" + evpn_all_vni_json = run_frr_cmd(rnode, cmd, isjson=True) + if not bool(evpn_all_vni_json): + errormsg = "No output for '{}' cli".format(cmd) + return errormsg + + if "vni" in input_dict[dut]: + for vni_dict in input_dict[dut]["vni"]: + found = False + vni = vni_dict["name"] + for evpn_vni_json in evpn_all_vni_json: + if "vni" in evpn_vni_json: + if evpn_vni_json["vni"] != int(vni): + continue + + for attribute in vni_dict.keys(): + if vni_dict[attribute] != evpn_vni_json[attribute]: + errormsg = ( + "[DUT: %s] Verifying " + "%s for VNI: %s [FAILED]||" + ", EXPECTED : %s " + " FOUND : %s" + % ( + dut, + attribute, + vni, + vni_dict[attribute], + evpn_vni_json[attribute], + ) + ) + return errormsg + + else: + found = True + logger.info( + "[DUT: %s] Verifying" + " %s for VNI: %s , " + "Found Expected : %s ", + dut, + attribute, + vni, + evpn_vni_json[attribute], + ) + + if evpn_vni_json["state"] != "Up": + errormsg = ( + "[DUT: %s] Failed: Verifying" + " State for VNI: %s is not Up" % (dut, vni) + ) + return errormsg + + else: + errormsg = ( + "[DUT: %s] Failed:" + " VNI: %s is not present in JSON" % (dut, vni) + ) + return errormsg + + if found: + logger.info( + "[DUT %s]: Verifying VNI : %s " + "details and state is Up [PASSED]!!", + dut, + vni, + ) + return True + + else: + errormsg = ( + "[DUT: %s] Failed:" " vni details are not present in input data" % (dut) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return False + + +@retry(attempts=2, wait=4, return_is_str=True, initial_wait=2) +def verify_vrf_vni(tgen, input_dict): + """ + API to verify vrf vni details using "show vrf vni json" + command. + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict`: having details like - for which router, evpn details + needs to be verified + Usage + ----- + input_dict = { + "edge1":{ + "vrfs": [ + { + "RED":{ + "vni": 75000, + "vxlanIntf": "vxlan75100", + "sviIntf": "br100", + "routerMac": "00:80:48:ba:d1:00", + "state": "Up" + } + } + ] + } + } + + result = verify_vrf_vni(tgen, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying vrf vni details :", dut) + + cmd = "show vrf vni json" + vrf_all_vni_json = run_frr_cmd(rnode, cmd, isjson=True) + if not bool(vrf_all_vni_json): + errormsg = "No output for '{}' cli".format(cmd) + return errormsg + + if "vrfs" in input_dict[dut]: + for vrfs in input_dict[dut]["vrfs"]: + for vrf, vrf_dict in vrfs.items(): + found = False + for vrf_vni_json in vrf_all_vni_json["vrfs"]: + if "vrf" in vrf_vni_json: + if vrf_vni_json["vrf"] != vrf: + continue + + for attribute in vrf_dict.keys(): + if vrf_dict[attribute] == vrf_vni_json[attribute]: + found = True + logger.info( + "[DUT %s]: VRF: %s, " + "verifying %s " + ", Found Expected: %s " + "[PASSED]!!", + dut, + vrf, + attribute, + vrf_vni_json[attribute], + ) + else: + errormsg = ( + "[DUT: %s] VRF: %s, " + "verifying %s [FAILED!!] " + ", EXPECTED : %s " + ", FOUND : %s" + % ( + dut, + vrf, + attribute, + vrf_dict[attribute], + vrf_vni_json[attribute], + ) + ) + return errormsg + + else: + errormsg = "[DUT: %s] VRF: %s " "is not present in JSON" % ( + dut, + vrf, + ) + return errormsg + + if found: + logger.info( + "[DUT %s] Verifying VRF: %s " " details [PASSED]!!", + dut, + vrf, + ) + return True + + else: + errormsg = ( + "[DUT: %s] Failed:" " vrf details are not present in input data" % (dut) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return False