Merge pull request #11164 from kuldeepkash/pim_v6

This commit is contained in:
Martin Winter 2022-07-27 10:05:25 +02:00 committed by GitHub
commit 8d4abfc9b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1063 additions and 99 deletions

View File

@ -449,6 +449,8 @@ def check_router_status(tgen):
daemons.append("zebra")
if "pimd" in result:
daemons.append("pimd")
if "pim6d" in result:
daemons.append("pim6d")
if "ospfd" in result:
daemons.append("ospfd")
if "ospf6d" in result:
@ -1035,6 +1037,12 @@ def start_topology(tgen, daemon=None):
TopoRouter.RD_PIM, "{}/{}/pimd.conf".format(tgen.logdir, rname)
)
if daemon and "pim6d" in daemon:
# Loading empty pimd.conf file to router, to start the pim6d deamon
router.load_config(
TopoRouter.RD_PIM6, "{}/{}/pim6d.conf".format(tgen.logdir, rname)
)
# Starting routers
logger.info("Starting all routers once topology is created")
tgen.start_router()
@ -1131,6 +1139,8 @@ def topo_daemons(tgen, topo=None):
for val in topo["routers"][rtr]["links"].values():
if "pim" in val and "pimd" not in daemon_list:
daemon_list.append("pimd")
if "pim6" in val and "pim6d" not in daemon_list:
daemon_list.append("pim6d")
if "ospf" in val and "ospfd" not in daemon_list:
daemon_list.append("ospfd")
if "ospf6" in val and "ospf6d" not in daemon_list:
@ -3234,6 +3244,86 @@ def configure_interface_mac(tgen, input_dict):
return True
def socat_send_igmp_join_traffic(
tgen,
server,
protocol_option,
igmp_groups,
send_from_intf,
send_from_intf_ip=None,
port=12345,
reuseaddr=True,
join=False,
traffic=False,
):
"""
API to send IGMP join using SOCAT tool
Parameters:
-----------
* `tgen` : Topogen object
* `server`: iperf server, from where IGMP join would be sent
* `protocol_option`: Protocol options, ex: UDP6-RECV
* `igmp_groups`: IGMP group for which join has to be sent
* `send_from_intf`: Interface from which join would be sent
* `send_from_intf_ip`: Interface IP, default is None
* `port`: Port to be used, default is 12345
* `reuseaddr`: True|False, bydefault True
* `join`: If join needs to be sent
* `traffic`: If traffic needs to be sent
returns:
--------
errormsg or True
"""
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
rnode = tgen.routers()[server]
socat_cmd = "socat -u "
# UDP4/TCP4/UDP6/UDP6-RECV
if protocol_option:
socat_cmd += "{}".format(protocol_option)
if port:
socat_cmd += ":{},".format(port)
if reuseaddr:
socat_cmd += "{},".format("reuseaddr")
# Group address range to cover
if igmp_groups:
if not isinstance(igmp_groups, list):
igmp_groups = [igmp_groups]
for igmp_group in igmp_groups:
if join:
join_traffic_option = "ipv6-join-group"
elif traffic:
join_traffic_option = "ipv6-join-group-source"
if send_from_intf and not send_from_intf_ip:
socat_cmd += "{}='[{}]:{}'".format(
join_traffic_option, igmp_group, send_from_intf
)
else:
socat_cmd += "{}='[{}]:{}:[{}]'".format(
join_traffic_option, igmp_group, send_from_intf, send_from_intf_ip
)
socat_cmd += " STDOUT"
socat_cmd += " &>{}/socat.logs &".format(tgen.logdir)
# Run socat command to send IGMP join
logger.info("[DUT: {}]: Running command: [{}]".format(server, socat_cmd))
output = rnode.run("set +m; {} sleep 0.5".format(socat_cmd))
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
return True
#############################################
# Verification APIs
#############################################

View File

