]> git.puffer.fish Git - matthieu/frr.git/commitdiff
tests: Adding ospfv3 basic functionality test cases
authornguggarigoud <nguggarigoud@vmware.com>
Tue, 1 Jun 2021 03:22:59 +0000 (20:22 -0700)
committernguggarigoud <nguggarigoud@vmware.com>
Tue, 20 Jul 2021 09:34:24 +0000 (02:34 -0700)
1. Adding  APIs to configure and verify ospfv3.
2. Adding ospfv3 base functionality testcase.

Signed-off-by: nguggarigoud <nguggarigoud@vmware.com>
tests/topotests/lib/common_config.py
tests/topotests/lib/ospf.py
tests/topotests/ospfv3_basic_functionality/ospfv3_ecmp.json [new file with mode: 0644]
tests/topotests/ospfv3_basic_functionality/ospfv3_routemaps.json [new file with mode: 0644]
tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp.py [new file with mode: 0644]
tests/topotests/ospfv3_basic_functionality/test_ospfv3_routemaps.py [new file with mode: 0644]
tests/topotests/ospfv3_basic_functionality/test_ospfv3_rte_calc.py
tests/topotests/ospfv3_basic_functionality/test_ospfv3_single_area.py

index 6a02e501270257b7c5ab0de66a2c0fcf81a4e07d..07bb5153ab8f9ab54cfb841a436b7baf0c485246 100644 (file)
@@ -1859,7 +1859,7 @@ def create_interfaces_cfg(tgen, topo, build=False):
                     )
                 if "ospf6" in data:
                     interface_data += _create_interfaces_ospf_cfg(
-                        "ospf6", c_data, data, ospf_keywords
+                        "ospf6", c_data, data, ospf_keywords + ["area"]
                     )
 
             result = create_common_configuration(
index dc9fe0fccaeb008e328e7d55cbb7a3e19b05f3e4..6aa7a2c0a94c22140d0278bd0f77e432efe160f4 100644 (file)
@@ -28,6 +28,7 @@ from time import sleep
 from lib.topolog import logger
 from lib.topotest import frr_unicode
 from ipaddress import IPv6Address
+
 # Import common_config to use commomnly used APIs
 from lib.common_config import (
     create_common_configuration,
@@ -89,8 +90,7 @@ def create_router_ospf(tgen, topo, input_dict=None, build=False, load_config=Tru
             logger.debug("Router %s: 'ospf' not present in input_dict", router)
             continue
 
-        result = __create_ospf_global(
-            tgen, input_dict, router, build, load_config)
+        result = __create_ospf_global(tgen, input_dict, router, build, load_config)
         if result is True:
             ospf_data = input_dict[router]["ospf"]
 
@@ -100,7 +100,8 @@ def create_router_ospf(tgen, topo, input_dict=None, build=False, load_config=Tru
             continue
 
         result = __create_ospf_global(
-            tgen, input_dict, router, build, load_config, ospf='ospf6')
+            tgen, input_dict, router, build, load_config, ospf="ospf6"
+        )
         if result is True:
             ospf_data = input_dict[router]["ospf6"]
 
@@ -172,7 +173,6 @@ def __create_ospf_global(
 
         config_data.append(cmd)
 
-
         # router id
         router_id = ospf_data.setdefault("router_id", None)
         del_router_id = ospf_data.setdefault("del_router_id", False)
@@ -187,8 +187,7 @@ def __create_ospf_global(
         if del_log_adj_changes:
             config_data.append("no log-adjacency-changes detail")
         if log_adj_changes:
-            config_data.append("log-adjacency-changes {}".format(
-                log_adj_changes))
+            config_data.append("log-adjacency-changes {}".format(log_adj_changes))
 
         # aggregation timer
         aggr_timer = ospf_data.setdefault("aggr_timer", None)
@@ -196,8 +195,7 @@ def __create_ospf_global(
         if del_aggr_timer:
             config_data.append("no aggregation timer")
         if aggr_timer:
-            config_data.append("aggregation timer {}".format(
-                aggr_timer))
+            config_data.append("aggregation timer {}".format(aggr_timer))
 
         # maximum path information
         ecmp_data = ospf_data.setdefault("maximum-paths", {})
@@ -245,12 +243,13 @@ def __create_ospf_global(
                         cmd = "no {}".format(cmd)
                     config_data.append(cmd)
 
-        #def route information
+        # def route information
         def_rte_data = ospf_data.setdefault("default-information", {})
         if def_rte_data:
             if "originate" not in def_rte_data:
-                logger.debug("Router %s: 'originate key' not present in "
-                            "input_dict", router)
+                logger.debug(
+                    "Router %s: 'originate key' not present in " "input_dict", router
+                )
             else:
                 cmd = "default-information originate"
 
@@ -261,12 +260,10 @@ def __create_ospf_global(
                     cmd = cmd + " metric {}".format(def_rte_data["metric"])
 
                 if "metric-type" in def_rte_data:
-                    cmd = cmd + " metric-type {}".format(def_rte_data[
-                        "metric-type"])
+                    cmd = cmd + " metric-type {}".format(def_rte_data["metric-type"])
 
                 if "route-map" in def_rte_data:
-                    cmd = cmd + " route-map {}".format(def_rte_data[
-                        "route-map"])
+                    cmd = cmd + " route-map {}".format(def_rte_data["route-map"])
 
                 del_action = def_rte_data.setdefault("delete", False)
                 if del_action:
@@ -288,19 +285,19 @@ def __create_ospf_global(
                         config_data.append(cmd)
 
                     try:
-                        if "area" in input_dict[router]['links'][neighbor][
-                            'ospf6']:
+                        if "area" in input_dict[router]["links"][neighbor]["ospf6"]:
                             iface = input_dict[router]["links"][neighbor]["interface"]
                             cmd = "interface {} area {}".format(
-                                iface, input_dict[router]['links'][neighbor][
-                            'ospf6']['area'])
-                            if input_dict[router]['links'][neighbor].setdefault(
-                                "delete", False):
+                                iface,
+                                input_dict[router]["links"][neighbor]["ospf6"]["area"],
+                            )
+                            if input_dict[router]["links"][neighbor].setdefault(
+                                "delete", False
+                            ):
                                 cmd = "no {}".format(cmd)
                             config_data.append(cmd)
                     except KeyError:
-                            pass
-
+                        pass
 
         # summary information
         summary_data = ospf_data.setdefault("summary-address", {})
@@ -420,6 +417,7 @@ def config_ospf_interface(tgen, topo, input_dict=None, build=False, load_config=
     True or False
     """
     logger.debug("Enter lib config_ospf_interface")
+    result = False
     if not input_dict:
         input_dict = deepcopy(topo)
     else:
@@ -502,7 +500,7 @@ def config_ospf_interface(tgen, topo, input_dict=None, build=False, load_config=
             # interface ospf mtu
             if data_ospf_mtu:
                 cmd = "ip ospf mtu-ignore"
-                if 'del_action' in ospf_data:
+                if "del_action" in ospf_data:
                     cmd = "no {}".format(cmd)
                 config_data.append(cmd)
 
@@ -543,8 +541,7 @@ def clear_ospf(tgen, router, ospf=None):
         version = "ip"
 
     cmd = "clear {} ospf interface".format(version)
-    logger.info(
-        "Clearing ospf process on router %s.. using command '%s'", router, cmd)
+    logger.info("Clearing ospf process on router %s.. using command '%s'", router, cmd)
     run_frr_cmd(rnode, cmd)
 
     logger.debug("Exiting lib API: clear_ospf()")
@@ -774,7 +771,7 @@ def verify_ospf_neighbor(tgen, topo, dut=None, input_dict=None, lan=False, expec
 ################################
 # Verification procs
 ################################
-@retry(retry_timeout=20)
+@retry(retry_timeout=50)
 def verify_ospf6_neighbor(tgen, topo, dut=None, input_dict=None, lan=False):
     """
     This API is to verify ospf neighborship by running
@@ -825,105 +822,133 @@ def verify_ospf6_neighbor(tgen, topo, dut=None, input_dict=None, lan=False):
 
     if input_dict:
         for router, rnode in tgen.routers().items():
-            if 'ospf6' not in topo['routers'][router]:
+            if "ospf6" not in topo["routers"][router]:
                 continue
 
             if dut is not None and dut != router:
                 continue
 
             logger.info("Verifying OSPF neighborship on router %s:", router)
-            show_ospf_json = run_frr_cmd(rnode,
-                "show ipv6 ospf neighbor json", isjson=True)
+            show_ospf_json = run_frr_cmd(
+                rnode, "show ipv6 ospf neighbor json", isjson=True
+            )
             # Verifying output dictionary show_ospf_json is empty or not
             if not bool(show_ospf_json):
                 errormsg = "OSPF6 is not running"
                 return errormsg
 
             ospf_data_list = input_dict[router]["ospf6"]
-            ospf_nbr_list = ospf_data_list['neighbors']
+            ospf_nbr_list = ospf_data_list["neighbors"]
 
             for ospf_nbr, nbr_data in ospf_nbr_list.items():
-                data_ip = data_rid = topo['routers'][ospf_nbr]['ospf6']['router_id']
+
+                try:
+                    data_ip = data_rid = topo["routers"][ospf_nbr]["ospf6"]["router_id"]
+                except KeyError:
+                    data_ip = data_rid = topo["routers"][nbr_data["nbr"]]["ospf6"][
+                        "router_id"
+                    ]
+
                 if ospf_nbr in data_ip:
                     nbr_details = nbr_data[ospf_nbr]
                 elif lan:
-                    for switch in topo['switches']:
-                        if 'ospf6' in topo['switches'][switch]['links'][router]:
+                    for switch in topo["switches"]:
+                        if "ospf6" in topo["switches"][switch]["links"][router]:
                             neighbor_ip = data_ip
                         else:
                             continue
                 else:
-                    neighbor_ip = data_ip[router]['ipv6'].split("/")[0]
+                    neighbor_ip = data_ip[router]["ipv6"].split("/")[0]
 
                 nh_state = None
                 neighbor_ip = neighbor_ip.lower()
                 nbr_rid = data_rid
-                get_index_val = dict((d['neighborId'], dict( \
-                        d, index=index)) for (index, d) in enumerate( \
-                            show_ospf_json['neighbors']))
+                get_index_val = dict(
+                    (d["neighborId"], dict(d, index=index))
+                    for (index, d) in enumerate(show_ospf_json["neighbors"])
+                )
                 try:
-                    nh_state =  get_index_val.get(neighbor_ip)['state']
-                    intf_state = get_index_val.get(neighbor_ip)['ifState']
+                    nh_state = get_index_val.get(neighbor_ip)["state"]
+                    intf_state = get_index_val.get(neighbor_ip)["ifState"]
                 except TypeError:
-                    errormsg = "[DUT: {}] OSPF peer {} missing,from "\
-                        "{} ".format(router,
-                    nbr_rid, ospf_nbr)
+                    errormsg = "[DUT: {}] OSPF peer {} missing,from " "{} ".format(
+                        router, nbr_rid, ospf_nbr
+                    )
                     return errormsg
 
-                nbr_state = nbr_data.setdefault("state",None)
-                nbr_role = nbr_data.setdefault("role",None)
+                nbr_state = nbr_data.setdefault("state", None)
+                nbr_role = nbr_data.setdefault("role", None)
 
                 if nbr_state:
                     if nbr_state == nh_state:
-                        logger.info("[DUT: {}] OSPF6 Nbr is {}:{} State {}".format
-                        (router, ospf_nbr, nbr_rid, nh_state))
+                        logger.info(
+                            "[DUT: {}] OSPF6 Nbr is {}:{} State {}".format(
+                                router, ospf_nbr, nbr_rid, nh_state
+                            )
+                        )
                         result = True
                     else:
-                        errormsg = ("[DUT: {}] OSPF6 is not Converged, neighbor"
-                        " state is {} , Expected state is {}".format(router,
-                        nh_state, nbr_state))
+                        errormsg = (
+                            "[DUT: {}] OSPF6 is not Converged, neighbor"
+                            " state is {} , Expected state is {}".format(
+                                router, nh_state, nbr_state
+                            )
+                        )
                         return errormsg
                 if nbr_role:
                     if nbr_role == intf_state:
-                        logger.info("[DUT: {}] OSPF6 Nbr is {}: {} Role {}".format(
-                        router, ospf_nbr, nbr_rid, nbr_role))
+                        logger.info(
+                            "[DUT: {}] OSPF6 Nbr is {}: {} Role {}".format(
+                                router, ospf_nbr, nbr_rid, nbr_role
+                            )
+                        )
                     else:
-                        errormsg = ("[DUT: {}] OSPF6 is not Converged with rid"
-                        "{}, role is {}, Expected role is {}".format(router,
-                        nbr_rid, intf_state, nbr_role))
+                        errormsg = (
+                            "[DUT: {}] OSPF6 is not Converged with rid"
+                            "{}, role is {}, Expected role is {}".format(
+                                router, nbr_rid, intf_state, nbr_role
+                            )
+                        )
                         return errormsg
                 continue
     else:
 
         for router, rnode in tgen.routers().items():
-            if 'ospf6' not in topo['routers'][router]:
+            if "ospf6" not in topo["routers"][router]:
                 continue
 
             if dut is not None and dut != router:
                 continue
 
             logger.info("Verifying OSPF6 neighborship on router %s:", router)
-            show_ospf_json = run_frr_cmd(rnode,
-                "show ipv6 ospf neighbor json", isjson=True)
+            show_ospf_json = run_frr_cmd(
+                rnode, "show ipv6 ospf neighbor json", isjson=True
+            )
             # Verifying output dictionary show_ospf_json is empty or not
             if not bool(show_ospf_json):
                 errormsg = "OSPF6 is not running"
                 return errormsg
 
             ospf_data_list = topo["routers"][router]["ospf6"]
-            ospf_neighbors = ospf_data_list['neighbors']
+            ospf_neighbors = ospf_data_list["neighbors"]
             total_peer = 0
             total_peer = len(ospf_neighbors.keys())
             no_of_ospf_nbr = 0
-            ospf_nbr_list = ospf_data_list['neighbors']
+            ospf_nbr_list = ospf_data_list["neighbors"]
             no_of_peer = 0
             for ospf_nbr, nbr_data in ospf_nbr_list.items():
-                data_ip = data_rid = topo['routers'][ospf_nbr]['ospf6']['router_id']
+                try:
+                    data_ip = data_rid = topo["routers"][ospf_nbr]["ospf6"]["router_id"]
+                except KeyError:
+                    data_ip = data_rid = topo["routers"][nbr_data["nbr"]]["ospf6"][
+                        "router_id"
+                    ]
+
                 if ospf_nbr in data_ip:
                     nbr_details = nbr_data[ospf_nbr]
                 elif lan:
-                    for switch in topo['switches']:
-                        if 'ospf6' in topo['switches'][switch]['links'][router]:
+                    for switch in topo["switches"]:
+                        if "ospf6" in topo["switches"][switch]["links"][router]:
                             neighbor_ip = data_ip
                         else:
                             continue
@@ -933,26 +958,27 @@ def verify_ospf6_neighbor(tgen, topo, dut=None, input_dict=None, lan=False):
                 nh_state = None
                 neighbor_ip = neighbor_ip.lower()
                 nbr_rid = data_rid
-                get_index_val = dict((d['neighborId'], dict( \
-                        d, index=index)) for (index, d) in enumerate( \
-                            show_ospf_json['neighbors']))
+                get_index_val = dict(
+                    (d["neighborId"], dict(d, index=index))
+                    for (index, d) in enumerate(show_ospf_json["neighbors"])
+                )
                 try:
-                    nh_state =  get_index_val.get(neighbor_ip)['state']
-                    intf_state = get_index_val.get(neighbor_ip)['ifState']
+                    nh_state = get_index_val.get(neighbor_ip)["state"]
+                    intf_state = get_index_val.get(neighbor_ip)["ifState"]
                 except TypeError:
-                    errormsg = "[DUT: {}] OSPF peer {} missing,from "\
-                        "{} ".format(router,
-                    nbr_rid, ospf_nbr)
+                    errormsg = "[DUT: {}] OSPF peer {} missing,from " "{} ".format(
+                        router, nbr_rid, ospf_nbr
+                    )
                     return errormsg
 
-                if nh_state == 'Full':
+                if nh_state == "Full":
                     no_of_peer += 1
 
             if no_of_peer == total_peer:
                 logger.info("[DUT: {}] OSPF6 is Converged".format(router))
                 result = True
             else:
-                errormsg = ("[DUT: {}] OSPF6 is not Converged".format(router))
+                errormsg = "[DUT: {}] OSPF6 is not Converged".format(router)
                 return errormsg
 
     logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
@@ -1627,31 +1653,34 @@ def verify_ospf6_rib(tgen, dut, input_dict, next_hop=None,
             found_routes = []
             missing_routes = []
 
-            if "static_routes" in input_dict[routerInput] or \
-                "prefix" in input_dict[routerInput]:
+            if (
+                "static_routes" in input_dict[routerInput]
+                or "prefix" in input_dict[routerInput]
+            ):
                 if "prefix" in input_dict[routerInput]:
                     static_routes = input_dict[routerInput]["prefix"]
                 else:
                     static_routes = input_dict[routerInput]["static_routes"]
 
-
                 for static_route in static_routes:
                     cmd = "{}".format(command)
 
                     cmd = "{} json".format(cmd)
 
-                    ospf_rib_json =  run_frr_cmd(rnode, cmd, isjson=True)
+                    ospf_rib_json = run_frr_cmd(rnode, cmd, isjson=True)
 
                     # Fix for PR 2644182
                     try:
-                        ospf_rib_json = ospf_rib_json['routes']
+                        ospf_rib_json = ospf_rib_json["routes"]
                     except KeyError:
                         pass
 
                     # Verifying output dictionary ospf_rib_json is not empty
                     if bool(ospf_rib_json) is False:
-                        errormsg = "[DUT: {}] No routes found in OSPF6 route " \
+                        errormsg = (
+                            "[DUT: {}] No routes found in OSPF6 route "
                             "table".format(router)
+                        )
                         return errormsg
 
                     network = static_route["network"]
@@ -1659,7 +1688,6 @@ def verify_ospf6_rib(tgen, dut, input_dict, next_hop=None,
                     _tag = static_route.setdefault("tag", None)
                     _rtype = static_route.setdefault("routeType", None)
 
-
                     # Generating IPs for verification
                     ip_list = generate_ips(network, no_of_ip)
                     st_found = False
@@ -1668,7 +1696,7 @@ def verify_ospf6_rib(tgen, dut, input_dict, next_hop=None,
                         st_rt = str(ipaddress.ip_network(frr_unicode(st_rt)))
 
                         _addr_type = validate_ip_address(st_rt)
-                        if _addr_type != 'ipv6':
+                        if _addr_type != "ipv6":
                             continue
 
                         if st_rt in ospf_rib_json:
@@ -1681,17 +1709,26 @@ def verify_ospf6_rib(tgen, dut, input_dict, next_hop=None,
                                     next_hop = [next_hop]
 
                                 for mnh in range(0, len(ospf_rib_json[st_rt])):
-                                    if 'fib' in ospf_rib_json[st_rt][
-                                        mnh]["nextHops"][0]:
-                                        found_hops.append([rib_r[
-                                            "ip"] for rib_r in ospf_rib_json[
-                                                st_rt][mnh]["nextHops"]])
+                                    if (
+                                        "fib"
+                                        in ospf_rib_json[st_rt][mnh]["nextHops"][0]
+                                    ):
+                                        found_hops.append(
+                                            [
+                                                rib_r["ip"]
+                                                for rib_r in ospf_rib_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])
+                                    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(
@@ -1699,13 +1736,18 @@ def verify_ospf6_rib(tgen, dut, input_dict, next_hop=None,
                                             "%s is not active for route %s in "
                                             "RIB of router %s\n",
                                             additional_nexthops_in_required_nhs,
-                                            st_rt, dut)
+                                            st_rt,
+                                            dut,
+                                        )
                                         errormsg = (
                                             "Nexthop {} is not active"
                                             " for route {} in RIB of router"
                                             " {}\n".format(
-                                            additional_nexthops_in_required_nhs,
-                                            st_rt, dut))
+                                                additional_nexthops_in_required_nhs,
+                                                st_rt,
+                                                dut,
+                                            )
+                                        )
                                         return errormsg
                                     else:
                                         nh_found = True
@@ -1713,98 +1755,118 @@ def verify_ospf6_rib(tgen, dut, input_dict, next_hop=None,
                             elif next_hop and fib is None:
                                 if type(next_hop) is not list:
                                     next_hop = [next_hop]
-                                found_hops = [rib_r['nextHop'] for rib_r in
-                                              ospf_rib_json[st_rt][
-                                    "nextHops"]]
+                                found_hops = [
+                                    rib_r["nextHop"]
+                                    for rib_r in ospf_rib_json[st_rt]["nextHops"]
+                                ]
 
                                 if found_hops:
-                                    missing_list_of_nexthops = \
-                                        set(found_hops).difference(next_hop)
-                                    additional_nexthops_in_required_nhs = \
-                                        set(next_hop).difference(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(
+                                            "Missing nexthop %s for route"
+                                            " %s in RIB of router %s\n",
                                             additional_nexthops_in_required_nhs,
-                                            st_rt, dut))
+                                            st_rt,
+                                            dut,
+                                        )
+                                        errormsg = (
+                                            "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 _rtype:
-                                if "destinationType" not in ospf_rib_json[
-                                    st_rt]:
-                                    errormsg = ("[DUT: {}]: destinationType missing"
-                                                "for route {} in OSPF RIB \n".\
-                                                format(dut, st_rt))
+                                if "destinationType" not in ospf_rib_json[st_rt]:
+                                    errormsg = (
+                                        "[DUT: {}]: destinationType missing"
+                                        "for route {} in OSPF RIB \n".format(dut, st_rt)
+                                    )
                                     return errormsg
-                                elif _rtype != ospf_rib_json[st_rt][
-                                    "destinationType"]:
-                                    errormsg = ("[DUT: {}]: destinationType mismatch"
-                                                "for route {} in OSPF RIB \n".\
-                                                format(dut, st_rt))
+                                elif _rtype != ospf_rib_json[st_rt]["destinationType"]:
+                                    errormsg = (
+                                        "[DUT: {}]: destinationType mismatch"
+                                        "for route {} in OSPF RIB \n".format(dut, st_rt)
+                                    )
                                     return errormsg
                                 else:
-                                    logger.info("DUT: {}]: Found destinationType {}"
-                                                "for route {}".\
-                                                format(dut, _rtype, st_rt))
+                                    logger.info(
+                                        "DUT: {}]: Found destinationType {}"
+                                        "for route {}".format(dut, _rtype, st_rt)
+                                    )
                             if tag:
-                                if "tag" not in ospf_rib_json[
-                                    st_rt]:
-                                    errormsg = ("[DUT: {}]: tag is not"
-                                                " present for"
-                                                " route {} in RIB \n".\
-                                                format(dut, st_rt
-                                                ))
+                                if "tag" not in ospf_rib_json[st_rt]:
+                                    errormsg = (
+                                        "[DUT: {}]: tag is not"
+                                        " present for"
+                                        " route {} in RIB \n".format(dut, st_rt)
+                                    )
                                     return errormsg
 
-                                if _tag != ospf_rib_json[
-                                    st_rt]["tag"]:
-                                    errormsg = ("[DUT: {}]: tag value {}"
-                                                " is not matched for"
-                                                " route {} in RIB \n".\
-                                                format(dut, _tag, st_rt,
-                                                ))
+                                if _tag != ospf_rib_json[st_rt]["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 "type2cost" not in ospf_rib_json[
-                                    st_rt]:
-                                    errormsg = ("[DUT: {}]: metric is"
-                                                " not present for"
-                                                " route {} in RIB \n".\
-                                                format(dut, st_rt))
+                                if "type2cost" not in ospf_rib_json[st_rt]:
+                                    errormsg = (
+                                        "[DUT: {}]: metric is"
+                                        " not present for"
+                                        " route {} in RIB \n".format(dut, st_rt)
+                                    )
                                     return errormsg
 
-                                if metric != ospf_rib_json[
-                                    st_rt]["type2cost"]:
-                                    errormsg = ("[DUT: {}]: metric value "
-                                                "{} is not matched for "
-                                                "route {} in RIB \n".\
-                                                format(dut, metric, st_rt,
-                                                ))
+                                if metric != ospf_rib_json[st_rt]["type2cost"]:
+                                    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("[DUT: {}]: Found next_hop {} for all OSPF"
-                                " routes in RIB".format(router, next_hop))
+                    logger.info(
+                        "[DUT: {}]: Found next_hop {} for all OSPF"
+                        " routes in RIB".format(router, next_hop)
+                    )
 
                 if len(missing_routes) > 0:
-                    errormsg = ("[DUT: {}]: Missing route in RIB, "
-                                "routes: {}".\
-                                    format(dut, missing_routes))
+                    errormsg = "[DUT: {}]: Missing route in RIB, " "routes: {}".format(
+                        dut, missing_routes
+                    )
                     return errormsg
 
                 if found_routes:
-                    logger.info("[DUT: %s]: Verified routes in RIB, found"
-                                " routes are: %s\n", dut, found_routes)
+                    logger.info(
+                        "[DUT: %s]: Verified routes in RIB, found" " routes are: %s\n",
+                        dut,
+                        found_routes,
+                    )
                     result = True
 
     logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
@@ -1855,15 +1917,16 @@ def verify_ospf6_interface(tgen, topo, dut=None,lan=False, input_dict=None):
     result = False
 
     for router, rnode in tgen.routers().iteritems():
-        if 'ospf6' not in topo['routers'][router]:
+        if "ospf6" not in topo["routers"][router]:
             continue
 
         if dut is not None and dut != router:
             continue
 
         logger.info("Verifying OSPF interface on router %s:", router)
-        show_ospf_json = run_frr_cmd(rnode, "show ipv6 ospf interface json",
-                                    isjson=True)
+        show_ospf_json = run_frr_cmd(
+            rnode, "show ipv6 ospf interface json", isjson=True
+        )
 
         # Verifying output dictionary show_ospf_json is empty or not
         if not bool(show_ospf_json):
@@ -1873,32 +1936,49 @@ def verify_ospf6_interface(tgen, topo, dut=None,lan=False, input_dict=None):
         # To find neighbor ip type
         ospf_intf_data = input_dict[router]["links"]
         for ospf_intf, intf_data in ospf_intf_data.items():
-            intf = topo['routers'][router]['links'][ospf_intf]['interface']
-            if  intf in show_ospf_json:
-                for intf_attribute in intf_data['ospf6']:
-                    if intf_data['ospf6'][intf_attribute] is not list:
-                        if intf_data['ospf6'][intf_attribute] ==  show_ospf_json[
-                            intf][intf_attribute]:
-                            logger.info("[DUT: %s] OSPF6 interface %s: %s is %s",
-                            router, intf, intf_attribute, intf_data['ospf6'][
-                                intf_attribute])
-                    elif intf_data['ospf6'][intf_attribute] is list:
+            intf = topo["routers"][router]["links"][ospf_intf]["interface"]
+            if intf in show_ospf_json:
+                for intf_attribute in intf_data["ospf6"]:
+                    if intf_data["ospf6"][intf_attribute] is not list:
+                        if (
+                            intf_data["ospf6"][intf_attribute]
+                            == show_ospf_json[intf][intf_attribute]
+                        ):
+                            logger.info(
+                                "[DUT: %s] OSPF6 interface %s: %s is %s",
+                                router,
+                                intf,
+                                intf_attribute,
+                                intf_data["ospf6"][intf_attribute],
+                            )
+                    elif intf_data["ospf6"][intf_attribute] is list:
                         for addr_list in len(show_ospf_json[intf][intf_attribute]):
-                            if show_ospf_json[intf][intf_attribute][addr_list][
-                                'address'].split('/')[0] == intf_data['ospf6'][
-                                    'internetAddress'][0]['address']:
-                                    break
+                            if (
+                                show_ospf_json[intf][intf_attribute][addr_list][
+                                    "address"
+                                ].split("/")[0]
+                                == intf_data["ospf6"]["internetAddress"][0]["address"]
+                            ):
+                                break
                             else:
-                                errormsg= "[DUT: {}] OSPF6 interface {}: {} is {}, \
-                                    Expected is {}".format(router, intf, intf_attribute,
-                                    intf_data['ospf6'][intf_attribute], intf_data['ospf6'][
-                                    intf_attribute])
+                                errormsg = "[DUT: {}] OSPF6 interface {}: {} is {}, \
+                                    Expected is {}".format(
+                                    router,
+                                    intf,
+                                    intf_attribute,
+                                    intf_data["ospf6"][intf_attribute],
+                                    intf_data["ospf6"][intf_attribute],
+                                )
                                 return errormsg
                     else:
-                        errormsg= "[DUT: {}] OSPF6 interface {}: {} is {}, \
-                        Expected is {}".format(router, intf, intf_attribute,
-                        intf_data['ospf6'][intf_attribute], intf_data['ospf6'][
-                            intf_attribute])
+                        errormsg = "[DUT: {}] OSPF6 interface {}: {} is {}, \
+                        Expected is {}".format(
+                            router,
+                            intf,
+                            intf_attribute,
+                            intf_data["ospf6"][intf_attribute],
+                            intf_data["ospf6"][intf_attribute],
+                        )
                         return errormsg
         result = True
     logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
@@ -1956,16 +2036,14 @@ def verify_ospf6_database(tgen, topo, dut, input_dict):
     router = dut
     logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
 
-    if 'ospf' not in topo['routers'][dut]:
-        errormsg = "[DUT: {}] OSPF is not configured on the router.".format(
-            dut)
+    if "ospf" not in topo["routers"][dut]:
+        errormsg = "[DUT: {}] OSPF is not configured on the router.".format(dut)
         return errormsg
 
     rnode = tgen.routers()[dut]
 
     logger.info("Verifying OSPF interface on router %s:", dut)
-    show_ospf_json = run_frr_cmd(rnode, "show ip ospf database json",
-                                isjson=True)
+    show_ospf_json = run_frr_cmd(rnode, "show ip ospf database json", isjson=True)
     # Verifying output dictionary show_ospf_json is empty or not
     if not bool(show_ospf_json):
         errormsg = "OSPF is not running"
@@ -1973,167 +2051,209 @@ def verify_ospf6_database(tgen, topo, dut, input_dict):
 
     # for inter and inter lsa's
     ospf_db_data = input_dict.setdefault("areas", None)
-    ospf_external_lsa = input_dict.setdefault(
-        'asExternalLinkStates', None)
+    ospf_external_lsa = input_dict.setdefault("asExternalLinkStates", None)
 
     if ospf_db_data:
-            for ospf_area, area_lsa in ospf_db_data.items():
-                if ospf_area in show_ospf_json['areas']:
-                    if 'routerLinkStates' in area_lsa:
-                        for lsa in area_lsa['routerLinkStates']:
-                            for rtrlsa in show_ospf_json['areas'][ospf_area][
-                                'routerLinkStates']:
-                                if lsa['lsaId'] == rtrlsa['lsaId'] and \
-                                    lsa['advertisedRouter'] == rtrlsa[
-                                        'advertisedRouter']:
-                                                result = True
-                                                break
-                            if result:
-                                logger.info(
-                                    "[DUT: %s]  OSPF LSDB area %s:Router "
-                                    "LSA %s", router, ospf_area, lsa)
+        for ospf_area, area_lsa in ospf_db_data.items():
+            if ospf_area in show_ospf_json["areas"]:
+                if "routerLinkStates" in area_lsa:
+                    for lsa in area_lsa["routerLinkStates"]:
+                        for rtrlsa in show_ospf_json["areas"][ospf_area][
+                            "routerLinkStates"
+                        ]:
+                            if (
+                                lsa["lsaId"] == rtrlsa["lsaId"]
+                                and lsa["advertisedRouter"]
+                                == rtrlsa["advertisedRouter"]
+                            ):
+                                result = True
                                 break
-                        else:
-                            errormsg = \
-                            "[DUT: {}]  OSPF LSDB area {}: expected" \
+                        if result:
+                            logger.info(
+                                "[DUT: %s]  OSPF LSDB area %s:Router " "LSA %s",
+                                router,
+                                ospf_area,
+                                lsa,
+                            )
+                            break
+                    else:
+                        errormsg = (
+                            "[DUT: {}]  OSPF LSDB area {}: expected"
                             " Router LSA is {}".format(router, ospf_area, lsa)
-                            return errormsg
+                        )
+                        return errormsg
 
-                    if 'networkLinkStates' in area_lsa:
-                        for lsa in area_lsa['networkLinkStates']:
-                            for netlsa in show_ospf_json['areas'][ospf_area][
-                                'networkLinkStates']:
-                                if lsa in show_ospf_json['areas'][ospf_area][
-                                    'networkLinkStates']:
-                                    if lsa['lsaId'] == netlsa['lsaId'] and \
-                                    lsa['advertisedRouter'] == netlsa[
-                                        'advertisedRouter']:
-                                                result = True
-                                                break
-                            if result:
-                                logger.info(
-                                    "[DUT: %s]  OSPF LSDB area %s:Network "
-                                    "LSA %s", router, ospf_area, lsa)
-                                break
-                            else:
-                                errormsg = \
-                                "[DUT: {}]  OSPF LSDB area {}: expected" \
+                if "networkLinkStates" in area_lsa:
+                    for lsa in area_lsa["networkLinkStates"]:
+                        for netlsa in show_ospf_json["areas"][ospf_area][
+                            "networkLinkStates"
+                        ]:
+                            if (
+                                lsa
+                                in show_ospf_json["areas"][ospf_area][
+                                    "networkLinkStates"
+                                ]
+                            ):
+                                if (
+                                    lsa["lsaId"] == netlsa["lsaId"]
+                                    and lsa["advertisedRouter"]
+                                    == netlsa["advertisedRouter"]
+                                ):
+                                    result = True
+                                    break
+                        if result:
+                            logger.info(
+                                "[DUT: %s]  OSPF LSDB area %s:Network " "LSA %s",
+                                router,
+                                ospf_area,
+                                lsa,
+                            )
+                            break
+                        else:
+                            errormsg = (
+                                "[DUT: {}]  OSPF LSDB area {}: expected"
                                 " Network LSA is {}".format(router, ospf_area, lsa)
-                                return errormsg
+                            )
+                            return errormsg
 
-                    if 'summaryLinkStates' in area_lsa:
-                        for lsa in area_lsa['summaryLinkStates']:
-                            for t3lsa in show_ospf_json['areas'][ospf_area][
-                                'summaryLinkStates']:
-                                if lsa['lsaId'] == t3lsa['lsaId'] and \
-                                lsa['advertisedRouter'] == t3lsa[
-                                    'advertisedRouter']:
-                                            result = True
-                                            break
-                            if result:
-                                logger.info(
-                                    "[DUT: %s]  OSPF LSDB area %s:Summary "
-                                    "LSA %s", router, ospf_area, lsa)
+                if "summaryLinkStates" in area_lsa:
+                    for lsa in area_lsa["summaryLinkStates"]:
+                        for t3lsa in show_ospf_json["areas"][ospf_area][
+                            "summaryLinkStates"
+                        ]:
+                            if (
+                                lsa["lsaId"] == t3lsa["lsaId"]
+                                and lsa["advertisedRouter"] == t3lsa["advertisedRouter"]
+                            ):
+                                result = True
                                 break
-                            else:
-                                errormsg = \
-                                "[DUT: {}]  OSPF LSDB area {}: expected" \
+                        if result:
+                            logger.info(
+                                "[DUT: %s]  OSPF LSDB area %s:Summary " "LSA %s",
+                                router,
+                                ospf_area,
+                                lsa,
+                            )
+                            break
+                        else:
+                            errormsg = (
+                                "[DUT: {}]  OSPF LSDB area {}: expected"
                                 " Summary LSA is {}".format(router, ospf_area, lsa)
-                                return errormsg
+                            )
+                            return errormsg
 
-                    if 'nssaExternalLinkStates' in area_lsa:
-                        for lsa in area_lsa['nssaExternalLinkStates']:
-                            for t7lsa in show_ospf_json['areas'][ospf_area][
-                                'nssaExternalLinkStates']:
-                                if lsa['lsaId'] == t7lsa['lsaId'] and \
-                                lsa['advertisedRouter'] == t7lsa[
-                                    'advertisedRouter']:
-                                            result = True
-                                            break
-                            if result:
-                                logger.info(
-                                    "[DUT: %s]  OSPF LSDB area %s:Type7 "
-                                    "LSA %s", router, ospf_area, lsa)
+                if "nssaExternalLinkStates" in area_lsa:
+                    for lsa in area_lsa["nssaExternalLinkStates"]:
+                        for t7lsa in show_ospf_json["areas"][ospf_area][
+                            "nssaExternalLinkStates"
+                        ]:
+                            if (
+                                lsa["lsaId"] == t7lsa["lsaId"]
+                                and lsa["advertisedRouter"] == t7lsa["advertisedRouter"]
+                            ):
+                                result = True
                                 break
-                            else:
-                                errormsg = \
-                                "[DUT: {}]  OSPF LSDB area {}: expected" \
+                        if result:
+                            logger.info(
+                                "[DUT: %s]  OSPF LSDB area %s:Type7 " "LSA %s",
+                                router,
+                                ospf_area,
+                                lsa,
+                            )
+                            break
+                        else:
+                            errormsg = (
+                                "[DUT: {}]  OSPF LSDB area {}: expected"
                                 " Type7 LSA is {}".format(router, ospf_area, lsa)
-                                return errormsg
+                            )
+                            return errormsg
 
-                    if 'asbrSummaryLinkStates' in area_lsa:
-                        for lsa in area_lsa['asbrSummaryLinkStates']:
-                            for t4lsa in show_ospf_json['areas'][ospf_area][
-                                'asbrSummaryLinkStates']:
-                                if lsa['lsaId'] == t4lsa['lsaId'] and \
-                                lsa['advertisedRouter'] == t4lsa[
-                                    'advertisedRouter']:
-                                            result = True
-                                            break
-                            if result:
-                                logger.info(
-                                    "[DUT: %s]  OSPF LSDB area %s:ASBR Summary "
-                                    "LSA %s", router, ospf_area, lsa)
+                if "asbrSummaryLinkStates" in area_lsa:
+                    for lsa in area_lsa["asbrSummaryLinkStates"]:
+                        for t4lsa in show_ospf_json["areas"][ospf_area][
+                            "asbrSummaryLinkStates"
+                        ]:
+                            if (
+                                lsa["lsaId"] == t4lsa["lsaId"]
+                                and lsa["advertisedRouter"] == t4lsa["advertisedRouter"]
+                            ):
                                 result = True
-                            else:
-                                errormsg = \
-                                    "[DUT: {}]  OSPF LSDB area {}: expected" \
-                                    " ASBR Summary LSA is {}".format(
-                                        router, ospf_area, lsa)
-                                return errormsg
+                                break
+                        if result:
+                            logger.info(
+                                "[DUT: %s]  OSPF LSDB area %s:ASBR Summary " "LSA %s",
+                                router,
+                                ospf_area,
+                                lsa,
+                            )
+                            result = True
+                        else:
+                            errormsg = (
+                                "[DUT: {}]  OSPF LSDB area {}: expected"
+                                " ASBR Summary LSA is {}".format(router, ospf_area, lsa)
+                            )
+                            return errormsg
 
-                    if 'linkLocalOpaqueLsa' in area_lsa:
-                        for lsa in area_lsa['linkLocalOpaqueLsa']:
-                            try:
-                                for lnklsa in show_ospf_json['areas'][ospf_area][
-                                    'linkLocalOpaqueLsa']:
-                                    if lsa['lsaId'] in lnklsa['lsaId'] and \
-                                        'linkLocalOpaqueLsa' in show_ospf_json[
-                                            'areas'][ospf_area]:
-                                        logger.info((
-                                        "[DUT: FRR]  OSPF LSDB area %s:Opaque-LSA"
-                                        "%s", ospf_area, lsa))
-                                        result = True
-                                    else:
-                                        errormsg = ("[DUT: FRR] OSPF LSDB area: {} "
-                                    "expected Opaque-LSA is {}, Found is {}".format(
-                                        ospf_area, lsa, show_ospf_json))
-                                        raise ValueError (errormsg)
-                                        return errormsg
-                            except KeyError:
-                                errormsg = ("[DUT: FRR] linkLocalOpaqueLsa Not "
-                                                "present")
-                                return errormsg
+                if "linkLocalOpaqueLsa" in area_lsa:
+                    for lsa in area_lsa["linkLocalOpaqueLsa"]:
+                        try:
+                            for lnklsa in show_ospf_json["areas"][ospf_area][
+                                "linkLocalOpaqueLsa"
+                            ]:
+                                if (
+                                    lsa["lsaId"] in lnklsa["lsaId"]
+                                    and "linkLocalOpaqueLsa"
+                                    in show_ospf_json["areas"][ospf_area]
+                                ):
+                                    logger.info(
+                                        (
+                                            "[DUT: FRR]  OSPF LSDB area %s:Opaque-LSA"
+                                            "%s",
+                                            ospf_area,
+                                            lsa,
+                                        )
+                                    )
+                                    result = True
+                                else:
+                                    errormsg = (
+                                        "[DUT: FRR] OSPF LSDB area: {} "
+                                        "expected Opaque-LSA is {}, Found is {}".format(
+                                            ospf_area, lsa, show_ospf_json
+                                        )
+                                    )
+                                    raise ValueError(errormsg)
+                                    return errormsg
+                        except KeyError:
+                            errormsg = "[DUT: FRR] linkLocalOpaqueLsa Not " "present"
+                            return errormsg
 
     if ospf_external_lsa:
-            for lsa in ospf_external_lsa:
-                try:
-                    for t5lsa in show_ospf_json['asExternalLinkStates']:
-                        if lsa['lsaId'] == t5lsa['lsaId'] and \
-                            lsa['advertisedRouter'] == t5lsa[
-                                        'advertisedRouter']:
-                            result = True
-                            break
-                except KeyError:
-                        result = False
-                if result:
-                    logger.info(
-                            "[DUT: %s]  OSPF LSDB:External LSA %s",
-                            router, lsa)
-                    result = True
-                else:
-                    errormsg = \
-                            "[DUT: {}]  OSPF LSDB : expected" \
-                            " External LSA is {}".format(router, lsa)
-                    return errormsg
+        for lsa in ospf_external_lsa:
+            try:
+                for t5lsa in show_ospf_json["asExternalLinkStates"]:
+                    if (
+                        lsa["lsaId"] == t5lsa["lsaId"]
+                        and lsa["advertisedRouter"] == t5lsa["advertisedRouter"]
+                    ):
+                        result = True
+                        break
+            except KeyError:
+                result = False
+            if result:
+                logger.info("[DUT: %s]  OSPF LSDB:External LSA %s", router, lsa)
+                result = True
+            else:
+                errormsg = (
+                    "[DUT: {}]  OSPF LSDB : expected"
+                    " External LSA is {}".format(router, lsa)
+                )
+                return errormsg
 
     logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
     return result
 
 
-
-def config_ospf6_interface (tgen, topo, input_dict=None, build=False,
-        load_config=True):
+def config_ospf6_interface(tgen, topo, input_dict=None, build=False, load_config=True):
     """
     API to configure ospf on router.
 
@@ -2180,17 +2300,17 @@ def config_ospf6_interface (tgen, topo, input_dict=None, build=False,
                              "input_dict, passed input_dict %s", router,
                              str(input_dict))
                 continue
-            ospf_data = input_dict[router]['links'][lnk]['ospf6']
+            ospf_data = input_dict[router]["links"][lnk]["ospf6"]
             data_ospf_area = ospf_data.setdefault("area", None)
-            data_ospf_auth = ospf_data.setdefault("authentication", None)
+            data_ospf_auth = ospf_data.setdefault("hash-algo", None)
             data_ospf_dr_priority = ospf_data.setdefault("priority", None)
             data_ospf_cost = ospf_data.setdefault("cost", None)
             data_ospf_mtu = ospf_data.setdefault("mtu_ignore", None)
 
             try:
-                intf = topo['routers'][router]['links'][lnk]['interface']
+                intf = topo["routers"][router]["links"][lnk]["interface"]
             except KeyError:
-                intf = topo['switches'][router]['links'][lnk]['interface']
+                intf = topo["switches"][router]["links"][lnk]["interface"]
 
             # interface
             cmd = "interface {}".format(intf)
@@ -2201,34 +2321,50 @@ def config_ospf6_interface (tgen, topo, input_dict=None, build=False,
                 cmd = "ipv6 ospf area {}".format(data_ospf_area)
                 config_data.append(cmd)
 
+            # interface ospf auth
+            if data_ospf_auth:
+                cmd = "ipv6 ospf6 authentication"
+
+                if "del_action" in ospf_data:
+                    cmd = "no {}".format(cmd)
+
+                if "hash-algo" in ospf_data:
+                    cmd = "{} key-id {} hash-algo {} key {}".format(
+                        cmd,
+                        ospf_data["key-id"],
+                        ospf_data["hash-algo"],
+                        ospf_data["key"],
+                    )
+                    if "del_action" in ospf_data:
+                        cmd = "no {}".format(cmd)
+                    config_data.append(cmd)
+
             # interface ospf dr priority
             if data_ospf_dr_priority:
-                cmd = "ipv6 ospf priority {}".format(
-                    ospf_data["priority"])
-                if 'del_action' in ospf_data:
+                cmd = "ipv6 ospf priority {}".format(ospf_data["priority"])
+                if "del_action" in ospf_data:
                     cmd = "no {}".format(cmd)
                 config_data.append(cmd)
 
             # interface ospf cost
             if data_ospf_cost:
-                cmd = "ipv6 ospf cost {}".format(
-                    ospf_data["cost"])
-                if 'del_action' in ospf_data:
+                cmd = "ipv6 ospf cost {}".format(ospf_data["cost"])
+                if "del_action" in ospf_data:
                     cmd = "no {}".format(cmd)
                 config_data.append(cmd)
 
             # interface ospf mtu
             if data_ospf_mtu:
                 cmd = "ipv6 ospf mtu-ignore"
-                if 'del_action' in ospf_data:
+                if "del_action" in ospf_data:
                     cmd = "no {}".format(cmd)
                 config_data.append(cmd)
 
             if build:
                 return config_data
             else:
-                result = create_common_configuration(tgen, router, config_data,
-                                             "interface_config",
-                                             build=build)
+                result = create_common_configuration(
+                    tgen, router, config_data, "interface_config", build=build
+                )
     logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
     return result
diff --git a/tests/topotests/ospfv3_basic_functionality/ospfv3_ecmp.json b/tests/topotests/ospfv3_basic_functionality/ospfv3_ecmp.json
new file mode 100644 (file)
index 0000000..c928093
--- /dev/null
@@ -0,0 +1,347 @@
+{
+    "address_types": [
+        "ipv6"
+    ],
+    "ipv6base": "fd00::",
+    "ipv6mask": 64,
+    "link_ip_start": {
+        "ipv6": "fd00::",
+        "v6mask": 64
+    },
+    "lo_prefix": {
+        "ipv6": "2001:db8:f::",
+        "v6mask": 128
+    },
+    "routers": {
+        "r0": {
+            "links": {
+                "lo": {
+                    "ipv6": "auto",
+                    "type": "loopback"
+                },
+                "r1": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1-link1": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1-link2": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1-link3": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1-link4": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1-link5": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1-link6": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1-link7": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r2": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r3": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4,
+                        "network": "point-to-point"
+                    }
+                }
+            },
+            "ospf6": {
+                "router_id": "100.1.1.0",
+                "neighbors": {
+                    "r1": {},
+                    "r1-link1": {
+                        "nbr": "r1"
+                    },
+                    "r1-link2": {
+                        "nbr": "r1"
+                    },
+                    "r1-link3": {
+                        "nbr": "r1"
+                    },
+                    "r1-link4": {
+                        "nbr": "r1"
+                    },
+                    "r1-link5": {
+                        "nbr": "r1"
+                    },
+                    "r1-link6": {
+                        "nbr": "r1"
+                    },
+                    "r1-link7": {
+                        "nbr": "r1"
+                    },
+                    "r2": {},
+                    "r3": {}
+                }
+            }
+        },
+        "r1": {
+            "links": {
+                "lo": {
+                    "ipv6": "auto",
+                    "type": "loopback"
+                },
+                "r0": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r0-link1": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r0-link2": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r0-link3": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r0-link4": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r0-link5": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r0-link6": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r0-link7": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r2": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r3": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r3-link0": {
+                    "ipv6": "auto",
+                    "description": "DummyIntftoR3"
+                }
+            },
+            "ospf6": {
+                "router_id": "100.1.1.1",
+                "neighbors": {
+                    "r0": {},
+                    "r0-link1": {
+                        "nbr": "r0"
+                    },
+                    "r0-link2": {
+                        "nbr": "r0"
+                    },
+                    "r0-link3": {
+                        "nbr": "r0"
+                    },
+                    "r0-link4": {
+                        "nbr": "r0"
+                    },
+                    "r0-link5": {
+                        "nbr": "r0"
+                    },
+                    "r0-link6": {
+                        "nbr": "r0"
+                    },
+                    "r0-link7": {
+                        "nbr": "r0"
+                    },
+                    "r2": {},
+                    "r3": {}
+                }
+            }
+        },
+        "r2": {
+            "links": {
+                "lo": {
+                    "ipv6": "auto",
+                    "type": "loopback"
+                },
+                "r0": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r3": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                }
+            },
+            "ospf6": {
+                "router_id": "100.1.1.2",
+                "neighbors": {
+                    "r1": {},
+                    "r0": {},
+                    "r3": {}
+                }
+            }
+        },
+        "r3": {
+            "links": {
+                "lo": {
+                    "ipv6": "auto",
+                    "type": "loopback"
+                },
+                "r0": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4,
+                        "network": "point-to-point"
+                    }
+                },
+                "r1": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r2": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1-link0": {
+                    "ipv6": "auto",
+                    "description": "DummyIntftoR1",
+                    "ospf": {
+                        "area": "0.0.0.0"
+                    },
+                    "ospf6": {
+                        "area": "0.0.0.0"
+                    }
+                }
+            },
+            "ospf6": {
+                "router_id": "100.1.1.3",
+                "neighbors": {
+                    "r0": {},
+                    "r1": {},
+                    "r2": {}
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/topotests/ospfv3_basic_functionality/ospfv3_routemaps.json b/tests/topotests/ospfv3_basic_functionality/ospfv3_routemaps.json
new file mode 100644 (file)
index 0000000..226f84f
--- /dev/null
@@ -0,0 +1,137 @@
+{
+    "address_types": ["ipv6"],
+    "ipv6base": "fd00::",
+    "ipv6mask": 64,
+    "link_ip_start": {"ipv6": "fd00::", "v6mask": 64},
+    "lo_prefix": {"ipv6": "2001:db8:f::", "v6mask": 128},
+    "routers": {
+        "r0": {
+            "links": {
+                "r1": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r2": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r3": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                }
+            },
+            "ospf6": {
+                "router_id": "100.1.1.0",
+                "neighbors": {"r1": {}, "r2": {}, "r3": {}}
+            }
+        },
+        "r1": {
+            "links": {
+                "r0": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r2": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r3": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                }
+            },
+            "ospf6": {
+                "router_id": "100.1.1.1",
+                "neighbors": {"r0": {}, "r2": {}, "r3": {}}
+            }
+        },
+        "r2": {
+            "links": {
+                "r0": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r3": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                }
+            },
+            "ospf6": {
+                "router_id": "100.1.1.2",
+                "neighbors": {"r1": {}, "r0": {}, "r3": {}}
+            }
+        },
+        "r3": {
+            "links": {
+                "r0": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r1": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                },
+                "r2": {
+                    "ipv6": "auto",
+                    "ospf6": {
+                        "area": "0.0.0.0",
+                        "hello_interval": 1,
+                        "dead_interval": 4
+                    }
+                }
+            },
+            "ospf6": {
+                "router_id": "100.1.1.3",
+                "neighbors": {"r0": {}, "r1": {}, "r2": {}}
+            }
+        }
+    }
+}
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp.py
new file mode 100644 (file)
index 0000000..a439375
--- /dev/null
@@ -0,0 +1,520 @@
+#!/usr/bin/python
+
+#
+# Copyright (c) 2021 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.
+#
+
+
+"""OSPF Basic Functionality Automation."""
+import os
+import sys
+import time
+import pytest
+import json
+from copy import deepcopy
+from ipaddress import IPv4Address
+from lib.topotest import frr_unicode
+
+# 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/"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from mininet.topo import Topo
+from lib.topogen import Topogen, get_topogen
+import ipaddress
+
+# Import topoJson from lib, to create topology and initial configuration
+from lib.common_config import (
+    start_topology,
+    write_test_header,
+    write_test_footer,
+    reset_config_on_routers,
+    verify_rib,
+    create_static_routes,
+    step,
+    create_route_maps,
+    shutdown_bringup_interface,
+    create_interfaces_cfg,
+    topo_daemons,
+    get_frr_ipv6_linklocal,
+)
+from lib.topolog import logger
+from lib.topojson import build_topo_from_json, build_config_from_json
+
+from lib.ospf import (
+    verify_ospf6_neighbor,
+    config_ospf_interface,
+    clear_ospf,
+    verify_ospf6_rib,
+    create_router_ospf,
+    verify_ospf6_interface,
+    verify_ospf6_database,
+    config_ospf6_interface,
+)
+
+from ipaddress import IPv6Address
+
+# Global variables
+topo = None
+
+# Reading the data from JSON File for topology creation
+jsonFile = "{}/ospfv3_ecmp.json".format(CWD)
+try:
+    with open(jsonFile, "r") as topoJson:
+        topo = json.load(topoJson)
+except IOError:
+    assert False, "Could not read file {}".format(jsonFile)
+
+NETWORK = {
+    "ipv4": [
+        "11.0.20.1/32",
+        "11.0.20.2/32",
+        "11.0.20.3/32",
+        "11.0.20.4/32",
+        "11.0.20.5/32",
+    ],
+    "ipv6": ["2::1/128", "2::2/128", "2::3/128", "2::4/128", "2::5/128"],
+}
+"""
+TOPOLOGY :
+      Please view in a fixed-width font such as Courier.
+      +---+  A1       +---+
+      +R1 +------------+R2 |
+      +-+-+-           +--++
+        |  --        --  |
+        |    -- A0 --    |
+      A0|      ----      |
+        |      ----      | A2
+        |    --    --    |
+        |  --        --  |
+      +-+-+-            +-+-+
+      +R0 +-------------+R3 |
+      +---+     A3     +---+
+
+TESTCASES :
+1. Verify OSPF ECMP with max path configured as 8 (ECMPconfigured at FRR level)
+2. Verify OSPF ECMP with max path configured as 2 (Edge having 2 uplink ports)
+ """
+
+
+class CreateTopo(Topo):
+    """
+    Test topology builder.
+
+    * `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.
+
+    # get list of daemons needs to be started for this suite.
+    daemons = topo_daemons(tgen, topo)
+
+    # Starting topology, create tmp files which are loaded to routers
+    #  to start deamons and then start routers
+    start_topology(tgen, daemons)
+
+    # Creating configuration from JSON
+    build_config_from_json(tgen, topo)
+
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    ospf_covergence = verify_ospf6_neighbor(tgen, topo)
+    assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+        ospf_covergence
+    )
+
+    logger.info("Running setup_module() done")
+
+
+def teardown_module(mod):
+    """
+    Teardown the pytest environment.
+
+    * `mod`: module name
+    """
+
+    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)
+
+
+def red_static(dut, config=True):
+    """Local def for Redstribute static routes inside ospf."""
+    global topo
+    tgen = get_topogen()
+    if config:
+        ospf_red = {dut: {"ospf6": {"redistribute": [{"redist_type": "static"}]}}}
+    else:
+        ospf_red = {
+            dut: {
+                "ospf6": {"redistribute": [{"redist_type": "static", "delete": True}]}
+            }
+        }
+    result = create_router_ospf(tgen, topo, ospf_red)
+    assert result is True, "Testcase : Failed \n Error: {}".format(result)
+
+
+def red_connected(dut, config=True):
+    """Local def for Redstribute connected routes inside ospf."""
+    global topo
+    tgen = get_topogen()
+    if config:
+        ospf_red = {dut: {"ospf6": {"redistribute": [{"redist_type": "connected"}]}}}
+    else:
+        ospf_red = {
+            dut: {
+                "ospf6": {
+                    "redistribute": [{"redist_type": "connected", "del_action": True}]
+                }
+            }
+        }
+    result = create_router_ospf(tgen, topo, ospf_red)
+    assert result is True, "Testcase: Failed \n Error: {}".format(result)
+
+
+def get_llip(onrouter, intf):
+    """
+    API to get the link local ipv6 address of a perticular interface
+
+    Parameters
+    ----------
+    * `fromnode`: Source node
+    * `tonode` : interface for which link local ip needs to be returned.
+
+    Usage
+    -----
+    result = get_llip('r1', 'r2-link0')
+
+    Returns
+    -------
+    1) link local ipv6 address from the interface.
+    2) errormsg - when link local ip not found.
+    """
+    tgen = get_topogen()
+    intf = topo["routers"][onrouter]["links"][intf]["interface"]
+    llip = get_frr_ipv6_linklocal(tgen, onrouter, intf)
+    if llip:
+        logger.info("llip ipv6 address to be set as NH is %s", llip)
+        return llip
+    return None
+
+
+def get_glipv6(onrouter, intf):
+    """
+    API to get the global ipv6 address of a perticular interface
+
+    Parameters
+    ----------
+    * `onrouter`: Source node
+    * `intf` : interface for which link local ip needs to be returned.
+
+    Usage
+    -----
+    result = get_glipv6('r1', 'r2-link0')
+
+    Returns
+    -------
+    1) global ipv6 address from the interface.
+    2) errormsg - when link local ip not found.
+    """
+    glipv6 = (topo["routers"][onrouter]["links"][intf]["ipv6"]).split("/")[0]
+    if glipv6:
+        logger.info("Global ipv6 address to be set as NH is %s", glipv6)
+        return glipv6
+    return None
+
+
+# ##################################
+# Test cases start here.
+# ##################################
+
+
+def test_ospfv3_ecmp_tc16_p0(request):
+    """
+    Verify OSPF ECMP.
+
+    Verify OSPF ECMP with max path configured as 8 (ECMP
+    configured at FRR level)
+    """
+    tc_name = request.node.name
+    write_test_header(tc_name)
+    tgen = get_topogen()
+
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    global topo
+    step("Bring up the base config as per the topology")
+    step("Configure 8 interfaces between R1 and R2 and enable ospf in area 0.")
+
+    reset_config_on_routers(tgen)
+
+    step("Verify that OSPF is up with 8 neighborship sessions.")
+    dut = "r1"
+    ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
+    assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+        ospf_covergence
+    )
+
+    step("Configure a static route in R0 and redistribute in OSPF.")
+
+    input_dict = {
+        "r0": {
+            "static_routes": [
+                {
+                    "network": NETWORK["ipv6"][0],
+                    "no_of_ip": 5,
+                    "next_hop": "Null0",
+                }
+            ]
+        }
+    }
+    result = create_static_routes(tgen, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    dut = "r0"
+    red_static(dut)
+
+    llip = get_llip("r0", "r1-link1")
+    assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Verify that route in R2 in stalled with 8 next hops.")
+    nh = []
+    for item in range(1, 7):
+        nh.append(llip)
+
+    llip = get_llip("r0", "r1")
+    assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    nh2 = llip
+
+    nh.append(nh2)
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("shut no shut all the interfaces on the remote router - R2")
+    dut = "r1"
+    for intfr in range(1, 7):
+        intf = topo["routers"]["r1"]["links"]["r0-link{}".format(intfr)]["interface"]
+        shutdown_bringup_interface(tgen, dut, intf, False)
+
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh, expected=False)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route present in OSPF RIB.  Error: {}".format(
+        tc_name, result
+    )
+
+    protocol = "ospf"
+    result = verify_rib(
+        tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh, expected=False
+    )
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route present in RIB. Error: {}".format(tc_name, result)
+
+    for intfr in range(1, 7):
+        intf = topo["routers"]["r1"]["links"]["r0-link{}".format(intfr)]["interface"]
+        shutdown_bringup_interface(tgen, dut, intf, True)
+
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("shut no shut on all the interfaces on DUT (r1)")
+    for intfr in range(1, 7):
+        intf = topo["routers"]["r1"]["links"]["r0-link{}".format(intfr)]["interface"]
+        shutdown_bringup_interface(tgen, dut, intf, False)
+
+    for intfr in range(1, 7):
+        intf = topo["routers"]["r1"]["links"]["r0-link{}".format(intfr)]["interface"]
+        shutdown_bringup_interface(tgen, dut, intf, True)
+
+    step(
+        "Verify that all the neighbours are up and routes are installed"
+        " with 8 next hop in ospf and ip route tables on R1."
+    )
+
+    step("Verify that OSPF is up with 8 neighborship sessions.")
+    dut = "r1"
+    ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
+    assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+        ospf_covergence
+    )
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    write_test_footer(tc_name)
+
+
+def test_ospfv3_ecmp_tc17_p0(request):
+    """
+    Verify OSPF ECMP.
+
+    Verify OSPF ECMP with max path configured as 2 (Edge having 2 uplink ports)
+    """
+    tc_name = request.node.name
+    write_test_header(tc_name)
+    tgen = get_topogen()
+
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    global topo
+    step("Bring up the base config as per the topology")
+    step("Configure 2 interfaces between R1 and R2 & enable ospf in area 0.")
+
+    reset_config_on_routers(tgen)
+
+    step("Verify that OSPF is up with 2 neighborship sessions.")
+    dut = "r1"
+    ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
+    assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+        ospf_covergence
+    )
+
+    step("Configure a static route in R0 and redistribute in OSPF.")
+
+    input_dict = {
+        "r0": {
+            "static_routes": [
+                {
+                    "network": NETWORK["ipv6"][0],
+                    "no_of_ip": 5,
+                    "next_hop": "Null0",
+                }
+            ]
+        }
+    }
+    result = create_static_routes(tgen, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    dut = "r0"
+    red_static(dut)
+
+    step("Verify that route in R2 in stalled with 2 next hops.")
+
+    llip = get_llip("r0", "r1-link1")
+    assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    nh1 = llip
+
+    llip = get_llip("r0", "r1")
+    assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    nh2 = llip
+
+    nh = [nh1, nh2]
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Configure ECMP value as 1.")
+    max_path = {"r1": {"ospf6": {"maximum-paths": 1}}}
+    result = create_router_ospf(tgen, topo, max_path)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh2)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    dut = "r1"
+    max_path = {"r1": {"ospf6": {"maximum-paths": 2}}}
+    result = create_router_ospf(tgen, topo, max_path)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Configure cost on R0 as 100")
+    r0_ospf_cost = {"r0": {"links": {"r1": {"ospf6": {"cost": 100}}}}}
+    result = config_ospf6_interface(tgen, topo, r0_ospf_cost)
+    assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    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/ospfv3_basic_functionality/test_ospfv3_routemaps.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_routemaps.py
new file mode 100644 (file)
index 0000000..9ca460e
--- /dev/null
@@ -0,0 +1,872 @@
+#!/usr/bin/python
+
+#
+# Copyright (c) 2021 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.
+#
+
+
+"""OSPF Basic Functionality Automation."""
+import os
+import sys
+import time
+import pytest
+import json
+from copy import deepcopy
+from ipaddress import IPv4Address
+from lib.topotest import frr_unicode
+
+# 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/"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from mininet.topo import Topo
+from lib.topogen import Topogen, get_topogen
+import ipaddress
+
+# Import topoJson from lib, to create topology and initial configuration
+from lib.common_config import (
+    start_topology,
+    write_test_header,
+    write_test_footer,
+    reset_config_on_routers,
+    create_prefix_lists,
+    verify_rib,
+    create_static_routes,
+    step,
+    create_route_maps,
+    verify_prefix_lists,
+    get_frr_ipv6_linklocal,
+    topo_daemons,
+)
+from lib.topolog import logger
+from lib.topojson import build_topo_from_json, build_config_from_json
+
+from lib.ospf import (
+    verify_ospf6_neighbor,
+    config_ospf_interface,
+    clear_ospf,
+    verify_ospf6_rib,
+    create_router_ospf,
+    verify_ospf6_interface,
+    verify_ospf6_database,
+    config_ospf6_interface,
+)
+
+from ipaddress import IPv6Address
+
+# Global variables
+topo = None
+
+# Reading the data from JSON File for topology creation
+jsonFile = "{}/ospfv3_routemaps.json".format(CWD)
+try:
+    with open(jsonFile, "r") as topoJson:
+        topo = json.load(topoJson)
+except IOError:
+    assert False, "Could not read file {}".format(jsonFile)
+
+NETWORK = {
+    "ipv4": [
+        "11.0.20.1/32",
+        "11.0.20.2/32",
+        "11.0.20.3/32",
+        "11.0.20.4/32",
+        "11.0.20.5/32",
+    ],
+    "ipv6": ["2::1/128", "2::2/128", "2::3/128", "2::4/128", "2::5/128"],
+}
+
+routerids = ["100.1.1.0", "100.1.1.1", "100.1.1.2", "100.1.1.3"]
+
+"""
+TOPOOLOGY =
+      Please view in a fixed-width font such as Courier.
+      +---+  A1       +---+
+      +R1 +------------+R2 |
+      +-+-+-           +--++
+        |  --        --  |
+        |    -- A0 --    |
+      A0|      ----      |
+        |      ----      | A2
+        |    --    --    |
+        |  --        --  |
+      +-+-+-            +-+-+
+      +R0 +-------------+R3 |
+      +---+     A3     +---+
+
+TESTCASES =
+2. Verify OSPF route map support functionality when route map is not
+    configured at system level but configured in OSPF
+4. Verify OSPF route map support functionality
+    when route map actions are toggled.
+5. Verify OSPF route map support  functionality with multiple sequence
+    numbers in a single  route-map for different match/set clauses.
+6. Verify OSPF route map support functionality when we add/remove route-maps
+    with multiple set clauses and without any match statement.(Set only)
+7.  Verify OSPF route map support functionality when we
+    add/remove route-maps with multiple match clauses and without
+    any set statement.(Match only)
+8. Verify OSPF route map applied to ospf redistribution with  ipv6 prefix list
+ """
+
+
+class CreateTopo(Topo):
+    """
+    Test topology builder.
+
+    * `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.
+
+    # get list of daemons needs to be started for this suite.
+    daemons = topo_daemons(tgen, topo)
+
+    # Starting topology, create tmp files which are loaded to routers
+    #  to start deamons and then start routers
+    start_topology(tgen, daemons)
+
+    # Creating configuration from JSON
+    build_config_from_json(tgen, topo)
+
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    ospf_covergence = verify_ospf6_neighbor(tgen, topo)
+    assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+        ospf_covergence
+    )
+
+    logger.info("Running setup_module() done")
+
+
+def teardown_module(mod):
+    """
+    Teardown the pytest environment.
+
+    * `mod`: module name
+    """
+
+    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)
+
+
+# ##################################
+# Test cases start here.
+# ##################################
+
+
+def test_ospfv3_routemaps_functionality_tc20_p0(request):
+    """
+    OSPF route map support functionality.
+
+    Verify OSPF route map support functionality when route map is not
+    configured at system level but configured in OSPF
+
+    """
+    tc_name = request.node.name
+    write_test_header(tc_name)
+    tgen = get_topogen()
+    global topo
+    step("Bring up the base config as per the topology")
+
+    reset_config_on_routers(tgen)
+
+    step("Create static routes(10.0.20.1/32 and 10.0.20.2/32) in R0")
+    # Create Static routes
+    input_dict = {
+        "r0": {
+            "static_routes": [
+                {
+                    "network": NETWORK["ipv6"][0],
+                    "no_of_ip": 5,
+                    "next_hop": "Null0",
+                }
+            ]
+        }
+    }
+    result = create_static_routes(tgen, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Redistribute to ospf using route map ( non existent route map)")
+    ospf_red_r1 = {
+        "r0": {
+            "ospf6": {
+                "redistribute": [{"redist_type": "static", "route_map": "rmap_ipv6"}]
+            }
+        }
+    }
+    result = create_router_ospf(tgen, topo, ospf_red_r1)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step(
+        "Verify that routes are not allowed in OSPF even tough no "
+        "matching routing map is configured."
+    )
+
+    dut = "r1"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    step(
+        "configure the route map with the same name that is used "
+        "in the ospf with deny rule."
+    )
+
+    # Create route map
+    routemaps = {"r0": {"route_maps": {"rmap_ipv6": [{"action": "deny"}]}}}
+    result = create_route_maps(tgen, routemaps)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("verify that now route map is activated & routes are denied in OSPF.")
+    dut = "r1"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    result = verify_rib(
+        tgen, "ipv6", dut, input_dict, protocol=protocol, expected=False
+    )
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    # Create route map
+    routemaps = {"r0": {"route_maps": {"rmap_ipv6": [{"action": "deny"}]}}}
+    result = create_route_maps(tgen, routemaps)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("verify that now route map is activated & routes are denied in OSPF.")
+    dut = "r1"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    result = verify_rib(
+        tgen, "ipv6", dut, input_dict, protocol=protocol, expected=False
+    )
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    step("Delete the route map.")
+    # Create route map
+    routemaps = {
+        "r0": {"route_maps": {"rmap_ipv6": [{"action": "deny", "delete": True}]}}
+    }
+    result = create_route_maps(tgen, routemaps)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step(
+        "Verify that routes are allowed in OSPF even tough "
+        "no matching routing map is configured."
+    )
+    dut = "r1"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    result = verify_rib(
+        tgen, "ipv6", dut, input_dict, protocol=protocol, expected=False
+    )
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    write_test_footer(tc_name)
+
+
+def test_ospfv3_routemaps_functionality_tc25_p0(request):
+    """
+    OSPF route map support functionality.
+
+    Verify OSPF route map support functionality
+    when route map actions are toggled.
+
+    """
+    tc_name = request.node.name
+    write_test_header(tc_name)
+    tgen = get_topogen()
+    global topo
+    step("Bring up the base config as per the topology")
+
+    reset_config_on_routers(tgen)
+
+    step(
+        "Create static routes(10.0.20.1/32) in R1 and redistribute "
+        "to OSPF using route map."
+    )
+
+    # Create Static routes
+    input_dict = {
+        "r0": {
+            "static_routes": [
+                {
+                    "network": NETWORK["ipv6"][0],
+                    "no_of_ip": 5,
+                    "next_hop": "Null0",
+                }
+            ]
+        }
+    }
+    result = create_static_routes(tgen, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    ospf_red_r0 = {
+        "r0": {
+            "ospf6": {
+                "redistribute": [{"redist_type": "static", "route_map": "rmap_ipv6"}]
+            }
+        }
+    }
+    result = create_router_ospf(tgen, topo, ospf_red_r0)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+    step("Configure route map with permit rule")
+    # Create route map
+    routemaps = {"r0": {"route_maps": {"rmap_ipv6": [{"action": "permit"}]}}}
+    result = create_route_maps(tgen, routemaps)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Verify that route is advertised to R1.")
+    dut = "r1"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+    step("Configure route map with deny rule")
+    # Create route map
+    routemaps = {
+        "r0": {"route_maps": {"rmap_ipv6": [{"seq_id": 10, "action": "deny"}]}}
+    }
+    result = create_route_maps(tgen, routemaps)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    # Api call verify whether OSPF is converged
+    ospf_covergence = verify_ospf6_neighbor(tgen, topo)
+    assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+        ospf_covergence
+    )
+
+    step("Verify that route is not advertised to R1.")
+    dut = "r1"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    result = verify_rib(
+        tgen, "ipv6", dut, input_dict, protocol=protocol, expected=False
+    )
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    write_test_footer(tc_name)
+
+
+def test_ospfv3_routemaps_functionality_tc22_p0(request):
+    """
+    OSPF Route map - Multiple sequence numbers.
+
+    Verify OSPF route map support  functionality with multiple sequence
+    numbers in a single  route-map for different match/set clauses.
+
+    """
+    tc_name = request.node.name
+    write_test_header(tc_name)
+    tgen = get_topogen()
+    global topo
+    step("Bring up the base config as per the topology")
+
+    reset_config_on_routers(tgen)
+
+    step(
+        "Configure route map with seq number 10 to with ip prefix"
+        " permitting route 10.0.20.1/32 in R1"
+    )
+    step(
+        "Configure route map with seq number 20 to with  ip prefix"
+        "  permitting route 10.0.20.2/32 in R1"
+    )
+
+    # Create route map
+    input_dict_3 = {
+        "r0": {
+            "route_maps": {
+                "rmap_ipv6": [
+                    {
+                        "action": "permit",
+                        "seq_id": "10",
+                        "match": {"ipv6": {"prefix_lists": "pf_list_1_ipv6"}},
+                    },
+                    {
+                        "action": "permit",
+                        "seq_id": "20",
+                        "match": {"ipv6": {"prefix_lists": "pf_list_2_ipv4"}},
+                    },
+                ]
+            }
+        }
+    }
+    result = create_route_maps(tgen, input_dict_3)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    # Create ip prefix list
+    input_dict_2 = {
+        "r0": {
+            "prefix_lists": {
+                "ipv4": {
+                    "pf_list_1_ipv6": [
+                        {"seqid": 10, "network": NETWORK["ipv6"][0], "action": "permit"}
+                    ]
+                }
+            }
+        }
+    }
+    result = create_prefix_lists(tgen, input_dict_2)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    # Create ip prefix list
+    input_dict_2 = {
+        "r0": {
+            "prefix_lists": {
+                "ipv4": {
+                    "pf_list_2_ipv4": [
+                        {"seqid": 10, "network": NETWORK["ipv6"][1], "action": "permit"}
+                    ]
+                }
+            }
+        }
+    }
+    result = create_prefix_lists(tgen, input_dict_2)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Configure static routes 10.0.20.1/32 and 10.0.20.2 in R1")
+    # Create Static routes
+    input_dict = {
+        "r0": {
+            "static_routes": [
+                {
+                    "network": NETWORK["ipv6"][0],
+                    "no_of_ip": 5,
+                    "next_hop": "Null0",
+                }
+            ]
+        }
+    }
+    result = create_static_routes(tgen, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Configure redistribute static route with route map.")
+    ospf_red_r0 = {
+        "r0": {
+            "ospf6": {
+                "redistribute": [{"redist_type": "static", "route_map": "rmap_ipv6"}]
+            }
+        }
+    }
+    result = create_router_ospf(tgen, topo, ospf_red_r0)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    input_dict = {
+        "r0": {
+            "static_routes": [
+                {
+                    "network": NETWORK["ipv6"][0],
+                    "no_of_ip": 2,
+                    "next_hop": "Null0",
+                }
+            ]
+        }
+    }
+    result = create_static_routes(tgen, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Verify that both routes are learned in R1 and R2")
+    dut = "r1"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    dut = "r2"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Change route map with seq number 20 to deny.")
+    # Create route map
+    input_dict_3 = {
+        "r0": {
+            "route_maps": {
+                "rmap_ipv6": [
+                    {
+                        "action": "deny",
+                        "seq_id": "20",
+                        "match": {"ipv6": {"prefix_lists": "pf_list_2_ipv4"}},
+                    }
+                ]
+            }
+        }
+    }
+    result = create_route_maps(tgen, input_dict_3)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step(
+        "Verify the route 10.0.20.2/32 is withdrawn and not present "
+        "in the routing table of R0 and R1."
+    )
+
+    input_dict = {
+        "r0": {"static_routes": [{"network": NETWORK["ipv6"][1], "next_hop": "Null0"}]}
+    }
+
+    dut = "r1"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    result = verify_rib(
+        tgen, "ipv6", dut, input_dict, protocol=protocol, expected=False
+    )
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    dut = "r2"
+    protocol = "ospf"
+    result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    result = verify_rib(
+        tgen, "ipv6", dut, input_dict, protocol=protocol, expected=False
+    )
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route found in the RIB, Error: {}".format(
+        tc_name, result
+    )
+
+    write_test_footer(tc_name)
+
+
+def test_ospfv3_routemaps_functionality_tc24_p0(request):
+    """
+    OSPF Route map - Multiple set clauses.
+
+    Verify OSPF route map support functionality when we
+    add/remove route-maps with multiple match clauses and without
+    any set statement.(Match only)
+
+    """
+    tc_name = request.node.name
+    write_test_header(tc_name)
+    tgen = get_topogen()
+    global topo
+    step("Bring up the base config as per the topology")
+
+    reset_config_on_routers(tgen)
+
+    step(
+        "Create static routes(10.0.20.1/32) in R1 and redistribute to "
+        "OSPF using route map."
+    )
+    # Create Static routes
+    input_dict = {
+        "r0": {
+            "static_routes": [
+                {
+                    "network": NETWORK["ipv6"][0],
+                    "no_of_ip": 1,
+                    "next_hop": "Null0",
+                }
+            ]
+        }
+    }
+    result = create_static_routes(tgen, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    ospf_red_r0 = {
+        "r0": {
+            "ospf6": {
+                "redistribute": [{"redist_type": "static", "route_map": "rmap_ipv6"}]
+            }
+        }
+    }
+    result = create_router_ospf(tgen, topo, ospf_red_r0)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    # Create ip prefix list
+    pfx_list = {
+        "r0": {
+            "prefix_lists": {
+                "ipv6": {
+                    "pf_list_1_ipv6": [
+                        {"seqid": 10, "network": "any", "action": "permit"}
+                    ]
+                }
+            }
+        }
+    }
+    result = create_prefix_lists(tgen, pfx_list)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("verify that prefix-list is created in R0.")
+    result = verify_prefix_lists(tgen, pfx_list)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Prefix list not " "present. Error: {}".format(
+        tc_name, result
+    )
+
+    # Create route map
+    routemaps = {
+        "r0": {
+            "route_maps": {
+                "rmap_ipv6": [
+                    {
+                        "action": "permit",
+                        "match": {"ipv6": {"prefix_lists": "pf_list_1_ipv6"}},
+                    }
+                ]
+            }
+        }
+    }
+    result = create_route_maps(tgen, routemaps)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Verify that metric falls back to original metric for ospf routes.")
+    dut = "r1"
+    protocol = "ospf"
+
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step(
+        "Create static routes(10.0.20.1/32) in R1 and redistribute to "
+        "OSPF using route map."
+    )
+    # Create Static routes
+    input_dict = {
+        "r0": {
+            "static_routes": [
+                {
+                    "network": NETWORK["ipv6"][1],
+                    "no_of_ip": 1,
+                    "next_hop": "Null0",
+                    "tag": 1000,
+                }
+            ]
+        }
+    }
+    result = create_static_routes(tgen, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    # Create ip prefix list
+    pfx_list = {
+        "r0": {
+            "prefix_lists": {
+                "ipv6": {
+                    "pf_list_1_ipv6": [
+                        {"seqid": 10, "network": "any", "action": "permit"}
+                    ]
+                }
+            }
+        }
+    }
+    result = create_prefix_lists(tgen, pfx_list)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("verify that prefix-list is created in R0.")
+    result = verify_prefix_lists(tgen, pfx_list)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Prefix list not " "present. Error: {}".format(
+        tc_name, result
+    )
+
+    # Create route map
+    routemaps = {
+        "r0": {
+            "route_maps": {
+                "rmap_ipv6": [{"action": "permit", "match": {"ipv6": {"tag": "1000"}}}]
+            }
+        }
+    }
+    result = create_route_maps(tgen, routemaps)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Verify that metric falls back to original metric for ospf routes.")
+    dut = "r1"
+    protocol = "ospf"
+
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Delete the match clause with tag in route map")
+    # Create route map
+    routemaps = {
+        "r0": {
+            "route_maps": {
+                "rmap_ipv6": [
+                    {
+                        "action": "permit",
+                        "match": {"ipv6": {"tag": "1000", "delete": True}},
+                    }
+                ]
+            }
+        }
+    }
+    result = create_route_maps(tgen, routemaps)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Verify that metric falls back to original metric for ospf routes.")
+    dut = "r1"
+    protocol = "ospf"
+
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Delete the match clause with metric in route map.")
+
+    # Create route map
+    routemaps = {
+        "r0": {
+            "route_maps": {
+                "rmap_ipv6": [
+                    {
+                        "action": "permit",
+                        "match": {"ipv6": {"prefix_lists": "pf_list_1_ipv6"}},
+                    }
+                ]
+            }
+        }
+    }
+    result = create_route_maps(tgen, routemaps)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol)
+    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))
index 4aa71bfb16c9cdc83a75de6411b71eebb53a9bbc..e01c6d604775e08da3dac89a8ba6e8513f6ef053 100644 (file)
@@ -281,6 +281,233 @@ def red_connected(dut, config=True):
 # ##################################
 # Test cases start here.
 # ##################################
+def test_ospfv3_redistribution_tc5_p0(request):
+    """Test OSPF intra area route calculations."""
+    tc_name = request.node.name
+    write_test_header(tc_name)
+    tgen = get_topogen()
+
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    global topo
+    step("Bring up the base config.")
+    reset_config_on_routers(tgen)
+
+    step("Verify that OSPF neighbors are FULL.")
+    ospf_covergence = verify_ospf6_neighbor(tgen, topo)
+    assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+        ospf_covergence
+    )
+
+    step("verify intra area route is calculated for r0-r3 interface ip in R1")
+    ip = topo["routers"]["r0"]["links"]["r3"]["ipv6"]
+    ip_net = str(ipaddress.ip_interface(u"{}".format(ip)).network)
+
+    llip = get_llip("r0", "r1")
+    assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, llip)
+
+    nh = llip
+    input_dict = {
+        "r1": {"static_routes": [{"network": ip_net, "no_of_ip": 1, "routeType": "N"}]}
+    }
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Delete the ip address on newly configured loopback of R0")
+    topo1 = {
+        "r0": {
+            "links": {
+                "r3": {
+                    "ipv6": topo["routers"]["r0"]["links"]["r3"]["ipv6"],
+                    "interface": topo["routers"]["r0"]["links"]["r3"]["interface"],
+                    "delete": True,
+                }
+            }
+        }
+    }
+
+    result = create_interfaces_cfg(tgen, topo1)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh, expected=False)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route present in RIB. Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(
+        tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh, expected=False
+    )
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route present in RIB. Error: {}".format(tc_name, result)
+
+    step("Add back the deleted ip address on newly configured interface of R0")
+    topo1 = {
+        "r0": {
+            "links": {
+                "r3": {
+                    "ipv6": topo["routers"]["r0"]["links"]["r3"]["ipv6"],
+                    "interface": topo["routers"]["r0"]["links"]["r3"]["interface"],
+                }
+            }
+        }
+    }
+
+    result = create_interfaces_cfg(tgen, topo1)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Shut no shut interface on R0")
+    dut = "r0"
+    intf = topo["routers"]["r0"]["links"]["r3"]["interface"]
+    shutdown_bringup_interface(tgen, dut, intf, False)
+
+    step("un shut the OSPF interface on R0")
+    dut = "r0"
+    shutdown_bringup_interface(tgen, dut, intf, True)
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    write_test_footer(tc_name)
+
+
+def test_ospfv3_redistribution_tc6_p0(request):
+    """Test OSPF inter area route calculations."""
+    tc_name = request.node.name
+    write_test_header(tc_name)
+    tgen = get_topogen()
+
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    global topo
+    step("Bring up the base config.")
+    reset_config_on_routers(tgen)
+
+    step("Verify that OSPF neighbors are FULL.")
+    ospf_covergence = verify_ospf6_neighbor(tgen, topo)
+    assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+        ospf_covergence
+    )
+
+    step("verify intra area route is calculated for r0-r3 interface ip in R1")
+    ip = topo["routers"]["r0"]["links"]["r3"]["ipv6"]
+    ip_net = str(ipaddress.ip_interface(u"{}".format(ip)).network)
+    llip = get_llip("r0", "r1")
+    assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, llip)
+    nh = llip
+    input_dict = {
+        "r1": {"static_routes": [{"network": ip_net, "no_of_ip": 1, "routeType": "N"}]}
+    }
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Delete the ip address on newly configured loopback of R0")
+    topo1 = {
+        "r0": {
+            "links": {
+                "r3": {
+                    "ipv6": topo["routers"]["r0"]["links"]["r3"]["ipv6"],
+                    "interface": topo["routers"]["r0"]["links"]["r3"]["interface"],
+                    "delete": True,
+                }
+            }
+        }
+    }
+
+    result = create_interfaces_cfg(tgen, topo1)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh, expected=False)
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route present in RIB. Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(
+        tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh, expected=False
+    )
+    assert (
+        result is not True
+    ), "Testcase {} : Failed \n Route present in RIB. Error: {}".format(tc_name, result)
+
+    step("Add back the deleted ip address on newly configured interface of R0")
+    topo1 = {
+        "r0": {
+            "links": {
+                "r3": {
+                    "ipv6": topo["routers"]["r0"]["links"]["r3"]["ipv6"],
+                    "interface": topo["routers"]["r0"]["links"]["r3"]["interface"],
+                }
+            }
+        }
+    }
+
+    result = create_interfaces_cfg(tgen, topo1)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    step("Shut no shut interface on R0")
+    dut = "r0"
+    intf = topo["routers"]["r0"]["links"]["r3"]["interface"]
+    shutdown_bringup_interface(tgen, dut, intf, False)
+
+    step("Verify that intraroute calculated for R1 intf on R0 is deleted.")
+    dut = "r1"
+
+    step("un shut the OSPF interface on R0")
+    dut = "r0"
+    shutdown_bringup_interface(tgen, dut, intf, True)
+
+    dut = "r1"
+    result = verify_ospf6_rib(tgen, dut, input_dict)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    protocol = "ospf"
+    result = verify_rib(tgen, "ipv6", dut, input_dict, protocol=protocol, next_hop=nh)
+    assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
+
+    write_test_footer(tc_name)
+
+
 def test_ospfv3_cost_tc52_p0(request):
     """OSPF Cost - verifying ospf interface cost functionality"""
     tc_name = request.node.name
@@ -368,7 +595,6 @@ def test_ospfv3_cost_tc52_p0(request):
     write_test_footer(tc_name)
 
 
-
 if __name__ == "__main__":
     args = ["-s"] + sys.argv[1:]
     sys.exit(pytest.main(args))
index a84f1a1eb6e9aed8f392d871518b3c93775fbb8b..faae4b3e1764685e6aa74039be30df4a3f6c371e 100644 (file)
@@ -54,7 +54,7 @@ from lib.common_config import (
     create_route_maps,
     shutdown_bringup_interface,
     create_interfaces_cfg,
-    topo_daemons,
+    topo_daemons
 )
 from lib.topolog import logger
 from lib.topojson import build_topo_from_json, build_config_from_json