summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pimd/pim_mroute.c7
-rw-r--r--tests/topotests/lib/common_config.py100
-rw-r--r--tests/topotests/lib/ospf.py187
-rw-r--r--tests/topotests/lib/topojson.py3
-rw-r--r--tests/topotests/ospf-dual-stack/test_ospf_dual_stack.dot107
-rw-r--r--tests/topotests/ospf-dual-stack/test_ospf_dual_stack.jpgbin0 -> 98314 bytes
-rw-r--r--tests/topotests/ospf-dual-stack/test_ospf_dual_stack.json255
-rw-r--r--tests/topotests/ospf-dual-stack/test_ospf_dual_stack.py152
8 files changed, 684 insertions, 127 deletions
diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c
index 23259900b7..afd38face3 100644
--- a/pimd/pim_mroute.c
+++ b/pimd/pim_mroute.c
@@ -1221,10 +1221,9 @@ void pim_mroute_update_counters(struct channel_oil *c_oil)
sg.src = c_oil->oil.mfcc_origin;
sg.grp = c_oil->oil.mfcc_mcastgrp;
- if (PIM_DEBUG_MROUTE)
- zlog_debug(
- "Channel%s is not installed no need to collect data from kernel",
- pim_str_sg_dump(&sg));
+ zlog_debug(
+ "Channel%s is not installed no need to collect data from kernel",
+ pim_str_sg_dump(&sg));
}
return;
}
diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py
index 175d660d1e..3f360ef40a 100644
--- a/tests/topotests/lib/common_config.py
+++ b/tests/topotests/lib/common_config.py
@@ -257,6 +257,7 @@ def create_common_configuration(
"bgp": "! BGP Config\n",
"vrf": "! VRF Config\n",
"ospf": "! OSPF Config\n",
+ "ospf6": "! OSPF Config\n",
"pim": "! PIM Config\n",
}
)
@@ -752,6 +753,12 @@ def start_topology(tgen, daemon=None):
TopoRouter.RD_OSPF, "{}/{}/ospfd.conf".format(TMPDIR, rname)
)
+ if daemon and "ospf6d" in daemon:
+ # Loading empty ospf.conf file to router, to start the bgp daemon
+ router.load_config(
+ TopoRouter.RD_OSPF6, "{}/{}/ospf6d.conf".format(TMPDIR, rname)
+ )
+
if daemon and "pimd" in daemon:
# Loading empty pimd.conf file to router, to start the pim deamon
router.load_config(
@@ -845,6 +852,9 @@ def topo_daemons(tgen, topo):
if "ospf" in topo["routers"][rtr] and "ospfd" not in daemon_list:
daemon_list.append("ospfd")
+ if "ospf6" in topo["routers"][rtr] and "ospf6d" not in daemon_list:
+ daemon_list.append("ospf6d")
+
for val in topo["routers"][rtr]["links"].values():
if "pim" in val and "pimd" not in daemon_list:
daemon_list.append("pimd")
@@ -1594,6 +1604,25 @@ def create_interfaces_cfg(tgen, topo, build=False):
-------
True or False
"""
+
+ def _create_interfaces_ospf_cfg(ospf, c_data, data, ospf_keywords):
+ interface_data = []
+ ip_ospf = "ipv6 ospf6" if ospf == "ospf6" else "ip ospf"
+ for keyword in ospf_keywords:
+ if keyword in data[ospf]:
+ intf_ospf_value = c_data["links"][destRouterLink][ospf][keyword]
+ if "delete" in data and data["delete"]:
+ interface_data.append(
+ "no {} {}".format(ip_ospf, keyword.replace("_", "-"))
+ )
+ else:
+ interface_data.append(
+ "{} {} {}".format(
+ ip_ospf, keyword.replace("_", "-"), intf_ospf_value
+ )
+ )
+ return interface_data
+
result = False
topo = deepcopy(topo)
@@ -1640,66 +1669,26 @@ def create_interfaces_cfg(tgen, topo, build=False):
else:
interface_data.append("ipv6 address {}\n".format(intf_addr))
+ ospf_keywords = [
+ "hello_interval",
+ "dead_interval",
+ "network",
+ "priority",
+ "cost",
+ ]
if "ospf" in data:
- ospf_data = data["ospf"]
- if "area" in ospf_data:
- intf_ospf_area = c_data["links"][destRouterLink]["ospf"]["area"]
- if "delete" in data and data["delete"]:
- interface_data.append("no ip ospf area")
- else:
- interface_data.append(
- "ip ospf area {}".format(intf_ospf_area)
- )
-
- if "hello_interval" in ospf_data:
- intf_ospf_hello = c_data["links"][destRouterLink]["ospf"][
- "hello_interval"
- ]
- if "delete" in data and data["delete"]:
- interface_data.append("no ip ospf " " hello-interval")
- else:
- interface_data.append(
- "ip ospf" " hello-interval {}".format(intf_ospf_hello)
- )
-
- if "dead_interval" in ospf_data:
- intf_ospf_dead = c_data["links"][destRouterLink]["ospf"][
- "dead_interval"
- ]
- if "delete" in data and data["delete"]:
- interface_data.append("no ip ospf" " dead-interval")
- else:
- interface_data.append(
- "ip ospf" " dead-interval {}".format(intf_ospf_dead)
- )
-
- if "network" in ospf_data:
- intf_ospf_nw = c_data["links"][destRouterLink]["ospf"][
- "network"
- ]
- if "delete" in data and data["delete"]:
- interface_data.append(
- "no ip ospf" " network {}".format(intf_ospf_nw)
- )
- else:
- interface_data.append(
- "ip ospf" " network {}".format(intf_ospf_nw)
- )
-
- if "priority" in ospf_data:
- intf_ospf_nw = c_data["links"][destRouterLink]["ospf"][
- "priority"
- ]
+ interface_data += _create_interfaces_ospf_cfg(
+ "ospf", c_data, data, ospf_keywords + ["area"]
+ )
+ if "ospf6" in data:
+ interface_data += _create_interfaces_ospf_cfg(
+ "ospf6", c_data, data, ospf_keywords
+ )
- if "delete" in data and data["delete"]:
- interface_data.append("no ip ospf" " priority")
- else:
- interface_data.append(
- "ip ospf" " priority {}".format(intf_ospf_nw)
- )
result = create_common_configuration(
tgen, c_router, interface_data, "interface_config", build=build
)
+
except InvalidCLIError:
# Traceback
errormsg = traceback.format_exc()
@@ -4395,4 +4384,3 @@ def verify_ip_nht(tgen, input_dict):
logger.debug("Exiting lib API: verify_ip_nht()")
return False
-
diff --git a/tests/topotests/lib/ospf.py b/tests/topotests/lib/ospf.py
index 3e368cd7d3..5bc9f14fea 100644
--- a/tests/topotests/lib/ospf.py
+++ b/tests/topotests/lib/ospf.py
@@ -94,7 +94,7 @@ def create_router_ospf(tgen, topo, input_dict=None, build=False, load_config=Tru
return result
-def __create_ospf_global(tgen, input_dict, router, build=False, load_config=True):
+def __create_ospf_global(tgen, input_dict, router, build=False, load_config=True, ospf="ospf"):
"""
Helper API to create ospf global configuration.
@@ -105,6 +105,33 @@ def __create_ospf_global(tgen, input_dict, router, build=False, load_config=True
* `router` : router to be configured.
* `build` : Only for initial setup phase this is set as True.
* `load_config` : Loading the config to router this is set as True.
+ * `ospf` : either 'ospf' or 'ospf6'
+
+ Usage
+ -----
+ input_dict = {
+ "routers": {
+ "r1": {
+ "links": {
+ "r3": {
+ "ipv6": "2013:13::1/64",
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ }
+ },
+ "ospf6": {
+ "router_id": "1.1.1.1",
+ "neighbors": {
+ "r3": {
+ "area": "1.1.1.1"
+ }
+ }
+ }
+ }
+ }
Returns
-------
@@ -115,17 +142,17 @@ def __create_ospf_global(tgen, input_dict, router, build=False, load_config=True
logger.debug("Entering lib API: __create_ospf_global()")
try:
- ospf_data = input_dict[router]["ospf"]
+ ospf_data = input_dict[router][ospf]
del_ospf_action = ospf_data.setdefault("delete", False)
if del_ospf_action:
- config_data = ["no router ospf"]
+ config_data = ["no router {}".format(ospf)]
result = create_common_configuration(
- tgen, router, config_data, "ospf", build, load_config
+ tgen, router, config_data, ospf, build, load_config
)
return result
config_data = []
- cmd = "router ospf"
+ cmd = "router {}".format(ospf)
config_data.append(cmd)
@@ -133,9 +160,9 @@ def __create_ospf_global(tgen, input_dict, router, build=False, load_config=True
router_id = ospf_data.setdefault("router_id", None)
del_router_id = ospf_data.setdefault("del_router_id", False)
if del_router_id:
- config_data.append("no ospf router-id")
+ config_data.append("no {} router-id".format(ospf))
if router_id:
- config_data.append("ospf router-id {}".format(router_id))
+ config_data.append("{} router-id {}".format(ospf, router_id))
# redistribute command
redistribute_data = ospf_data.setdefault("redistribute", {})
@@ -154,6 +181,7 @@ def __create_ospf_global(tgen, input_dict, router, build=False, load_config=True
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
+
# area information
area_data = ospf_data.setdefault("area", {})
if area_data:
@@ -173,6 +201,20 @@ def __create_ospf_global(tgen, input_dict, router, build=False, load_config=True
cmd = "no {}".format(cmd)
config_data.append(cmd)
+ # area interface information for ospf6d only
+ if ospf == "ospf6":
+ area_iface = ospf_data.setdefault("neighbors", {})
+ if area_iface:
+ for neighbor in area_iface:
+ if "area" in area_iface[neighbor]:
+ iface = input_dict[router]["links"][neighbor]["interface"]
+ cmd = "interface {} area {}".format(
+ iface, area_iface[neighbor]["area"]
+ )
+ if area_iface[neighbor].setdefault("delete", False):
+ cmd = "no {}".format(cmd)
+ config_data.append(cmd)
+
# summary information
summary_data = ospf_data.setdefault("summary-address", {})
if summary_data:
@@ -197,8 +239,9 @@ def __create_ospf_global(tgen, input_dict, router, build=False, load_config=True
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
+
result = create_common_configuration(
- tgen, router, config_data, "ospf", build, load_config
+ tgen, router, config_data, ospf, build, load_config
)
except InvalidCLIError:
@@ -235,7 +278,7 @@ def create_router_ospf6(tgen, topo, input_dict=None, build=False, load_config=Tr
-------
True or False
"""
- logger.debug("Entering lib API: create_router_ospf()")
+ logger.debug("Entering lib API: create_router_ospf6()")
result = False
if not input_dict:
@@ -244,67 +287,15 @@ def create_router_ospf6(tgen, topo, input_dict=None, build=False, load_config=Tr
topo = topo["routers"]
input_dict = deepcopy(input_dict)
for router in input_dict.keys():
- if "ospf" not in input_dict[router]:
- logger.debug("Router %s: 'ospf' not present in input_dict", router)
+ if "ospf6" not in input_dict[router]:
+ logger.debug("Router %s: 'ospf6' not present in input_dict", router)
continue
- result = __create_ospf_global(tgen, input_dict, router, build, load_config)
-
- logger.debug("Exiting lib API: create_router_ospf()")
- return result
-
-
-def __create_ospf6_global(tgen, input_dict, router, build=False, load_config=True):
- """
- Helper API to create ospf global configuration.
-
- Parameters
- ----------
- * `tgen` : Topogen object
- * `input_dict` : Input dict data, required when configuring from testcase
- * `router` : router id to be configured.
- * `build` : Only for initial setup phase this is set as True.
-
- Returns
- -------
- True or False
- """
-
- result = False
- logger.debug("Entering lib API: __create_ospf_global()")
- try:
-
- ospf_data = input_dict[router]["ospf6"]
- del_ospf_action = ospf_data.setdefault("delete", False)
- if del_ospf_action:
- config_data = ["no ipv6 router ospf"]
- result = create_common_configuration(
- tgen, router, config_data, "ospf", build, load_config
- )
- return result
-
- config_data = []
- cmd = "router ospf"
-
- config_data.append(cmd)
-
- router_id = ospf_data.setdefault("router_id", None)
- del_router_id = ospf_data.setdefault("del_router_id", False)
- if del_router_id:
- config_data.append("no ospf router-id")
- if router_id:
- config_data.append("ospf router-id {}".format(router_id))
-
- result = create_common_configuration(
- tgen, router, config_data, "ospf", build, load_config
+ result = __create_ospf_global(
+ tgen, input_dict, router, build, load_config, "ospf6"
)
- except InvalidCLIError:
- # Traceback
- errormsg = traceback.format_exc()
- logger.error(errormsg)
- return errormsg
- logger.debug("Exiting lib API: create_ospf_global()")
+ logger.debug("Exiting lib API: create_router_ospf6()")
return result
@@ -682,6 +673,70 @@ def verify_ospf_neighbor(tgen, topo, dut=None, input_dict=None, lan=False):
return result
+################################
+# Verification procs
+################################
+@retry(attempts=40, wait=2, return_is_str=True)
+def verify_ospf6_neighbor(tgen, topo):
+ """
+ This API is to verify ospf neighborship by running
+ show ip ospf neighbour command,
+
+ Parameters
+ ----------
+ * `tgen` : Topogen object
+ * `topo` : json file data
+
+ Usage
+ -----
+ Check FULL neighbors.
+ verify_ospf_neighbor(tgen, topo)
+
+ result = verify_ospf_neighbor(tgen, topo)
+
+ Returns
+ -------
+ True or False (Error Message)
+ """
+
+ logger.debug("Entering lib API: verify_ospf6_neighbor()")
+ result = False
+ for router, rnode in tgen.routers().items():
+ if "ospf6" not in topo["routers"][router]:
+ continue
+
+ logger.info("Verifying OSPF6 neighborship on router %s:", router)
+ show_ospf_json = run_frr_cmd(
+ rnode, "show ipv6 ospf6 neighbor json", isjson=True
+ )
+
+ if not show_ospf_json:
+ return "OSPF6 is not running"
+
+ ospf_nbr_list = topo["routers"][router]["ospf6"]["neighbors"]
+ no_of_peer = 0
+ for ospf_nbr in ospf_nbr_list:
+ ospf_nbr_rid = topo["routers"][ospf_nbr]["ospf6"]["router_id"]
+ for neighbor in show_ospf_json["neighbors"]:
+ if neighbor["neighborId"] == ospf_nbr_rid:
+ nh_state = neighbor["state"]
+ break
+ else:
+ return "[DUT: {}] OSPF6 peer {} missing".format(router, data_rid)
+
+ if nh_state == "Full":
+ no_of_peer += 1
+
+ if no_of_peer == len(ospf_nbr_list):
+ logger.info("[DUT: {}] OSPF6 is Converged".format(router))
+ result = True
+ else:
+ return "[DUT: {}] OSPF6 is not Converged".format(router)
+
+ logger.debug("Exiting API: verify_ospf6_neighbor()")
+ return result
+
+
@retry(attempts=21, wait=2, return_is_str=True)
def verify_ospf_rib(
tgen, dut, input_dict, next_hop=None, tag=None, metric=None, fib=None
diff --git a/tests/topotests/lib/topojson.py b/tests/topotests/lib/topojson.py
index 88e6f78b92..fcc6c19868 100644
--- a/tests/topotests/lib/topojson.py
+++ b/tests/topotests/lib/topojson.py
@@ -45,7 +45,7 @@ from lib.common_config import (
from lib.pim import create_pim_config, create_igmp_config
from lib.bgp import create_router_bgp
-from lib.ospf import create_router_ospf
+from lib.ospf import create_router_ospf, create_router_ospf6
ROUTER_LIST = []
@@ -314,6 +314,7 @@ def build_config_from_json(tgen, topo, save_bkup=True):
("igmp", create_igmp_config),
("bgp", create_router_bgp),
("ospf", create_router_ospf),
+ ("ospf6", create_router_ospf6),
]
)
diff --git a/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.dot b/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.dot
new file mode 100644
index 0000000000..2c6d0aab16
--- /dev/null
+++ b/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.dot
@@ -0,0 +1,107 @@
+## Color coding:
+#########################
+## Main FRR: #f08080 red
+## Switches: #d0e0d0 gray
+## RIP: #19e3d9 Cyan
+## RIPng: #fcb314 dark yellow
+## OSPFv2: #32b835 Green
+## OSPFv3: #19e3d9 Cyan
+## ISIS IPv4 #fcb314 dark yellow
+## ISIS IPv6 #9a81ec purple
+## BGP IPv4 #eee3d3 beige
+## BGP IPv6 #fdff00 yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph ospf_topo1 {
+ label="ospf dual stack";
+
+ # Routers
+ r1 [
+ label="r1\nrtr-id 1.1.1.1/32",
+ shape=doubleoctagon,
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r2 [
+ label="r2\nrtr-id 2.2.2.2/32",
+ shape=doubleoctagon,
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r3 [
+ label="r3\nrtr-id 3.3.3.3/32",
+ shape=doubleoctagon,
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r4 [
+ label="r4\nrtr-id 4.4.4.4/32",
+ shape=doubleoctagon,
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r5 [
+ label="r5\nrtr-id 5.5.5.5/32",
+ shape=doubleoctagon,
+ fillcolor="#f08080",
+ style=filled,
+ ];
+
+ # Switches
+ s1 [
+ label="s1\n10.0.13.0/24\n2013:13::/64",
+ shape=oval,
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+ s2 [
+ label="s2\n10.0.23.0/24\n2023:23::/64",
+ shape=oval,
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+ s3 [
+ label="s3\n10.0.34.0/24\n2034:34::/64",
+ shape=oval,
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+ s4 [
+ label="s4\n10.0.24.0/24\n2024:24::/64",
+ shape=oval,
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+ s5 [
+ label="s5\n10.0.45.0/24\n2045:45::/64",
+ shape=oval,
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+
+ # Connections
+ subgraph cluster1 {
+ label="area 1.1.1.1"
+
+ r1 -- s1 [label="eth0\n.1\n::1"];
+ r3 -- s1 [label="eth0\n.3\n::3"];
+ r3 -- s2 [label="eth1\n.3\n::3"];
+ r2 -- s2 [label="eth0\n.2\n::2"];
+ }
+
+ subgraph cluster0 {
+ label="area 0.0.0.0"
+
+ r3 -- s3 [label="eth2\n.3\n::3"];
+ r4 -- s3 [label="eth0\n.4\n::4"];
+ r2 -- s4 [label="eth1\n.2\n::2"];
+ r4 -- s4 [label="eth1\n.4\n::4"];
+ }
+
+ subgraph cluster2 {
+ label="area 2.2.2.2"
+
+ r4 -- s5 [label="eth2\n.4\n::4"];
+ r5 -- s5 [label="eth0\n.5\n::5"];
+ }
+}
diff --git a/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.jpg b/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.jpg
new file mode 100644
index 0000000000..44efda8390
--- /dev/null
+++ b/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.jpg
Binary files differ
diff --git a/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.json b/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.json
new file mode 100644
index 0000000000..c8a3ce783b
--- /dev/null
+++ b/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.json
@@ -0,0 +1,255 @@
+{
+ "ipv4base": "10.0.0.0",
+ "ipv4mask": 24,
+ "link_ip_start": {
+ "ipv4": "10.0.0.0",
+ "v4mask": 24
+ },
+ "lo_prefix": {
+ "ipv4": "1.0.",
+ "v4mask": 32
+ },
+ "routers": {
+ "r1": {
+ "links": {
+ "r3": {
+ "ipv4": "10.0.13.1/24",
+ "ipv6": "2013:13::1/64",
+ "ospf": {
+ "area": "1.1.1.1",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4
+ }
+ }
+ },
+ "ospf": {
+ "router_id": "1.1.1.1",
+ "neighbors": {
+ "r3": {}
+ }
+ },
+ "ospf6": {
+ "router_id": "1.1.1.1",
+ "neighbors": {
+ "r3": {
+ "area": "1.1.1.1"
+ }
+ }
+ }
+ },
+ "r2": {
+ "links": {
+ "r3": {
+ "ipv4": "10.0.23.2/24",
+ "ipv6": "2023:23::2/64",
+ "ospf": {
+ "area": "1.1.1.1",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ },
+ "r4": {
+ "ipv4": "10.0.24.2/24",
+ "ipv6": "2034:34::2/64",
+ "ospf": {
+ "area": "0.0.0.0",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ }
+ },
+ "ospf": {
+ "router_id": "2.2.2.2",
+ "neighbors": {
+ "r3": {},
+ "r4": {}
+ }
+ },
+ "ospf6": {
+ "router_id": "2.2.2.2",
+ "neighbors": {
+ "r3": { "area": "1.1.1.1" },
+ "r4": { "area": "0.0.0.0" }
+ }
+ }
+ },
+ "r3": {
+ "links": {
+ "r1": {
+ "ipv4": "10.0.13.3/24",
+ "ipv6": "2013:13::3/64",
+ "ospf": {
+ "area": "1.1.1.1",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ },
+ "r2": {
+ "ipv4": "10.0.23.3/24",
+ "ipv6": "2023:23::3/64",
+ "ospf": {
+ "area": "1.1.1.1",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ },
+ "r4": {
+ "ipv4": "10.0.34.3/24",
+ "ipv6": "2034:34::3/64",
+ "ospf": {
+ "area": "0.0.0.0",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ }
+ },
+ "ospf": {
+ "router_id": "3.3.3.3",
+ "neighbors": {
+ "r1": {},
+ "r2": {},
+ "r4": {}
+ }
+ },
+ "ospf6": {
+ "router_id": "3.3.3.3",
+ "neighbors": {
+ "r1": { "area": "1.1.1.1" },
+ "r2": { "area": "1.1.1.1" },
+ "r4": { "area": "0.0.0.0" }
+ }
+ }
+ },
+ "r4": {
+ "links": {
+ "r2": {
+ "ipv4": "10.0.24.4/24",
+ "ipv6": "2024:24::4/64",
+ "ospf": {
+ "area": "0.0.0.0",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ },
+ "r3": {
+ "ipv4": "10.0.34.4/24",
+ "ipv6": "2034:34::4/64",
+ "ospf": {
+ "area": "0.0.0.0",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ },
+ "r5": {
+ "ipv4": "10.0.45.4/24",
+ "ipv6": "2045:45::4/64",
+ "ospf": {
+ "area": "2.2.2.2",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ }
+ },
+ "ospf": {
+ "router_id": "4.4.4.4",
+ "neighbors": {
+ "r2": {},
+ "r3": {},
+ "r5": {}
+ }
+ },
+ "ospf6": {
+ "router_id": "4.4.4.4",
+ "neighbors": {
+ "r2": { "area": "0.0.0.0" },
+ "r3": { "area": "0.0.0.0" },
+ "r5": { "area": "2.2.2.2" }
+ }
+ }
+ },
+ "r5": {
+ "links": {
+ "r4": {
+ "ipv4": "10.0.45.5/24",
+ "ipv6": "2045:45::5/64",
+ "ospf": {
+ "area": "2.2.2.2",
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ },
+ "ospf6": {
+ "hello_interval": 1,
+ "dead_interval": 4,
+ "network": "point-to-point"
+ }
+ }
+ },
+ "ospf": {
+ "router_id": "5.5.5.5",
+ "neighbors": {
+ "r4": {}
+ }
+ },
+ "ospf6": {
+ "router_id": "5.5.5.5",
+ "neighbors": {
+ "r4": { "area": "2.2.2.2" }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.py b/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.py
new file mode 100644
index 0000000000..5e7802fa04
--- /dev/null
+++ b/tests/topotests/ospf-dual-stack/test_ospf_dual_stack.py
@@ -0,0 +1,152 @@
+#!/usr/bin/python
+
+import os
+import sys
+import time
+import pytest
+import json
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+sys.path.append(os.path.join(CWD, "../lib/"))
+
+from mininet.topo import Topo
+from lib.topogen import Topogen, get_topogen
+
+from lib.common_config import (
+ start_topology,
+ write_test_header,
+ write_test_footer,
+ reset_config_on_routers,
+ stop_router,
+ start_router,
+ verify_rib,
+ create_static_routes,
+ step,
+ start_router_daemons,
+ shutdown_bringup_interface,
+ topo_daemons,
+ create_prefix_lists,
+ create_interfaces_cfg,
+ run_frr_cmd,
+)
+from lib.topolog import logger
+from lib.topojson import build_topo_from_json, build_config_from_json
+from lib.ospf import (
+ verify_ospf_neighbor,
+ verify_ospf6_neighbor,
+ create_router_ospf,
+ create_router_ospf6,
+ verify_ospf_summary,
+ redistribute_ospf,
+ verify_ospf_database,
+)
+
+# Global variables
+topo = None
+
+# Reading the data from JSON File for topology creation
+jsonFile = "{}/test_ospf_dual_stack.json".format(CWD)
+try:
+ with open(jsonFile, "r") as topoJson:
+ topo = json.load(topoJson)
+except IOError:
+ assert False, "Could not read file {}".format(jsonFile)
+
+
+class CreateTopo(Topo):
+ """Test topology builder."""
+
+ 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."""
+ 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 daemons 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)
+
+ # Api call verify whether OSPF converged
+ ospf_covergence_ipv4 = verify_ospf_neighbor(tgen, topo)
+ assert ospf_covergence_ipv4 is True, "setup_module :Failed \n Error:" " {}".format(
+ ospf_covergence_ipv4
+ )
+
+ # Api call verify whether OSPF6 converged
+ ospf_covergence_ipv6 = verify_ospf6_neighbor(tgen, topo)
+ assert ospf_covergence_ipv6 is True, "setup_module :Failed \n Error:" " {}".format(
+ ospf_covergence_ipv6
+ )
+ 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 topology 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_ospf_dual_stack(request):
+ """OSPF test dual stack."""
+
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ # Don't run this test if we have any failure.
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ global topo
+
+ step("Bring up the base configuration as per the JSON topology")
+ reset_config_on_routers(tgen)
+ write_test_footer(tc_name)
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))