@ -36,6 +36,7 @@ from lib.common_config import (
InvalidCLIError,
retry,
run_frr_cmd,
validate_ip_address,
)
from lib.micronet import get_exec_path
from lib.topolog import logger
@ -47,7 +48,7 @@ CWD = os.path.dirname(os.path.realpath(__file__))
def create_pim_config(tgen, topo, input_dict=None, build=False, load_config=True):
"""
API to configure pim on router
API to configure pim/pimv6 on router
Parameters
----------
@ -70,6 +71,16 @@ def create_pim_config(tgen, topo, input_dict=None, build=False, load_config=True
"prefix-list": "pf_list_1"
"delete": True
}]
},
"pim6": {
"disable" : ["l1-i1-eth1"],
"rp": [{
"rp_addr" : "2001:db8:f::5:17".
"keep-alive-timer": "100"
"group_addr_range": ["FF00::/8"]
"prefix-list": "pf_list_1"
"delete": True
}]
}
}
}
@ -97,12 +108,8 @@ def create_pim_config(tgen, topo, input_dict=None, build=False, load_config=True
# Now add RP config to all routers
for router in input_dict.keys():
if "pim" not in input_dict[router]:
continue
if "rp" not in input_dict[router]["pim"]:
continue
if "pim" in input_dict[router] or "pim6" in input_dict[router]:
_add_pim_rp_config(tgen, topo, input_dict, router, build, config_data_dict)
try:
result = create_common_configurations(
tgen, config_data_dict, "pim", build, load_config
@ -133,33 +140,53 @@ def _add_pim_rp_config(tgen, topo, input_dict, router, build, config_data_dict):
"""
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
rp_data = []
# PIMv4
pim_data = None
if "pim" in input_dict[router]:
pim_data = input_dict[router]["pim"]
rp_data = pim_data["rp"]
if "rp" in input_dict[router]["pim"]:
rp_data += pim_data["rp"]
# PIMv6
pim6_data = None
if "pim6" in input_dict[router]:
pim6_data = input_dict[router]["pim6"]
if "rp" in input_dict[router]["pim6"]:
rp_data += pim6_data["rp"]
# Configure this RP on every router.
for dut in tgen.routers():
# At least one interface must be enabled for PIM on the router
pim_if_enabled = False
pim6_if_enabled = False
for destLink, data in topo[dut]["links"].items():
if "pim" in data:
pim_if_enabled = True
if not pim_if_enabled:
if "pim6" in data:
pim6_if_enabled = True
if not pim_if_enabled and pim_data:
continue
if not pim6_if_enabled and pim6_data:
continue
config_data = []
if rp_data:
for rp_dict in deepcopy(rp_data):
# ip address of RP
if "rp_addr" not in rp_dict and build:
logger.error(
"Router %s: 'ip address of RP' not " "present in input_dict/JSON",
"Router %s: 'ip address of RP' not "
"present in input_dict/JSON",
router,
)
return False
rp_addr = rp_dict.setdefault("rp_addr", None)
if rp_addr:
addr_type = validate_ip_address(rp_addr)
# Keep alive Timer
keep_alive_timer = rp_dict.setdefault("keep_alive_timer", None)
@ -181,10 +208,16 @@ def _add_pim_rp_config(tgen, topo, input_dict, router, build, config_data_dict):
del_action = rp_dict.setdefault("delete", False)
if keep_alive_timer:
if addr_type == "ipv4":
cmd = "ip pim rp keep-alive-timer {}".format(keep_alive_timer)
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
if addr_type == "ipv6":
cmd = "ipv6 pim rp keep-alive-timer {}".format(keep_alive_timer)
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
if rp_addr:
if group_addr_range:
@ -192,13 +225,29 @@ def _add_pim_rp_config(tgen, topo, input_dict, router, build, config_data_dict):
group_addr_range = [group_addr_range]
for grp_addr in group_addr_range:
if addr_type == "ipv4":
cmd = "ip pim rp {} {}".format(rp_addr, grp_addr)
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
if addr_type == "ipv6":
cmd = "ipv6 pim rp {} {}".format(rp_addr, grp_addr)
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
if prefix_list:
cmd = "ip pim rp {} prefix-list {}".format(rp_addr, prefix_list)
if addr_type == "ipv4":
cmd = "ip pim rp {} prefix-list {}".format(
rp_addr, prefix_list
)
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
if addr_type == "ipv6":
cmd = "ipv6 pim rp {} prefix-list {}".format(
rp_addr, prefix_list
)
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
@ -319,6 +368,121 @@ def create_igmp_config(tgen, topo, input_dict=None, build=False):
return result
def create_mld_config(tgen, topo, input_dict=None, build=False):
"""
API to configure mld for PIMv6 on router
Parameters
----------
* `tgen` : Topogen object
* `topo` : json file data
* `input_dict` : Input dict data, required when configuring from
testcase
* `build` : Only for initial setup phase this is set as True.
Usage
-----
input_dict = {
"r1": {
"mld": {
"interfaces": {
"r1-r0-eth0" :{
"mld":{
"version": "2",
"delete": True
"query": {
"query-interval" : 100,
"query-max-response-time": 200
}
}
}
}
}
}
}
Returns
-------
True or False
"""
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
result = False
if not input_dict:
input_dict = deepcopy(topo)
else:
topo = topo["routers"]
input_dict = deepcopy(input_dict)
for router in input_dict.keys():
if "mld" not in input_dict[router]:
logger.debug("Router %s: 'mld' is not present in " "input_dict", router)
continue
mld_data = input_dict[router]["mld"]
if "interfaces" in mld_data:
config_data = []
intf_data = mld_data["interfaces"]
for intf_name in intf_data.keys():
cmd = "interface {}".format(intf_name)
config_data.append(cmd)
protocol = "mld"
del_action = intf_data[intf_name]["mld"].setdefault("delete", False)
cmd = "ipv6 mld"
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
del_attr = intf_data[intf_name]["mld"].setdefault("delete_attr", False)
join = intf_data[intf_name]["mld"].setdefault("join", None)
source = intf_data[intf_name]["mld"].setdefault("source", None)
version = intf_data[intf_name]["mld"].setdefault("version", False)
query = intf_data[intf_name]["mld"].setdefault("query", {})
if version:
cmd = "ipv6 {} version {}".format(protocol, version)
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
if source and join:
for group in join:
cmd = "ipv6 {} join {} {}".format(protocol, group, source)
if del_attr:
cmd = "no {}".format(cmd)
config_data.append(cmd)
elif join:
for group in join:
cmd = "ipv6 {} join {}".format(protocol, group)
if del_attr:
cmd = "no {}".format(cmd)
config_data.append(cmd)
if query:
for _query, value in query.items():
if _query != "delete":
cmd = "ipv6 {} {} {}".format(protocol, _query, value)
if "delete" in intf_data[intf_name][protocol]["query"]:
cmd = "no {}".format(cmd)
config_data.append(cmd)
try:
result = create_common_configuration(
tgen, router, config_data, "interface_config", build=build
)
except InvalidCLIError:
errormsg = traceback.format_exc()
logger.error(errormsg)
return errormsg
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
return result
def _enable_disable_pim_config(tgen, topo, input_dict, router, build=False):
"""
Helper API to enable or disable pim on interfaces
@ -338,7 +502,7 @@ def _enable_disable_pim_config(tgen, topo, input_dict, router, build=False):
config_data = []
# Enable pim on interfaces
# Enable pim/pim6 on interfaces
for destRouterLink, data in sorted(topo[router]["links"].items()):
if "pim" in data and data["pim"] == "enable":
# Loopback interfaces
@ -351,6 +515,17 @@ def _enable_disable_pim_config(tgen, topo, input_dict, router, build=False):
config_data.append(cmd)
config_data.append("ip pim")
if "pim6" in data and data["pim6"] == "enable":
# Loopback interfaces
if "type" in data and data["type"] == "loopback":
interface_name = destRouterLink
else:
interface_name = data["interface"]
cmd = "interface {}".format(interface_name)
config_data.append(cmd)
config_data.append("ipv6 pim")
# pim global config
if "pim" in input_dict[router]:
pim_data = input_dict[router]["pim"]
@ -366,6 +541,21 @@ def _enable_disable_pim_config(tgen, topo, input_dict, router, build=False):
cmd = "no {}".format(cmd)
config_data.append(cmd)
# pim6 global config
if "pim6" in input_dict[router]:
pim6_data = input_dict[router]["pim6"]
del_action = pim6_data.setdefault("delete", False)
for t in [
"join-prune-interval",
"keep-alive-timer",
"register-suppress-time",
]:
if t in pim6_data:
cmd = "ipv6 pim {} {}".format(t, pim6_data[t])
if del_action:
cmd = "no {}".format(cmd)
config_data.append(cmd)
return config_data
@ -732,9 +922,6 @@ def verify_upstream_iif(
"[DUT: %s]: Verifying upstream Inbound Interface" " for IGMP groups received:",
dut,
)
show_ip_pim_upstream_json = run_frr_cmd(
rnode, "show ip pim upstream json", isjson=True
)
if type(group_addresses) is not list:
group_addresses = [group_addresses]
@ -742,6 +929,17 @@ def verify_upstream_iif(
if type(iif) is not list:
iif = [iif]
for grp in group_addresses:
addr_type = validate_ip_address(grp)
if addr_type == "ipv4":
ip_cmd = "ip"
elif addr_type == "ipv6":
ip_cmd = "ipv6"
cmd = "show {} pim upstream json".format(ip_cmd)
show_ip_pim_upstream_json = run_frr_cmd(rnode, cmd, isjson=True)
for grp_addr in group_addresses:
# Verify group address
if grp_addr not in show_ip_pim_upstream_json:
@ -883,13 +1081,19 @@ def verify_join_state_and_timer(
"[DUT: %s]: Verifying Join state and Join Timer" " for IGMP groups received:",
dut,
)
show_ip_pim_upstream_json = run_frr_cmd(
rnode, "show ip pim upstream json", isjson=True
)
if type(group_addresses) is not list:
group_addresses = [group_addresses]
for grp in group_addresses:
addr_type = validate_ip_address(grp)
if addr_type == "ipv4":
cmd = "show ip pim upstream json"
elif addr_type == "ipv6":
cmd = "show ipv6 pim upstream json"
show_ip_pim_upstream_json = run_frr_cmd(rnode, cmd, isjson=True)
for grp_addr in group_addresses:
# Verify group address
if grp_addr not in show_ip_pim_upstream_json:
@ -1010,20 +1214,6 @@ def verify_mroutes(
rnode = tgen.routers()[dut]
if return_uptime:
logger.info("Sleeping for %s sec..", mwait)
sleep(mwait)
logger.info("[DUT: %s]: Verifying ip mroutes", dut)
show_ip_mroute_json = run_frr_cmd(rnode, "show ip mroute json", isjson=True)
if return_uptime:
uptime_dict = {}
if bool(show_ip_mroute_json) == False:
error_msg = "[DUT %s]: mroutes are not present or flushed out !!" % (dut)
return error_msg
if not isinstance(group_addresses, list):
group_addresses = [group_addresses]
@ -1033,6 +1223,30 @@ def verify_mroutes(
if not isinstance(oil, list) and oil != "none":
oil = [oil]
for grp in group_addresses:
addr_type = validate_ip_address(grp)
if addr_type == "ipv4":
ip_cmd = "ip"
elif addr_type == "ipv6":
ip_cmd = "ipv6"
if return_uptime:
logger.info("Sleeping for %s sec..", mwait)
sleep(mwait)
logger.info("[DUT: %s]: Verifying ip mroutes", dut)
show_ip_mroute_json = run_frr_cmd(
rnode, "show {} mroute json".format(ip_cmd), isjson=True
)
if return_uptime:
uptime_dict = {}
if bool(show_ip_mroute_json) == False:
error_msg = "[DUT %s]: mroutes are not present or flushed out !!" % (dut)
return error_msg
for grp_addr in group_addresses:
if grp_addr not in show_ip_mroute_json:
errormsg = "[DUT %s]: Verifying (%s, %s) mroute," "[FAILED]!! " % (
@ -1214,15 +1428,20 @@ def verify_pim_rp_info(
rnode = tgen.routers()[dut]
logger.info("[DUT: %s]: Verifying ip rp info", dut)
show_ip_rp_info_json = run_frr_cmd(rnode, "show ip pim rp-info json", isjson=True)
if type(group_addresses) is not list:
group_addresses = [group_addresses]
if type(oif) is not list:
oif = [oif]
for grp in group_addresses:
addr_type = validate_ip_address(grp)
if addr_type == "ipv4":
ip_cmd = "ip"
elif addr_type == "ipv6":
ip_cmd = "ipv6"
for grp_addr in group_addresses:
if rp is None:
rp_details = find_rp_details(tgen, topo)
@ -1232,9 +1451,14 @@ def verify_pim_rp_info(
else:
iamRP = False
else:
if addr_type == "ipv4":
show_ip_route_json = run_frr_cmd(
rnode, "show ip route connected json", isjson=True
)
elif addr_type == "ipv6":
show_ip_route_json = run_frr_cmd(
rnode, "show ipv6 route connected json", isjson=True
)
for _rp in show_ip_route_json.keys():
if rp == _rp.split("/")[0]:
iamRP = True
@ -1242,16 +1466,27 @@ def verify_pim_rp_info(
else:
iamRP = False
logger.info("[DUT: %s]: Verifying ip rp info", dut)
cmd = "show {} pim rp-info json".format(ip_cmd)
show_ip_rp_info_json = run_frr_cmd(rnode, cmd, isjson=True)
if rp not in show_ip_rp_info_json:
errormsg = "[DUT %s]: Verifying rp-info" "for rp_address %s [FAILED]!! " % (
dut,
rp,
errormsg = (
"[DUT %s]: Verifying rp-info "
"for rp_address %s [FAILED]!! " % (dut, rp)
)
return errormsg
else:
group_addr_json = show_ip_rp_info_json[rp]
for rp_json in group_addr_json:
if "rpAddress" not in rp_json:
errormsg = "[DUT %s]: %s key not " "present in rp-info " % (
dut,
"rpAddress",
)
return errormsg
if oif is not None:
found = False
if rp_json["outboundInterface"] not in oif:
@ -1380,14 +1615,26 @@ def verify_pim_state(
rnode = tgen.routers()[dut]
logger.info("[DUT: %s]: Verifying pim state", dut)
show_pim_state_json = run_frr_cmd(rnode, "show ip pim state json", isjson=True)
if installed_fl is None:
installed_fl = 1
if type(group_addresses) is not list:
group_addresses = [group_addresses]
for grp in group_addresses:
addr_type = validate_ip_address(grp)
if addr_type == "ipv4":
ip_cmd = "ip"
elif addr_type == "ipv6":
ip_cmd = "ipv6"
logger.info("[DUT: %s]: Verifying pim state", dut)
show_pim_state_json = run_frr_cmd(
rnode, "show {} pim state json".format(ip_cmd), isjson=True
)
if installed_fl is None:
installed_fl = 1
for grp_addr in group_addresses:
if src_address is None:
src_address = "*"
@ -3635,7 +3882,7 @@ def verify_local_igmp_groups(tgen, dut, interface, group_addresses):
return True
def verify_pim_interface_traffic(tgen, input_dict, return_stats=True):
def verify_pim_interface_traffic(tgen, input_dict, return_stats=True, addr_type="ipv4"):
"""
Verify ip pim interface traffice by running
"show ip pim interface traffic" cli
@ -3645,6 +3892,8 @@ def verify_pim_interface_traffic(tgen, input_dict, return_stats=True):
* `tgen`: topogen object
* `input_dict(dict)`: defines DUT, what and from which interfaces
traffic needs to be verified
* [optional]`addr_type`: specify address-family, default is ipv4
Usage
-----
input_dict = {
@ -3675,9 +3924,13 @@ def verify_pim_interface_traffic(tgen, input_dict, return_stats=True):
rnode = tgen.routers()[dut]
logger.info("[DUT: %s]: Verifying pim interface traffic", dut)
show_pim_intf_traffic_json = run_frr_cmd(
rnode, "show ip pim interface traffic json", isjson=True
)
if addr_type == "ipv4":
cmd = "show ip pim interface traffic json"
elif addr_type == "ipv6":
cmd = "show ipv6 pim interface traffic json"
show_pim_intf_traffic_json = run_frr_cmd(rnode, cmd, isjson=True)
output_dict[dut] = {}
for intf, data in input_dict[dut].items():

View File

@ -725,6 +725,7 @@ class TopoRouter(TopoGear):
RD_PBRD = 16
RD_PATH = 17
RD_SNMP = 18
RD_PIM6 = 19
RD = {
RD_FRR: "frr",
RD_ZEBRA: "zebra",
@ -735,6 +736,7 @@ class TopoRouter(TopoGear):
RD_ISIS: "isisd",
RD_BGP: "bgpd",
RD_PIM: "pimd",
RD_PIM6: "pim6d",
RD_LDP: "ldpd",
RD_EIGRP: "eigrpd",
RD_NHRP: "nhrpd",
@ -820,7 +822,8 @@ class TopoRouter(TopoGear):
Possible daemon values are: TopoRouter.RD_ZEBRA, TopoRouter.RD_RIP,
TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
TopoRouter.RD_PIM, TopoRouter.RD_PBR, TopoRouter.RD_SNMP.
TopoRouter.RD_PIM, TopoRouter.RD_PIM6, TopoRouter.RD_PBR,
TopoRouter.RD_SNMP.
Possible `source` values are `None` for an empty config file, a path name which is
used directly, or a file name with no path components which is first looked for
@ -1276,6 +1279,7 @@ def diagnose_env_linux(rundir):
"ripngd",
"isisd",
"pimd",
"pim6d",
"ldpd",
"pbrd",
]:

View File

@ -41,7 +41,11 @@ from lib.common_config import (
number_to_column,
)
from lib.ospf import create_router_ospf
from lib.pim import create_igmp_config, create_pim_config
from lib.pim import (
create_igmp_config,
create_pim_config,
create_mld_config,
)
from lib.topolog import logger
@ -332,6 +336,7 @@ def build_config_from_json(tgen, topo=None, save_bkup=True):
("route_maps", create_route_maps),
("pim", create_pim_config),
("igmp", create_igmp_config),
("mld", create_mld_config),
("bgp", create_router_bgp),
("ospf", create_router_ospf),
]
@ -352,7 +357,9 @@ def build_config_from_json(tgen, topo=None, save_bkup=True):
logger.info("build_config_from_json: failed to configure topology")
pytest.exit(1)
logger.info("Built config now clearing ospf neighbors as that router-id might not be what is used")
logger.info(
"Built config now clearing ospf neighbors as that router-id might not be what is used"
)
for ospf in ["ospf", "ospf6"]:
for router in data:
if ospf not in data[router]:

