From: Kuldeep Kashyap Date: Wed, 13 May 2020 17:53:05 +0000 (+0000) Subject: tests: Framework support for BGP Multi-VRF automation X-Git-Tag: base_7.5~356^2~3 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=f8d572e88fdde80005ec9f3d68c3a5dac425f913;p=matthieu%2Ffrr.git tests: Framework support for BGP Multi-VRF automation 1. Added/Enhanced APIs to created bgp multi-vrf config and to verify config/functionality Signed-off-by: Kuldeep Kashyap --- diff --git a/tests/topotests/lib/bgp.py b/tests/topotests/lib/bgp.py index 2dd90e9a86..69c807f300 100644 --- a/tests/topotests/lib/bgp.py +++ b/tests/topotests/lib/bgp.py @@ -75,8 +75,12 @@ def create_router_bgp(tgen, topo, input_dict=None, build=False, load_config=True "address_family": { "ipv4": { "unicast": { - "redistribute": [ - {"redist_type": "static"}, + "redistribute": [{ + "redist_type": "static", + "attribute": { + "metric" : 123 + } + }, {"redist_type": "connected"} ], "advertise_networks": [ @@ -143,50 +147,55 @@ def create_router_bgp(tgen, topo, input_dict=None, build=False, load_config=True logger.debug("Router %s: 'bgp' not present in input_dict", router) continue - data_all_bgp = __create_bgp_global(tgen, input_dict, router, build) - if data_all_bgp: - bgp_data = input_dict[router]["bgp"] + bgp_data_list = input_dict[router]["bgp"] - bgp_addr_data = bgp_data.setdefault("address_family", {}) + if type(bgp_data_list) is not list: + bgp_data_list = [bgp_data_list] - if not bgp_addr_data: - logger.debug( - "Router %s: 'address_family' not present in " "input_dict for BGP", - router, - ) - else: + for bgp_data in bgp_data_list: + data_all_bgp = __create_bgp_global(tgen, bgp_data, router, build) + if data_all_bgp: + bgp_addr_data = bgp_data.setdefault("address_family", {}) - ipv4_data = bgp_addr_data.setdefault("ipv4", {}) - ipv6_data = bgp_addr_data.setdefault("ipv6", {}) + if not bgp_addr_data: + logger.debug( + "Router %s: 'address_family' not present in " + "input_dict for BGP", + router, + ) + else: - neigh_unicast = ( - True - if ipv4_data.setdefault("unicast", {}) - or ipv6_data.setdefault("unicast", {}) - else False - ) + ipv4_data = bgp_addr_data.setdefault("ipv4", {}) + ipv6_data = bgp_addr_data.setdefault("ipv6", {}) - if neigh_unicast: - data_all_bgp = __create_bgp_unicast_neighbor( - tgen, - topo, - input_dict, - router, - afi_test, - config_data=data_all_bgp, + neigh_unicast = ( + True + if ipv4_data.setdefault("unicast", {}) + or ipv6_data.setdefault("unicast", {}) + else False ) - try: - result = create_common_configuration( - tgen, router, data_all_bgp, "bgp", build, load_config - ) - except InvalidCLIError: - # Traceback - errormsg = traceback.format_exc() - logger.error(errormsg) - return errormsg + if neigh_unicast: + data_all_bgp = __create_bgp_unicast_neighbor( + tgen, + topo, + bgp_data, + router, + afi_test, + config_data=data_all_bgp, + ) - logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + try: + result = create_common_configuration( + tgen, router, data_all_bgp, "bgp", build, load_config + ) + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: create_router_bgp()") return result @@ -206,19 +215,16 @@ def __create_bgp_global(tgen, input_dict, router, build=False): True or False """ + result = False logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) - bgp_data = input_dict[router]["bgp"] + bgp_data = input_dict del_bgp_action = bgp_data.setdefault("delete", False) - if del_bgp_action: - config_data = ["no router bgp"] - - return config_data config_data = [] if "local_as" not in bgp_data and build: - logger.error( + logger.debug( "Router %s: 'local_as' not present in input_dict" "for BGP", router ) return False @@ -229,6 +235,12 @@ def __create_bgp_global(tgen, input_dict, router, build=False): if vrf_id: cmd = "{} vrf {}".format(cmd, vrf_id) + if del_bgp_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + return config_data + config_data.append(cmd) config_data.append("no bgp ebgp-requires-policy") @@ -325,12 +337,15 @@ def __create_bgp_unicast_neighbor( * `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)) add_neigh = True + bgp_data = input_dict if "router bgp" in config_data: add_neigh = False - bgp_data = input_dict[router]["bgp"]["address_family"] + + bgp_data = input_dict["address_family"] for addr_type, addr_dict in bgp_data.iteritems(): if not addr_dict: @@ -403,14 +418,19 @@ def __create_bgp_unicast_neighbor( if redistribute_data: for redistribute in redistribute_data: if "redist_type" not in redistribute: - logger.error( + logger.debug( "Router %s: 'redist_type' not present in " "input_dict", router ) else: cmd = "redistribute {}".format(redistribute["redist_type"]) redist_attr = redistribute.setdefault("attribute", None) if redist_attr: - cmd = "{} {}".format(cmd, redist_attr) + if isinstance(redist_attr, dict): + for key, value in redist_attr.items(): + cmd = "{} {} {}".format(cmd, key, value) + else: + cmd = "{} {}".format(cmd, redist_attr) + del_action = redistribute.setdefault("delete", False) if del_action: cmd = "no {}".format(cmd) @@ -453,13 +473,18 @@ def __create_bgp_neighbor(topo, input_dict, router, addr_type, add_neigh=True): config_data = [] logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) - bgp_data = input_dict[router]["bgp"]["address_family"] + bgp_data = input_dict["address_family"] neigh_data = bgp_data[addr_type]["unicast"]["neighbor"] for name, peer_dict in neigh_data.iteritems(): for dest_link, peer in peer_dict["dest_link"].iteritems(): nh_details = topo[name] - remote_as = nh_details["bgp"]["local_as"] + + if "vrfs" in topo[router]: + remote_as = nh_details["bgp"][0]["local_as"] + else: + remote_as = nh_details["bgp"]["local_as"] + update_source = None if dest_link in nh_details["links"].keys(): @@ -549,7 +574,7 @@ def __create_bgp_unicast_address_family( config_data = [] logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) - bgp_data = input_dict[router]["bgp"]["address_family"] + bgp_data = input_dict["address_family"] neigh_data = bgp_data[addr_type]["unicast"]["neighbor"] for peer_name, peer_dict in deepcopy(neigh_data).iteritems(): @@ -605,8 +630,12 @@ def __create_bgp_unicast_address_family( allowas_in = peer.setdefault("allowas-in", None) # next-hop-self - if next_hop_self: - config_data.append("{} next-hop-self".format(neigh_cxt)) + if next_hop_self is not None: + if next_hop_self is True: + config_data.append("{} next-hop-self".format(neigh_cxt)) + else: + config_data.append("no {} next-hop-self".format(neigh_cxt)) + # send_community if send_community: config_data.append("{} send-community".format(neigh_cxt)) @@ -840,77 +869,288 @@ def verify_router_id(tgen, topo, input_dict): @retry(attempts=20, wait=2, return_is_str=True) -def verify_bgp_convergence(tgen, topo): +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 - * `addr_type`: ip_type, ipv4/ipv6 + * `dut`: device under test + Usage ----- # To veriry is BGP is converged for all the routers used in topology - results = verify_bgp_convergence(tgen, topo, "ipv4") + results = verify_bgp_convergence(tgen, topo, dut="r1") + Returns ------- errormsg(str) or True """ - logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + logger.debug("Entering lib API: verify_bgp_convergence()") for router, rnode in tgen.routers().iteritems(): if "bgp" not in topo["routers"][router]: continue - logger.info("Verifying BGP Convergence on router %s", router) - show_bgp_json = run_frr_cmd(rnode, "show bgp summary json", isjson=True) + if dut is not None and dut != router: + continue + + logger.info("Verifying BGP Convergence on router %s:", router) + show_bgp_json = run_frr_cmd(rnode, "show bgp vrf all summary json", isjson=True) # Verifying output dictionary show_bgp_json is empty or not if not bool(show_bgp_json): errormsg = "BGP is not running" return errormsg # To find neighbor ip type - bgp_addr_type = topo["routers"][router]["bgp"]["address_family"] - for addr_type in bgp_addr_type.keys(): - if not check_address_types(addr_type): - continue - total_peer = 0 + bgp_data_list = topo["routers"][router]["bgp"] - bgp_neighbors = bgp_addr_type[addr_type]["unicast"]["neighbor"] + if type(bgp_data_list) is not list: + bgp_data_list = [bgp_data_list] - for bgp_neighbor in bgp_neighbors: - total_peer += len(bgp_neighbors[bgp_neighbor]["dest_link"]) + for bgp_data in bgp_data_list: + if "vrf" in bgp_data: + vrf = bgp_data["vrf"] + if vrf is None: + vrf = "default" + else: + vrf = "default" - for addr_type in bgp_addr_type.keys(): - if not check_address_types(addr_type): - continue - bgp_neighbors = bgp_addr_type[addr_type]["unicast"]["neighbor"] + # To find neighbor ip type + bgp_addr_type = bgp_data["address_family"] + if "l2vpn" in bgp_addr_type: + total_evpn_peer = 0 - no_of_peer = 0 - for bgp_neighbor, peer_data in bgp_neighbors.iteritems(): - for dest_link in peer_data["dest_link"].keys(): - data = topo["routers"][bgp_neighbor]["links"] - if dest_link in data: - neighbor_ip = data[dest_link][addr_type].split("/")[0] - if addr_type == "ipv4": - ipv4_data = show_bgp_json["ipv4Unicast"]["peers"] - nh_state = ipv4_data[neighbor_ip]["state"] - else: - ipv6_data = show_bgp_json["ipv6Unicast"]["peers"] - nh_state = ipv6_data[neighbor_ip]["state"] + if "neighbor" not in bgp_addr_type["l2vpn"]["evpn"]: + continue - if nh_state == "Established": - no_of_peer += 1 - if no_of_peer == total_peer: - logger.info("BGP is Converged for router %s", router) + bgp_neighbors = bgp_addr_type["l2vpn"]["evpn"]["neighbor"] + total_evpn_peer += len(bgp_neighbors) + + no_of_evpn_peer = 0 + for bgp_neighbor, peer_data in bgp_neighbors.items(): + for _addr_type, dest_link_dict in peer_data.items(): + data = topo["routers"][bgp_neighbor]["links"] + for dest_link in dest_link_dict.keys(): + if dest_link in data: + peer_details = peer_data[_addr_type][dest_link] + + neighbor_ip = data[dest_link][_addr_type].split("/")[0] + nh_state = None + + if ( + "ipv4Unicast" in show_bgp_json[vrf] + or "ipv6Unicast" in show_bgp_json[vrf] + ): + errormsg = ( + "[DUT: %s] VRF: %s, " + "ipv4Unicast/ipv6Unicast" + " address-family present" + " under l2vpn" % (router, vrf) + ) + return errormsg + + l2VpnEvpn_data = show_bgp_json[vrf]["l2VpnEvpn"][ + "peers" + ] + nh_state = l2VpnEvpn_data[neighbor_ip]["state"] + + if nh_state == "Established": + no_of_evpn_peer += 1 + + if no_of_evpn_peer == total_evpn_peer: + logger.info( + "[DUT: %s] VRF: %s, BGP is Converged for " "epvn peers", + router, + vrf, + ) + else: + errormsg = ( + "[DUT: %s] VRF: %s, BGP is not converged " + "for evpn peers" % (router, vrf) + ) + return errormsg + else: + for addr_type in bgp_addr_type.keys(): + if not check_address_types(addr_type): + continue + total_peer = 0 + + bgp_neighbors = bgp_addr_type[addr_type]["unicast"]["neighbor"] + + for bgp_neighbor in bgp_neighbors: + total_peer += len(bgp_neighbors[bgp_neighbor]["dest_link"]) + + for addr_type in bgp_addr_type.keys(): + if not check_address_types(addr_type): + continue + bgp_neighbors = bgp_addr_type[addr_type]["unicast"]["neighbor"] + + no_of_peer = 0 + for bgp_neighbor, peer_data in bgp_neighbors.items(): + for dest_link in peer_data["dest_link"].keys(): + data = topo["routers"][bgp_neighbor]["links"] + if dest_link in data: + peer_details = peer_data["dest_link"][dest_link] + # for link local neighbors + if ( + "neighbor_type" in peer_details + and peer_details["neighbor_type"] == "link-local" + ): + neighbor_ip = get_ipv6_linklocal_address( + topo["routers"], bgp_neighbor, dest_link + ) + elif "source_link" in peer_details: + neighbor_ip = topo["routers"][bgp_neighbor][ + "links" + ][peer_details["source_link"]][addr_type].split( + "/" + )[ + 0 + ] + elif ( + "neighbor_type" in peer_details + and peer_details["neighbor_type"] == "unnumbered" + ): + neighbor_ip = data[dest_link]["peer-interface"] + else: + neighbor_ip = data[dest_link][addr_type].split("/")[ + 0 + ] + nh_state = None + + if addr_type == "ipv4": + ipv4_data = show_bgp_json[vrf]["ipv4Unicast"][ + "peers" + ] + nh_state = ipv4_data[neighbor_ip]["state"] + else: + ipv6_data = show_bgp_json[vrf]["ipv6Unicast"][ + "peers" + ] + nh_state = ipv6_data[neighbor_ip]["state"] + + if nh_state == "Established": + no_of_peer += 1 + + if no_of_peer == total_peer: + logger.info("[DUT: %s] VRF: %s, BGP is Converged", router, vrf) + else: + errormsg = "[DUT: %s] VRF: %s, BGP is not converged" % (router, vrf) + return errormsg + + logger.debug("Exiting API: verify_bgp_convergence()") + return True + + +@retry(attempts=3, wait=4, return_is_str=True) +def verify_bgp_community( + tgen, addr_type, router, network, input_dict=None, vrf=None, bestpath=False +): + """ + API to veiryf BGP large community is attached in route for any given + DUT by running "show bgp ipv4/6 {route address} json" command. + + Parameters + ---------- + * `tgen`: topogen object + * `addr_type` : ip type, ipv4/ipv6 + * `dut`: Device Under Test + * `network`: network for which set criteria needs to be verified + * `input_dict`: having details like - for which router, community and + values needs to be verified + * `vrf`: VRF name + * `bestpath`: To check best path cli + + Usage + ----- + networks = ["200.50.2.0/32"] + input_dict = { + "largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5" + } + result = verify_bgp_community(tgen, "ipv4", dut, network, input_dict=None) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: verify_bgp_community()") + if router not in tgen.routers(): + return False + + rnode = tgen.routers()[router] + + logger.info( + "Verifying BGP community attributes on dut %s: for %s " "network %s", + router, + addr_type, + network, + ) + + command = "show bgp" + + sleep(5) + for net in network: + if vrf: + cmd = "{} vrf {} {} {} json".format(command, vrf, addr_type, net) + elif bestpath: + cmd = "{} {} {} bestpath json".format(command, addr_type, net) else: - errormsg = "BGP is not converged for router {}".format(router) + cmd = "{} {} {} json".format(command, addr_type, net) + + show_bgp_json = run_frr_cmd(rnode, cmd, isjson=True) + if "paths" not in show_bgp_json: + return "Prefix {} not found in BGP table of router: {}".format(net, router) + + as_paths = show_bgp_json["paths"] + found = False + for i in range(len(as_paths)): + if ( + "largeCommunity" in show_bgp_json["paths"][i] + or "community" in show_bgp_json["paths"][i] + ): + found = True + logger.info( + "Large Community attribute is found for route:" " %s in router: %s", + net, + router, + ) + if input_dict is not None: + for criteria, comm_val in input_dict.items(): + show_val = show_bgp_json["paths"][i][criteria]["string"] + if comm_val == show_val: + logger.info( + "Verifying BGP %s for prefix: %s" + " in router: %s, found expected" + " value: %s", + criteria, + net, + router, + comm_val, + ) + else: + errormsg = ( + "Failed: Verifying BGP attribute" + " {} for route: {} in router: {}" + ", expected value: {} but found" + ": {}".format(criteria, net, router, comm_val, show_val) + ) + return errormsg + + if not found: + errormsg = ( + "Large Community attribute is not found for route: " + "{} in router: {} ".format(net, router) + ) return errormsg - logger.debug("Exiting API: verify_bgp_convergence()") + logger.debug("Exiting lib API: verify_bgp_community()") return True @@ -1090,6 +1330,7 @@ def clear_bgp(tgen, addr_type, router, vrf=None): """ logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + if router not in tgen.routers(): return False @@ -1102,9 +1343,21 @@ def clear_bgp(tgen, addr_type, router, vrf=None): # Clearing BGP logger.info("Clearing BGP neighborship for router %s..", router) if addr_type == "ipv4": - run_frr_cmd(rnode, "clear ip bgp *") + if vrf: + for _vrf in vrf: + run_frr_cmd(rnode, "clear ip bgp vrf {} *".format(_vrf)) + else: + run_frr_cmd(rnode, "clear ip bgp *") elif addr_type == "ipv6": - run_frr_cmd(rnode, "clear bgp ipv6 *") + if vrf: + for _vrf in vrf: + run_frr_cmd(rnode, "clear bgp vrf {} ipv6 *".format(_vrf)) + else: + run_frr_cmd(rnode, "clear bgp ipv6 *") + else: + run_frr_cmd(rnode, "clear bgp *") + + sleep(5) logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) @@ -1698,7 +1951,6 @@ def verify_best_path_as_per_bgp_attribute( "show bgp ipv4/6 json" command will be run and verify best path according to shortest as-path, highest local-preference and med, lowest weight and route origin IGP>EGP>INCOMPLETE. - Parameters ---------- * `tgen` : topogen object @@ -1707,7 +1959,6 @@ def verify_best_path_as_per_bgp_attribute( * `attribute` : calculate best path using this attribute * `input_dict`: defines different routes to calculate for which route best path is selected - Usage ----- # To verify best path for routes 200.50.2.0/32 and 200.60.2.0/32 from @@ -1741,114 +1992,155 @@ def verify_best_path_as_per_bgp_attribute( """ logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + if router not in tgen.routers(): return False rnode = tgen.routers()[router] - command = "show bgp {} json".format(addr_type) + # Verifying show bgp json + command = "show bgp" - sleep(5) + sleep(2) logger.info("Verifying router %s RIB for best path:", router) - sh_ip_bgp_json = run_frr_cmd(rnode, command, isjson=True) + static_route = False + advertise_network = False for route_val in input_dict.values(): - net_data = route_val["bgp"]["address_family"][addr_type]["unicast"] - networks = net_data["advertise_networks"] - for network in networks: - route = network["network"] + if "static_routes" in route_val: + static_route = True + networks = route_val["static_routes"] + else: + advertise_network = True + net_data = route_val["bgp"]["address_family"][addr_type]["unicast"] + networks = net_data["advertise_networks"] - route_attributes = sh_ip_bgp_json["routes"][route] - _next_hop = None - compare = None - attribute_dict = {} - for route_attribute in route_attributes: - next_hops = route_attribute["nexthops"] - for next_hop in next_hops: - next_hop_ip = next_hop["ip"] - attribute_dict[next_hop_ip] = route_attribute[attribute] + for network in networks: + _network = network["network"] + no_of_ip = network.setdefault("no_of_ip", 1) + vrf = network.setdefault("vrf", None) - # AS_PATH attribute - if attribute == "path": - # Find next_hop for the route have minimum as_path - _next_hop = min( - attribute_dict, key=lambda x: len(set(attribute_dict[x])) - ) - compare = "SHORTEST" - - # LOCAL_PREF attribute - elif attribute == "locPrf": - # Find next_hop for the route have highest local preference - _next_hop = max(attribute_dict, key=(lambda k: attribute_dict[k])) - compare = "HIGHEST" - - # WEIGHT attribute - elif attribute == "weight": - # Find next_hop for the route have highest weight - _next_hop = max(attribute_dict, key=(lambda k: attribute_dict[k])) - compare = "HIGHEST" - - # ORIGIN attribute - elif attribute == "origin": - # Find next_hop for the route have IGP as origin, - - # - rule is IGP>EGP>INCOMPLETE - _next_hop = [ - key for (key, value) in attribute_dict.iteritems() if value == "IGP" - ][0] - compare = "" - - # MED attribute - elif attribute == "metric": - # Find next_hop for the route have LOWEST MED - _next_hop = min(attribute_dict, key=(lambda k: attribute_dict[k])) - compare = "LOWEST" - - # Show ip route - if addr_type == "ipv4": - command = "show ip route json" + if vrf: + cmd = "{} vrf {}".format(command, vrf) else: - command = "show ipv6 route json" + cmd = command + + cmd = "{} {}".format(cmd, addr_type) + cmd = "{} json".format(cmd) + sh_ip_bgp_json = run_frr_cmd(rnode, cmd, isjson=True) + + routes = generate_ips(_network, no_of_ip) + for route in routes: + route = str(ipaddr.IPNetwork(unicode(route))) + + if route in sh_ip_bgp_json["routes"]: + route_attributes = sh_ip_bgp_json["routes"][route] + _next_hop = None + compare = None + attribute_dict = {} + for route_attribute in route_attributes: + next_hops = route_attribute["nexthops"] + for next_hop in next_hops: + next_hop_ip = next_hop["ip"] + attribute_dict[next_hop_ip] = route_attribute[attribute] + + # AS_PATH attribute + if attribute == "path": + # Find next_hop for the route have minimum as_path + _next_hop = min( + attribute_dict, key=lambda x: len(set(attribute_dict[x])) + ) + compare = "SHORTEST" - rib_routes_json = run_frr_cmd(rnode, command, isjson=True) + # LOCAL_PREF attribute + elif attribute == "locPrf": + # Find next_hop for the route have highest local preference + _next_hop = max( + attribute_dict, key=(lambda k: attribute_dict[k]) + ) + compare = "HIGHEST" - # Verifying output dictionary rib_routes_json is not empty - if not bool(rib_routes_json): - errormsg = "No route found in RIB of router {}..".format(router) - return errormsg + # WEIGHT attribute + elif attribute == "weight": + # Find next_hop for the route have highest weight + _next_hop = max( + attribute_dict, key=(lambda k: attribute_dict[k]) + ) + compare = "HIGHEST" + + # ORIGIN attribute + elif attribute == "origin": + # Find next_hop for the route have IGP as origin, - + # - rule is IGP>EGP>INCOMPLETE + _next_hop = [ + key + for (key, value) in attribute_dict.iteritems() + if value == "IGP" + ][0] + compare = "" + + # MED attribute + elif attribute == "metric": + # Find next_hop for the route have LOWEST MED + _next_hop = min( + attribute_dict, key=(lambda k: attribute_dict[k]) + ) + compare = "LOWEST" - st_found = False - nh_found = False - # Find best is installed in RIB - if route in rib_routes_json: - st_found = True - # Verify next_hop in rib_routes_json - if rib_routes_json[route][0]["nexthops"][0]["ip"] in attribute_dict: - nh_found = True - else: - errormsg = ( - "Incorrect Nexthop for BGP route {} in " - "RIB of router {}, Expected: {}, Found:" - " {}\n".format( + # Show ip route + if addr_type == "ipv4": + command_1 = "show ip route" + else: + command_1 = "show ipv6 route" + + if vrf: + cmd = "{} vrf {} json".format(command_1, vrf) + else: + cmd = "{} json".format(command_1) + + rib_routes_json = run_frr_cmd(rnode, cmd, isjson=True) + + # Verifying output dictionary rib_routes_json is not empty + if not bool(rib_routes_json): + errormsg = "No route found in RIB of router {}..".format(router) + return errormsg + + st_found = False + nh_found = False + # Find best is installed in RIB + if route in rib_routes_json: + st_found = True + # Verify next_hop in rib_routes_json + if ( + rib_routes_json[route][0]["nexthops"][0]["ip"] + in attribute_dict + ): + nh_found = True + else: + errormsg = ( + "Incorrect Nexthop for BGP route {} in " + "RIB of router {}, Expected: {}, Found:" + " {}\n".format( + route, + router, + rib_routes_json[route][0]["nexthops"][0]["ip"], + _next_hop, + ) + ) + return errormsg + + if st_found and nh_found: + logger.info( + "Best path for prefix: %s with next_hop: %s is " + "installed according to %s %s: (%s) in RIB of " + "router %s", route, - router, - rib_routes_json[route][0]["nexthops"][0]["ip"], _next_hop, + compare, + attribute, + attribute_dict[_next_hop], + router, ) - ) - return errormsg - - if st_found and nh_found: - logger.info( - "Best path for prefix: %s with next_hop: %s is " - "installed according to %s %s: (%s) in RIB of " - "router %s", - route, - _next_hop, - compare, - attribute, - attribute_dict[_next_hop], - router, - ) logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) return True @@ -1965,7 +2257,7 @@ def verify_best_path_as_per_admin_distance( return True -@retry(attempts=10, wait=2, return_is_str=True, initial_wait=2) +@retry(attempts=5, wait=2, return_is_str=True, initial_wait=2) def verify_bgp_rib(tgen, addr_type, dut, input_dict, next_hop=None, aspath=None): """ This API is to verify whether bgp rib has any @@ -1995,7 +2287,7 @@ def verify_bgp_rib(tgen, addr_type, dut, input_dict, next_hop=None, aspath=None) errormsg(str) or True """ - logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + logger.debug("Entering lib API: verify_bgp_rib()") router_list = tgen.routers() additional_nexthops_in_required_nhs = [] @@ -2210,7 +2502,7 @@ def verify_bgp_rib(tgen, addr_type, dut, input_dict, next_hop=None, aspath=None) "routes are: {}\n".format(dut, found_routes) ) - logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + logger.debug("Exiting lib API: verify_bgp_rib()") return True diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py index 0b19877aff..3de7ab3ebe 100644 --- a/tests/topotests/lib/common_config.py +++ b/tests/topotests/lib/common_config.py @@ -175,6 +175,49 @@ def run_frr_cmd(rnode, cmd, isjson=False): raise InvalidCLIError("No actual cmd passed") +def apply_raw_config(tgen, input_dict): + + """ + API to configure raw configuration on device. This can be used for any cli + which does has not been implemented in JSON. + + Parameters + ---------- + * `tgen`: tgen onject + * `input_dict`: configuration that needs to be applied + + Usage + ----- + input_dict = { + "r2": { + "raw_config": [ + "router bgp", + "no bgp update-group-split-horizon" + ] + } + } + Returns + ------- + True or errormsg + """ + + result = True + for router_name in input_dict.keys(): + config_cmd = input_dict[router_name]["raw_config"] + + if not isinstance(config_cmd, list): + config_cmd = [config_cmd] + + frr_cfg_file = "{}/{}/{}".format(TMPDIR, router_name, FRRCFG_FILE) + with open(frr_cfg_file, "w") as cfg: + for cmd in config_cmd: + cfg.write("{}\n".format(cmd)) + + result = load_config_to_router(tgen, router_name) + + return result + + def create_common_configuration( tgen, router, data, config_type=None, build=False, load_config=True ): @@ -207,6 +250,7 @@ def create_common_configuration( "bgp_community_list": "! Community List Config\n", "route_maps": "! Route Maps Config\n", "bgp": "! BGP Config\n", + "vrf": "! VRF Config\n", } ) @@ -355,6 +399,7 @@ def reset_config_on_routers(tgen, routerName=None): """ Resets configuration on routers to the snapshot created using input JSON file. It replaces existing router configuration with FRRCFG_BKUP_FILE + Parameters ---------- * `tgen` : Topogen object @@ -370,6 +415,7 @@ def reset_config_on_routers(tgen, routerName=None): router = router_list[rname] logger.info("Configuring router %s to initial test configuration", rname) + cfg = router.run("vtysh -c 'show running'") fname = "{}/{}/frr.sav".format(TMPDIR, rname) dname = "{}/{}/delta.conf".format(TMPDIR, rname) @@ -387,22 +433,13 @@ def reset_config_on_routers(tgen, routerName=None): f.write("\n") f.close() - run_cfg_file = "{}/{}/frr.sav".format(TMPDIR, rname) init_cfg_file = "{}/{}/frr_json_initial.conf".format(TMPDIR, rname) - - tempdir = mkdtemp() - with open(os.path.join(tempdir, "vtysh.conf"), "w") as fd: - pass - - command = "/usr/lib/frr/frr-reload.py --confdir {} --input {} --test {} > {}".format( - tempdir, run_cfg_file, init_cfg_file, dname + command = "/usr/lib/frr/frr-reload.py --input {} --test {} > {}".format( + run_cfg_file, init_cfg_file, dname ) result = call(command, shell=True, stderr=SUB_STDOUT, stdout=SUB_PIPE) - os.unlink(os.path.join(tempdir, "vtysh.conf")) - os.rmdir(tempdir) - # Assert if command fail if result > 0: logger.error("Delta file creation failed. Command executed %s", command) @@ -459,17 +496,18 @@ def reset_config_on_routers(tgen, routerName=None): # Router current configuration to log file or console if # "show_router_config" is defined in "pytest.ini" if show_router_config: - logger.info("Configuration on router {} after config reset:".format(rname)) + logger.info("Configuration on router {} after reset:".format(rname)) logger.info(delta.getvalue()) delta.close() - logger.debug("Exting API: reset_config_on_routers") + logger.debug("Exiting API: reset_config_on_routers") return True def load_config_to_router(tgen, routerName, save_bkup=False): """ Loads configuration on router from the file FRRCFG_FILE. + Parameters ---------- * `tgen` : Topogen object @@ -481,7 +519,7 @@ def load_config_to_router(tgen, routerName, save_bkup=False): router_list = tgen.routers() for rname in ROUTER_LIST: - if routerName and routerName != rname: + if routerName and rname != routerName: continue router = router_list[rname] @@ -504,6 +542,7 @@ def load_config_to_router(tgen, routerName, save_bkup=False): raise InvalidCLIError("%s" % output) cfg.truncate(0) + except IOError as err: errormsg = ( "Unable to open config File. error(%s):" " %s", @@ -514,24 +553,29 @@ def load_config_to_router(tgen, routerName, save_bkup=False): # Router current configuration to log file or console if # "show_router_config" is defined in "pytest.ini" if show_router_config: + logger.info("New configuration for router {}:".format(rname)) new_config = router.run("vtysh -c 'show running'") logger.info(new_config) - logger.debug("Exting API: load_config_to_router") + logger.debug("Exiting API: load_config_to_router") return True -def get_frr_ipv6_linklocal(tgen, router, intf=None): +def get_frr_ipv6_linklocal(tgen, router, intf=None, vrf=None): """ API to get the link local ipv6 address of a perticular interface using FRR command 'show interface' + * `tgen`: tgen onject * `router` : router for which hightest interface should be calculated * `intf` : interface for which linklocal address needs to be taken + * `vrf` : VRF name + Usage ----- linklocal = get_frr_ipv6_linklocal(tgen, router, "intf1", RED_A) + Returns ------- 1) array of interface names to link local ips. @@ -544,7 +588,10 @@ def get_frr_ipv6_linklocal(tgen, router, intf=None): linklocal = [] - cmd = "show interface" + if vrf: + cmd = "show interface vrf {}".format(vrf) + else: + cmd = "show interface" ifaces = router_list[router].run('vtysh -c "{}"'.format(cmd)) @@ -635,6 +682,55 @@ def start_topology(tgen): tgen.start_router() +def stop_router(tgen, router): + """ + Router"s current config would be saved to /etc/frr/ for each deamon + and router and its deamons would be stopped. + + * `tgen` : topogen object + * `router`: Device under test + """ + + router_list = tgen.routers() + + # Saving router config to /etc/frr, which will be loaded to router + # when it starts + router_list[router].vtysh_cmd("write memory") + + # Stop router + router_list[router].stop() + + +def start_router(tgen, router): + """ + Router will started and config would be loaded from /etc/frr/ for each + deamon + + * `tgen` : topogen object + * `router`: Device under test + """ + + logger.debug("Entering lib API: start_router") + + try: + router_list = tgen.routers() + + # Router and its deamons would be started and config would + # be loaded to router for each deamon from /etc/frr + router_list[router].start() + + # Waiting for router to come up + sleep(5) + + except Exception as e: + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: start_router()") + return True + + def number_to_row(routerName): """ Returns the number for the router. @@ -658,6 +754,190 @@ def number_to_column(routerName): ############################################# +def create_vrf_cfg(tgen, topo, input_dict=None, build=False): + """ + Create vrf configuration for created topology. VRF + configuration is provided in input json file. + + VRF config is done in Linux Kernel: + * Create VRF + * Attach interface to VRF + * Bring up VRF + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `input_dict` : Input dict data, required when configuring + from testcase + * `build` : Only for initial setup phase this is set as True. + + Usage + ----- + input_dict={ + "r3": { + "links": { + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r2-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + }, + { + "name": "BLUE_A", + "id": "3", + "delete": True + }, + { + "name": "BLUE_B", + "id": "4" + } + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict) + + Returns + ------- + True or False + """ + result = True + if not input_dict: + input_dict = deepcopy(topo) + else: + input_dict = deepcopy(input_dict) + + try: + for c_router, c_data in input_dict.iteritems(): + rnode = tgen.routers()[c_router] + if "vrfs" in c_data: + for vrf in c_data["vrfs"]: + config_data = [] + del_action = vrf.setdefault("delete", False) + name = vrf.setdefault("name", None) + table_id = vrf.setdefault("id", None) + vni = vrf.setdefault("vni", None) + del_vni = vrf.setdefault("no_vni", None) + + if del_action: + # Kernel cmd- Add VRF and table + cmd = "ip link del {} type vrf table {}".format( + vrf["name"], vrf["id"] + ) + + logger.info("[DUT: %s]: Running kernel cmd [%s]", c_router, cmd) + rnode.run(cmd) + + # Kernel cmd - Bring down VRF + cmd = "ip link set dev {} down".format(name) + logger.info("[DUT: %s]: Running kernel cmd [%s]", c_router, cmd) + rnode.run(cmd) + + else: + if name and table_id: + # Kernel cmd- Add VRF and table + cmd = "ip link add {} type vrf table {}".format( + name, table_id + ) + logger.info( + "[DUT: %s]: Running kernel cmd " "[%s]", c_router, cmd + ) + rnode.run(cmd) + + # Kernel cmd - Bring up VRF + cmd = "ip link set dev {} up".format(name) + logger.info( + "[DUT: %s]: Running kernel " "cmd [%s]", c_router, cmd + ) + rnode.run(cmd) + + if "links" in c_data: + for destRouterLink, data in sorted( + c_data["links"].iteritems() + ): + # Loopback interfaces + if "type" in data and data["type"] == "loopback": + interface_name = destRouterLink + else: + interface_name = data["interface"] + + if "vrf" in data: + vrf_list = data["vrf"] + + if type(vrf_list) is not list: + vrf_list = [vrf_list] + + for _vrf in vrf_list: + cmd = "ip link set {} master {}".format( + interface_name, _vrf + ) + + logger.info( + "[DUT: %s]: Running" " kernel cmd [%s]", + c_router, + cmd, + ) + rnode.run(cmd) + + result = create_common_configuration( + tgen, c_router, config_data, "vrf", build=build + ) + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + return result + + +def create_interface_in_kernel( + tgen, dut, name, ip_addr, vrf=None, netmask=None, create=True +): + """ + 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 + * `name` : interface name + * `ip_addr` : ip address for interface + * `vrf` : VRF name, to which interface will be associated + * `netmask` : netmask value, default is None + * `create`: Create interface in kernel, if created then no need + to create + """ + + rnode = tgen.routers()[dut] + + if create: + cmd = "sudo ip link add name {} type dummy".format(name) + rnode.run(cmd) + + addr_type = validate_ip_address(ip_addr) + if addr_type == "ipv4": + cmd = "ifconfig {} {} netmask {}".format(name, ip_addr, netmask) + else: + cmd = "ifconfig {} inet6 add {}/{}".format(name, ip_addr, netmask) + + rnode.run(cmd) + + if vrf: + cmd = "ip link set {} master {}".format(name, vrf) + rnode.run(cmd) + + def validate_ip_address(ip_address): """ Validates the type of ip address @@ -860,13 +1140,15 @@ def interface_status(tgen, topo, input_dict): return True -def retry(attempts=3, wait=2, return_is_str=True, initial_wait=0): +def retry(attempts=3, wait=2, return_is_str=True, initial_wait=0, return_is_dict=False): """ Retries function execution, if return is an errormsg or exception + * `attempts`: Number of attempts to make * `wait`: Number of seconds to wait between each attempt * `return_is_str`: Return val is an errormsg in case of failure * `initial_wait`: Sleeps for this much seconds before executing function + """ def _retry(func): @@ -883,15 +1165,20 @@ def retry(attempts=3, wait=2, return_is_str=True, initial_wait=0): sleep(initial_wait) _return_is_str = kwargs.pop("return_is_str", return_is_str) + _return_is_dict = kwargs.pop("return_is_str", return_is_dict) for i in range(1, _attempts + 1): try: _expected = kwargs.setdefault("expected", True) kwargs.pop("expected") ret = func(*args, **kwargs) logger.debug("Function returned %s" % ret) - if return_is_str and isinstance(ret, bool) and _expected: + if _return_is_str and isinstance(ret, bool) and _expected: return ret - if isinstance(ret, str) and _expected is False: + if ( + isinstance(ret, str) or isinstance(ret, unicode) + ) and _expected is False: + return ret + if _return_is_dict and isinstance(ret, dict): return ret if _attempts == i: @@ -945,16 +1232,19 @@ def create_interfaces_cfg(tgen, topo, build=False): """ Create interface configuration for created topology. Basic Interface configuration is provided in input json file. + Parameters ---------- * `tgen` : Topogen object * `topo` : json file data * `build` : Only for initial setup phase this is set as True. + Returns ------- True or False """ result = False + topo = deepcopy(topo) try: for c_router, c_data in topo.iteritems(): @@ -965,13 +1255,30 @@ def create_interfaces_cfg(tgen, topo, build=False): interface_name = destRouterLink else: interface_name = data["interface"] + interface_data.append("interface {}".format(str(interface_name))) if "ipv4" in data: intf_addr = c_data["links"][destRouterLink]["ipv4"] - interface_data.append("ip address {}".format(intf_addr)) + + if "delete" in data and data["delete"]: + interface_data.append("no ip address {}".format(intf_addr)) + else: + interface_data.append("ip address {}".format(intf_addr)) if "ipv6" in data: intf_addr = c_data["links"][destRouterLink]["ipv6"] - interface_data.append("ipv6 address {}".format(intf_addr)) + + if "delete" in data and data["delete"]: + interface_data.append("no ipv6 address {}".format(intf_addr)) + else: + interface_data.append("ipv6 address {}".format(intf_addr)) + + if "ipv6-link-local" in data: + intf_addr = c_data["links"][destRouterLink]["ipv6-link-local"] + + if "delete" in data and data["delete"]: + interface_data.append("no ipv6 address {}".format(intf_addr)) + else: + interface_data.append("ipv6 address {}\n".format(intf_addr)) result = create_common_configuration( tgen, c_router, interface_data, "interface_config", build=build @@ -988,11 +1295,13 @@ def create_interfaces_cfg(tgen, topo, build=False): def create_static_routes(tgen, input_dict, build=False): """ Create static routes for given router as defined in input_dict + Parameters ---------- * `tgen` : Topogen object * `input_dict` : Input dict data, required when configuring from testcase * `build` : Only for initial setup phase this is set as True. + Usage ----- input_dict should be in the format below: @@ -1002,7 +1311,9 @@ def create_static_routes(tgen, input_dict, build=False): # admin_distance: admin distance for route/routes. # next_hop: starting next-hop address # tag: tag id for static routes + # vrf: VRF name in which static routes needs to be created # delete: True if config to be removed. Default False. + Example: "routers": { "r1": { @@ -1012,24 +1323,27 @@ def create_static_routes(tgen, input_dict, build=False): "no_of_ip": 9, "admin_distance": 100, "next_hop": "10.0.0.1", - "tag": 4001 + "tag": 4001, + "vrf": "RED_A" "delete": true } ] } } + Returns ------- errormsg(str) or True """ result = False - logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + logger.debug("Entering lib API: create_static_routes()") input_dict = deepcopy(input_dict) + try: for router in input_dict.keys(): if "static_routes" not in input_dict[router]: errormsg = "static_routes not present in input_dict" - logger.debug(errormsg) + logger.info(errormsg) continue static_routes_list = [] @@ -1037,27 +1351,38 @@ def create_static_routes(tgen, input_dict, build=False): static_routes = input_dict[router]["static_routes"] for static_route in static_routes: del_action = static_route.setdefault("delete", False) - # No of IPs no_of_ip = static_route.setdefault("no_of_ip", 1) - admin_distance = static_route.setdefault("admin_distance", None) - tag = static_route.setdefault("tag", None) - if "next_hop" not in static_route or "network" not in static_route: - errormsg = "'next_hop' or 'network' missing in" " input_dict" - return errormsg - - next_hop = static_route["next_hop"] - network = static_route["network"] + network = static_route.setdefault("network", []) if type(network) is not list: network = [network] + admin_distance = static_route.setdefault("admin_distance", None) + tag = static_route.setdefault("tag", None) + vrf = static_route.setdefault("vrf", None) + interface = static_route.setdefault("interface", None) + next_hop = static_route.setdefault("next_hop", None) + nexthop_vrf = static_route.setdefault("nexthop_vrf", None) + ip_list = generate_ips(network, no_of_ip) for ip in ip_list: addr_type = validate_ip_address(ip) if addr_type == "ipv4": - cmd = "ip route {} {}".format(ip, next_hop) + cmd = "ip route {}".format(ip) else: - cmd = "ipv6 route {} {}".format(ip, next_hop) + cmd = "ipv6 route {}".format(ip) + + if interface: + cmd = "{} {}".format(cmd, interface) + + if next_hop: + cmd = "{} {}".format(cmd, next_hop) + + if nexthop_vrf: + cmd = "{} nexthop-vrf {}".format(cmd, nexthop_vrf) + + if vrf: + cmd = "{} vrf {}".format(cmd, vrf) if tag: cmd = "{} tag {}".format(cmd, str(tag)) @@ -1080,7 +1405,7 @@ def create_static_routes(tgen, input_dict, build=False): logger.error(errormsg) return errormsg - logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + logger.debug("Exiting lib API: create_static_routes()") return result @@ -1433,6 +1758,9 @@ def create_route_maps(tgen, input_dict, build=False): "large_community_list", {} ) + metric = match_data.setdefault("metric", None) + source_vrf = match_data.setdefault("source-vrf", None) + if ipv4_data: # fetch prefix list data from rmap prefix_name = ipv4_data.setdefault("prefix_lists", None) @@ -1525,6 +1853,14 @@ def create_route_maps(tgen, input_dict, build=False): cmd = "{} exact-match".format(cmd) rmap_data.append(cmd) + if source_vrf: + cmd = "match source-vrf {}".format(source_vrf) + rmap_data.append(cmd) + + if metric: + cmd = "match metric {}".format(metric) + rmap_data.append(cmd) + result = create_common_configuration( tgen, router, rmap_data, "route_maps", build=build ) @@ -1689,17 +2025,97 @@ def shutdown_bringup_interface(tgen, dut, intf_name, ifaceaction=False): interface_set_status(router_list[dut], intf_name, ifaceaction) +def addKernelRoute( + tgen, router, intf, group_addr_range, next_hop=None, src=None, del_action=None +): + """ + Add route to kernel + + Parameters: + ----------- + * `tgen` : Topogen object + * `router`: router for which kernal routes needs to be added + * `intf`: interface name, for which kernal routes needs to be added + * `bindToAddress`: bind to , an interface or multicast + address + + returns: + -------- + errormsg or True + """ + + logger.debug("Entering lib API: addKernelRoute()") + + rnode = tgen.routers()[router] + + if type(group_addr_range) is not list: + group_addr_range = [group_addr_range] + + for grp_addr in group_addr_range: + + addr_type = validate_ip_address(grp_addr) + if addr_type == "ipv4": + if next_hop is not None: + cmd = "ip route add {} via {}".format(grp_addr, next_hop) + else: + cmd = "ip route add {} dev {}".format(grp_addr, intf) + if del_action: + cmd = "ip route del {}".format(grp_addr) + verify_cmd = "ip route" + elif addr_type == "ipv6": + if intf and src: + cmd = "ip -6 route add {} dev {} src {}".format(grp_addr, intf, src) + else: + cmd = "ip -6 route add {} via {}".format(grp_addr, next_hop) + verify_cmd = "ip -6 route" + if del_action: + cmd = "ip -6 route del {}".format(grp_addr) + + logger.info("[DUT: {}]: Running command: [{}]".format(router, cmd)) + output = rnode.run(cmd) + + # Verifying if ip route added to kernal + result = rnode.run(verify_cmd) + logger.debug("{}\n{}".format(verify_cmd, result)) + if "/" in grp_addr: + ip, mask = grp_addr.split("/") + if mask == "32" or mask == "128": + grp_addr = ip + + if not re_search(r"{}".format(grp_addr), result) and mask is not "0": + errormsg = ( + "[DUT: {}]: Kernal route is not added for group" + " address {} Config output: {}".format(router, grp_addr, output) + ) + + return errormsg + + logger.debug("Exiting lib API: addKernelRoute()") + return True + + ############################################# # Verification APIs ############################################# -@retry(attempts=10, return_is_str=True, initial_wait=2) -def verify_rib(tgen, addr_type, dut, input_dict, next_hop=None, protocol=None): +@retry(attempts=5, wait=2, return_is_str=True, initial_wait=2) +def verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=None, + protocol=None, + tag=None, + metric=None, + fib=None, +): """ Data will be read from input_dict or input JSON file, API will generate same prefixes, which were redistributed by either create_static_routes() or advertise_networks_using_network_command() and do will verify next_hop and each prefix/routes is present in "show ip/ipv6 route {bgp/stataic} json" command o/p. + Parameters ---------- * `tgen` : topogen object @@ -1709,12 +2125,14 @@ def verify_rib(tgen, addr_type, dut, input_dict, next_hop=None, protocol=None): * `next_hop`[optional]: next_hop which needs to be verified, default: static * `protocol`[optional]: protocol, default = None + Usage ----- # RIB can be verified for static routes OR network advertised using network command. Following are input_dicts to create static routes and advertise networks using network command. Any one of the input_dict can be passed to verify_rib() to verify routes in DUT"s RIB. + # Creating static routes for r1 input_dict = { "r1": { @@ -1732,186 +2150,328 @@ def verify_rib(tgen, addr_type, dut, input_dict, next_hop=None, protocol=None): dut = "r2" protocol = "bgp" result = verify_rib(tgen, "ipv4", dut, input_dict, protocol = protocol) + Returns ------- errormsg(str) or True """ - logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + logger.info("Entering lib API: verify_rib()") router_list = tgen.routers() + additional_nexthops_in_required_nhs = [] + found_hops = [] for routerInput in input_dict.keys(): for router, rnode in router_list.iteritems(): if router != dut: continue + logger.info("Checking router %s RIB:", router) + # Verifying RIB routes if addr_type == "ipv4": - if protocol: - command = "show ip route {} json".format(protocol) - else: - command = "show ip route json" + command = "show ip route" else: - if protocol: - command = "show ipv6 route {} json".format(protocol) - else: - command = "show ipv6 route json" - - logger.info("Checking router %s RIB:", router) - rib_routes_json = run_frr_cmd(rnode, command, isjson=True) + command = "show ipv6 route" - # Verifying output dictionary rib_routes_json is not empty - if bool(rib_routes_json) is False: - errormsg = "No {} route found in rib of router {}..".format( - protocol, router - ) - return errormsg + found_routes = [] + missing_routes = [] if "static_routes" in input_dict[routerInput]: static_routes = input_dict[routerInput]["static_routes"] - st_found = False - nh_found = False - found_routes = [] - missing_routes = [] + for static_route in static_routes: + if "vrf" in static_route and static_route["vrf"] is not None: + + logger.info( + "[DUT: {}]: Verifying routes for VRF:" + " {}".format(router, static_route["vrf"]) + ) + + cmd = "{} vrf {}".format(command, static_route["vrf"]) + + else: + cmd = "{}".format(command) + + if protocol: + cmd = "{} {}".format(cmd, protocol) + + cmd = "{} json".format(cmd) + + rib_routes_json = run_frr_cmd(rnode, cmd, isjson=True) + + # Verifying output dictionary rib_routes_json is not empty + if bool(rib_routes_json) is False: + errormsg = "No route found in rib of router {}..".format(router) + return errormsg + network = static_route["network"] if "no_of_ip" in static_route: no_of_ip = static_route["no_of_ip"] else: no_of_ip = 1 + if "tag" in static_route: + _tag = static_route["tag"] + else: + _tag = None + # Generating IPs for verification ip_list = generate_ips(network, no_of_ip) + st_found = False + nh_found = False + for st_rt in ip_list: st_rt = str(ipaddr.IPNetwork(unicode(st_rt))) + _addr_type = validate_ip_address(st_rt) + if _addr_type != addr_type: + continue + if st_rt in rib_routes_json: st_found = True found_routes.append(st_rt) - if next_hop: + if fib and next_hop: if type(next_hop) is not list: next_hop = [next_hop] + for mnh in range(0, len(rib_routes_json[st_rt])): + if ( + "fib" + in rib_routes_json[st_rt][mnh]["nexthops"][0] + ): + found_hops.append( + [ + rib_r["ip"] + for rib_r in rib_routes_json[st_rt][ + mnh + ]["nexthops"] + ] + ) + + if found_hops[0]: + missing_list_of_nexthops = set( + found_hops[0] + ).difference(next_hop) + additional_nexthops_in_required_nhs = set( + next_hop + ).difference(found_hops[0]) + + if additional_nexthops_in_required_nhs: + logger.info( + "Nexthop " + "%s is not active for route %s in " + "RIB of router %s\n", + additional_nexthops_in_required_nhs, + st_rt, + dut, + ) + errormsg = ( + "Nexthop {} is not active" + " for route {} in RIB of router" + " {}\n".format( + additional_nexthops_in_required_nhs, + st_rt, + dut, + ) + ) + return errormsg + else: + nh_found = True + + elif next_hop and fib is None: + if type(next_hop) is not list: + next_hop = [next_hop] found_hops = [ rib_r["ip"] for rib_r in rib_routes_json[st_rt][0]["nexthops"] ] - for nh in found_hops: - nh_found = False - if nh and nh in next_hop: - nh_found = True - else: + + if found_hops: + missing_list_of_nexthops = set( + found_hops + ).difference(next_hop) + additional_nexthops_in_required_nhs = set( + next_hop + ).difference(found_hops) + + if additional_nexthops_in_required_nhs: + logger.info( + "Missing nexthop %s for route" + " %s in RIB of router %s\n", + additional_nexthops_in_required_nhs, + st_rt, + dut, + ) errormsg = ( - "Nexthop {} is Missing for {}" - " route {} in RIB of router" - " {}\n".format( - next_hop, protocol, st_rt, dut + "Nexthop {} is Missing for " + "route {} in RIB of router {}\n".format( + additional_nexthops_in_required_nhs, + st_rt, + dut, ) ) - return errormsg + else: + nh_found = True + + if tag: + if "tag" not in rib_routes_json[st_rt][0]: + errormsg = ( + "[DUT: {}]: tag is not" + " present for" + " route {} in RIB \n".format(dut, st_rt) + ) + return errormsg + + if _tag != rib_routes_json[st_rt][0]["tag"]: + errormsg = ( + "[DUT: {}]: tag value {}" + " is not matched for" + " route {} in RIB \n".format(dut, _tag, st_rt,) + ) + return errormsg + + if metric is not None: + if "metric" not in rib_routes_json[st_rt][0]: + errormsg = ( + "[DUT: {}]: metric is" + " not present for" + " route {} in RIB \n".format(dut, st_rt) + ) + return errormsg + + if metric != rib_routes_json[st_rt][0]["metric"]: + errormsg = ( + "[DUT: {}]: metric value " + "{} is not matched for " + "route {} in RIB \n".format(dut, metric, st_rt,) + ) + return errormsg + else: missing_routes.append(st_rt) if nh_found: logger.info( - "Found next_hop %s for all routes in RIB of" " router %s\n", - next_hop, - dut, + "[DUT: {}]: Found next_hop {} for all bgp" + " routes in RIB".format(router, next_hop) ) - if not st_found and len(missing_routes) > 0: - errormsg = ( - "Missing route in RIB of router {}, routes: " - "{}\n".format(dut, missing_routes) + if len(missing_routes) > 0: + errormsg = "[DUT: {}]: Missing route in RIB, " "routes: {}".format( + dut, missing_routes ) return errormsg - logger.info( - "Verified routes in router %s RIB, found routes" " are: %s\n", - dut, - found_routes, - ) + if found_routes: + logger.info( + "[DUT: %s]: Verified routes in RIB, found" " routes are: %s\n", + dut, + found_routes, + ) continue if "bgp" in input_dict[routerInput]: if ( "advertise_networks" - in input_dict[routerInput]["bgp"]["address_family"][addr_type][ + not in input_dict[routerInput]["bgp"]["address_family"][addr_type][ "unicast" ] ): + continue - found_routes = [] - missing_routes = [] - advertise_network = input_dict[routerInput]["bgp"][ - "address_family" - ][addr_type]["unicast"]["advertise_networks"] + found_routes = [] + missing_routes = [] + advertise_network = input_dict[routerInput]["bgp"]["address_family"][ + addr_type + ]["unicast"]["advertise_networks"] - for advertise_network_dict in advertise_network: - start_ip = advertise_network_dict["network"] - if "no_of_network" in advertise_network_dict: - no_of_network = advertise_network_dict["no_of_network"] - else: - no_of_network = 1 - - # Generating IPs for verification - ip_list = generate_ips(start_ip, no_of_network) - for st_rt in ip_list: - st_rt = str(ipaddr.IPNetwork(unicode(st_rt))) - - found = False - nh_found = False - if st_rt in rib_routes_json: - found = True - found_routes.append(st_rt) - - if next_hop: - if type(next_hop) is not list: - next_hop = [next_hop] - - for nh in next_hop: - for nh_json in rib_routes_json[st_rt][0][ - "nexthops" - ]: - if nh != nh_json["ip"]: - continue - nh_found = True - - if not nh_found: - errormsg = ( - "Nexthop {} is Missing" - " for {} route {} in " - "RIB of router {}\n".format( - next_hop, protocol, st_rt, dut - ) - ) - return errormsg + # Continue if there are no network advertise + if len(advertise_network) == 0: + continue + + for advertise_network_dict in advertise_network: + if "vrf" in advertise_network_dict: + cmd = "{} vrf {} json".format(command, static_route["vrf"]) + else: + cmd = "{} json".format(command) + + rib_routes_json = run_frr_cmd(rnode, cmd, isjson=True) + + # Verifying output dictionary rib_routes_json is not empty + if bool(rib_routes_json) is False: + errormsg = "No route found in rib of router {}..".format(router) + return errormsg + + start_ip = advertise_network_dict["network"] + if "no_of_network" in advertise_network_dict: + no_of_network = advertise_network_dict["no_of_network"] + else: + no_of_network = 1 + + # Generating IPs for verification + ip_list = generate_ips(start_ip, no_of_network) + st_found = False + nh_found = False + + for st_rt in ip_list: + st_rt = str(ipaddr.IPNetwork(unicode(st_rt))) + + _addr_type = validate_ip_address(st_rt) + if _addr_type != addr_type: + continue + + if st_rt in rib_routes_json: + st_found = True + found_routes.append(st_rt) + + if next_hop: + if type(next_hop) is not list: + next_hop = [next_hop] + + count = 0 + for nh in next_hop: + for nh_dict in rib_routes_json[st_rt][0]["nexthops"]: + if nh_dict["ip"] != nh: + continue + else: + count += 1 + if count == len(next_hop): + nh_found = True else: - missing_routes.append(st_rt) + errormsg = ( + "Nexthop {} is Missing" + " for route {} in " + "RIB of router {}\n".format(next_hop, st_rt, dut) + ) + return errormsg + else: + missing_routes.append(st_rt) - if nh_found: - logger.info( - "Found next_hop {} for all routes in RIB" - " of router {}\n".format(next_hop, dut) - ) + if nh_found: + logger.info( + "Found next_hop {} for all routes in RIB" + " of router {}\n".format(next_hop, dut) + ) - if not found and len(missing_routes) > 0: - errormsg = ( - "Missing {} route in RIB of router {}, " - "routes: {} \n".format(addr_type, dut, missing_routes) - ) - return errormsg + if len(missing_routes) > 0: + errormsg = ( + "Missing {} route in RIB of router {}, " + "routes: {} \n".format(addr_type, dut, missing_routes) + ) + return errormsg + if found_routes: logger.info( "Verified {} routes in router {} RIB, found" " routes are: {}\n".format(addr_type, dut, found_routes) ) - logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + logger.info("Exiting lib API: verify_rib()") return True diff --git a/tests/topotests/lib/topojson.py b/tests/topotests/lib/topojson.py index b25317ba7f..24b61981d6 100644 --- a/tests/topotests/lib/topojson.py +++ b/tests/topotests/lib/topojson.py @@ -37,6 +37,7 @@ from lib.common_config import ( create_prefix_lists, create_route_maps, create_bgp_community_lists, + create_vrf_cfg, ) from lib.bgp import create_router_bgp @@ -49,7 +50,6 @@ def build_topo_from_json(tgen, topo): Reads configuration from JSON file. Adds routers, creates interface names dynamically and link routers as defined in JSON to create topology. Assigns IPs dynamically to all interfaces of each router. - * `tgen`: Topogen object * `topo`: json file data """ @@ -203,6 +203,7 @@ def build_config_from_json(tgen, topo, save_bkup=True): func_dict = OrderedDict( [ + ("vrfs", create_vrf_cfg), ("links", create_interfaces_cfg), ("static_routes", create_static_routes), ("prefix_lists", create_prefix_lists),