View File

@ -1330,6 +1330,7 @@ class Router(Node):
"isisd": 0,
"bgpd": 0,
"pimd": 0,
"pim6d": 0,
"ldpd": 0,
"eigrpd": 0,
"nhrpd": 0,

View File

@ -0,0 +1,197 @@
{
"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"}
}
},
"r1": {
"links": {
"lo": {"ipv6": "auto", "type": "loopback", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r0": {"ipv6": "auto", "pim6": "enable"},
"r2": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r3": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r4": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}}
},
"ospf6": {
"router_id": "100.1.1.0",
"neighbors": {
"r2": {},
"r3": {},
"r4": {}
},
"redistribute": [
{
"redist_type": "static"
},
{
"redist_type": "connected"
}
]
},
"mld": {
"interfaces": {
"r1-r0-eth0" :{
"mld":{
}
}
}
}
},
"r2": {
"links": {
"lo": {"ipv6": "auto", "type": "loopback", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r1": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r3": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}}
},
"ospf6": {
"router_id": "100.1.1.1",
"neighbors": {
"r1": {},
"r3": {}
},
"redistribute": [
{
"redist_type": "static"
},
{
"redist_type": "connected"
}
]
}
},
"r3": {
"links": {
"lo": {"ipv6": "auto", "type": "loopback", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r1": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r2": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r4": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r5": {"ipv6": "auto", "pim6": "enable"}
},
"ospf6": {
"router_id": "100.1.1.2",
"neighbors": {
"r1": {},
"r2": {},
"r4": {}
},
"redistribute": [
{
"redist_type": "static"
},
{
"redist_type": "connected"
}
]
}
},
"r4": {
"links": {
"lo": {"ipv6": "auto", "type": "loopback", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r1": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}},
"r3": {"ipv6": "auto", "pim6": "enable",
"ospf6": {
"area": "0.0.0.0",
"hello_interval": 1,
"dead_interval": 4
}}
},
"ospf6": {
"router_id": "100.1.1.3",
"neighbors": {
"r1": {},
"r3": {}
},
"redistribute": [
{
"redist_type": "static"
},
{
"redist_type": "connected"
}
]
}
},
"r5": {
"links": {
"r3": {"ipv6": "auto"}
}
}
}
}

View File

@ -0,0 +1,412 @@
#!/usr/bin/env python
#
# Copyright (c) 2022 by VMware, Inc. ("VMware")
# Used Copyright (c) 2018 by Network Device Education Foundation,
# Inc. ("NetDEF") in this file.
#
# Permission to use, copy, modify, and/or distribute this software
# for any purpose with or without fee is hereby granted, provided
# that the above copyright notice and this permission notice appear
# in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND VMWARE DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
#
"""
Following tests are covered to test Multicast basic functionality:
Topology:
_______r2_____
| |
iperf | | iperf
r0-----r1-------------r3-----r5
| |
|_____________|
r4
Test steps
- Create topology (setup module)
- Bring up topology
TC_1 : Verify upstream interfaces(IIF) and join state are updated properly
after adding and deleting the static RP
TC_2 : Verify IIF and OIL in "show ip pim state" updated properly after
adding and deleting the static RP
TC_3: (*, G) Mroute entry are cleared when static RP gets deleted
TC_4: Verify (*,G) prune is send towards the RP after deleting the static RP
TC_24 : Verify (*,G) and (S,G) populated correctly when SPT and RPT share the
same path
"""
import os
import sys
import json
import time
import pytest
from time import sleep
import datetime
# Save the Current Working Directory to find configuration files.
CWD = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(CWD, "../"))
sys.path.append(os.path.join(CWD, "../lib/"))
# Required to instantiate the topology builder class.
# pylint: disable=C0413
# Import topogen and topotest helpers
from lib.topogen import Topogen, get_topogen
from lib.common_config import (
start_topology,
write_test_header,
write_test_footer,
reset_config_on_routers,
step,
shutdown_bringup_interface,
kill_router_daemons,
start_router_daemons,
create_static_routes,
check_router_status,
socat_send_igmp_join_traffic,
topo_daemons
)
from lib.pim import (
create_pim_config,
verify_igmp_groups,
verify_upstream_iif,
verify_join_state_and_timer,
verify_mroutes,
verify_pim_neighbors,
verify_pim_interface_traffic,
verify_pim_rp_info,
verify_pim_state,
clear_pim_interface_traffic,
clear_igmp_interfaces,
clear_pim_interfaces,
clear_mroute,
clear_mroute_verify,
)
from lib.topolog import logger
from lib.topojson import build_topo_from_json, build_config_from_json
# Global variables
GROUP_RANGE_V6 = "ff08::/64"
IGMP_JOIN_V6 = "ff08::1"
STAR = "*"
SOURCE = "Static"
pytestmark = [pytest.mark.pimd]
def build_topo(tgen):
"""Build function"""
# Building topology from json file
build_topo_from_json(tgen, TOPO)
def setup_module(mod):
"""
Sets up the pytest environment
* `mod`: module name
"""
testsuite_run_time = time.asctime(time.localtime(time.time()))
logger.info("Testsuite start time: %s", testsuite_run_time)
logger.info("=" * 40)
topology = """
_______r2_____
| |
iperf | | iperf
r0-----r1-------------r3-----r5
| |
|_____________|
r4
"""
logger.info("Master Topology: \n %s", topology)
logger.info("Running setup_module to create topology")
# This function initiates the topology build with Topogen...
json_file = "{}/multicast_pimv6_static_rp.json".format(CWD)
tgen = Topogen(json_file, mod.__name__)
global TOPO
TOPO = tgen.json_topo
# ... 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)
# Don"t run this test if we have any failure.
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
# Creating configuration from JSON
build_config_from_json(tgen, TOPO)
# Verify PIM neighbors
result = verify_pim_neighbors(tgen, TOPO)
assert result is True, "setup_module :Failed \n Error:" " {}".format(result)
logger.info("Running setup_module() done")
def teardown_module():
"""Teardown the pytest environment"""
logger.info("Running teardown_module to delete topology")
tgen = get_topogen()
# Stop toplogy and Remove tmp files
tgen.stop_topology()
logger.info("Testsuite end time: %s", time.asctime(time.localtime(time.time())))
logger.info("=" * 40)
#####################################################
#
# Testcases
#
#####################################################
def verify_state_incremented(state_before, state_after):
"""
API to compare interface traffic state incrementing
Parameters
----------
* `state_before` : State dictionary for any particular instance
* `state_after` : State dictionary for any particular instance
"""
for router, state_data in state_before.items():
for state, value in state_data.items():
if state_before[router][state] >= state_after[router][state]:
errormsg = (
"[DUT: %s]: state %s value has not"
" incremented, Initial value: %s, "
"Current value: %s [FAILED!!]"
% (
router,
state,
state_before[router][state],
state_after[router][state],
)
)
return errormsg
logger.info(
"[DUT: %s]: State %s value is "
"incremented, Initial value: %s, Current value: %s"
" [PASSED!!]",
router,
state,
state_before[router][state],
state_after[router][state],
)
return True
#####################################################
def test_pimv6_add_delete_static_RP_p0(request):
"""
TC_1: Verify upstream interfaces(IIF) and join state are updated
properly after adding and deleting the static RP
TC_2: Verify IIF and OIL in "show ip pim state" updated properly
after adding and deleting the static RP
TC_3: (*, G) Mroute entry are cleared when static RP gets deleted
TC_4: Verify (*,G) prune is send towards the RP after deleting the
static RP
TOPOlogy used:
r0------r1-----r2
iperf DUT RP
"""
tgen = get_topogen()
tc_name = request.node.name
write_test_header(tc_name)
# Don"t run this test if we have any failure.
if tgen.routers_have_failure():
check_router_status(tgen)
step("Shut link b/w R1 and R3")
intf = TOPO["routers"]["r1"]["links"]["r3"]["interface"]
shutdown_bringup_interface(tgen, "r1", intf, ifaceaction=False)
step("Enable PIM between r1 and r2")
step("Enable MLD on r1 interface and send IGMP " "join (FF08::1) to r1")
step("Configure r2 loopback interface as RP")
input_dict = {
"r2": {
"pim6": {
"rp": [
{
"rp_addr": TOPO["routers"]["r2"]["links"]["lo"]["ipv6"].split(
"/"
)[0],
"group_addr_range": GROUP_RANGE_V6,
}
]
}
}
}
result = create_pim_config(tgen, TOPO, input_dict)
assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
step("Verify show ip pim interface traffic without any mld join")
state_dict = {
"r1": {TOPO["routers"]["r1"]["links"]["r2"]["interface"]: ["pruneTx"]}
}
state_before = verify_pim_interface_traffic(tgen, state_dict, addr_type="ipv6")
assert isinstance(
state_before, dict
), "Testcase{} : Failed \n state_before is not dictionary \n " "Error: {}".format(
tc_name, result
)
step("send mld join (FF08::1) to R1")
intf = TOPO["routers"]["r0"]["links"]["r1"]["interface"]
intf_ip = TOPO["routers"]["r0"]["links"]["r1"]["ipv6"].split("/")[0]
result = socat_send_igmp_join_traffic(
tgen, "r0", "UDP6-RECV", IGMP_JOIN_V6, intf, intf_ip, join=True
)
assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
step("r1: Verify RP info")
dut = "r1"
oif = TOPO["routers"]["r1"]["links"]["r2"]["interface"]
iif = TOPO["routers"]["r1"]["links"]["r0"]["interface"]
rp_address = TOPO["routers"]["r2"]["links"]["lo"]["ipv6"].split("/")[0]
result = verify_pim_rp_info(
tgen, TOPO, dut, GROUP_RANGE_V6, oif, rp_address, SOURCE
)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step("r1: Verify upstream IIF interface")
result = verify_upstream_iif(tgen, dut, oif, STAR, IGMP_JOIN_V6)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step("r1: Verify upstream join state and join timer")
result = verify_join_state_and_timer(tgen, dut, oif, STAR, IGMP_JOIN_V6)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step("r1: Verify PIM state")
result = verify_pim_state(tgen, dut, oif, iif, IGMP_JOIN_V6)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step("r1: Verify ip mroutes")
result = verify_mroutes(tgen, dut, STAR, IGMP_JOIN_V6, oif, iif)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step("r1: Delete RP configuration")
input_dict = {
"r2": {
"pim6": {
"rp": [
{
"rp_addr": TOPO["routers"]["r2"]["links"]["lo"]["ipv6"].split(
"/"
)[0],
"group_addr_range": GROUP_RANGE_V6,
"delete": True,
}
]
}
}
}
result = create_pim_config(tgen, TOPO, input_dict)
assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
step("r1: Verify RP info")
result = verify_pim_rp_info(
tgen, TOPO, dut, GROUP_RANGE_V6, oif, rp_address, SOURCE, expected=False
)
assert (
result is not True
), "Testcase {} :Failed \n " "RP: {} info is still present \n Error: {}".format(
tc_name, rp_address, result
)
step("r1: Verify upstream IIF interface")
result = verify_upstream_iif(tgen, dut, oif, STAR, IGMP_JOIN_V6, expected=False)
assert result is not True, (
"Testcase {} :Failed \n "
"Upstream ({}, {}) is still in join state \n Error: {}".format(
tc_name, STAR, IGMP_JOIN_V6, result
)
)
step("r1: Verify upstream join state and join timer")
result = verify_join_state_and_timer(
tgen, dut, oif, STAR, IGMP_JOIN_V6, expected=False
)
assert result is not True, (
"Testcase {} :Failed \n "
"Upstream ({}, {}) timer is still running \n Error: {}".format(
tc_name, STAR, IGMP_JOIN_V6, result
)
)
step("r1: Verify PIM state")
result = verify_pim_state(tgen, dut, oif, iif, IGMP_JOIN_V6, expected=False)
assert result is not True, (
"Testcase {} :Failed \n "
"PIM state for group: {} is still Active \n Error: {}".format(
tc_name, IGMP_JOIN_V6, result
)
)
step("r1: Verify ip mroutes")
result = verify_mroutes(tgen, dut, STAR, IGMP_JOIN_V6, oif, iif, expected=False)
assert result is not True, (
"Testcase {} :Failed \n "
"mroute ({}, {}) is still present \n Error: {}".format(
tc_name, STAR, IGMP_JOIN_V6, result
)
)
step("r1: Verify show ip pim interface traffic without any IGMP join")
state_after = verify_pim_interface_traffic(tgen, state_dict, addr_type="ipv6")
assert isinstance(
state_after, dict
), "Testcase{} : Failed \n state_before is not dictionary \n " "Error: {}".format(
tc_name, result
)
result = verify_state_incremented(state_before, state_after)
assert result is True, "Testcase{} : Failed Error: {}".format(tc_name, result)
write_test_footer(tc_name)
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))