diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py index 38f7f3191d..a45b595a5b 100644 --- a/tests/topotests/lib/common_config.py +++ b/tests/topotests/lib/common_config.py @@ -31,7 +31,6 @@ from re import search as re_search from tempfile import mkdtemp import os -import io import sys import traceback import socket @@ -39,6 +38,7 @@ import ipaddress import platform if sys.version_info[0] > 2: + import io import configparser else: import StringIO @@ -257,6 +257,7 @@ def create_common_configuration( "bgp": "! BGP Config\n", "vrf": "! VRF Config\n", "ospf": "! OSPF Config\n", + "pim": "! PIM Config\n", } ) @@ -389,6 +390,8 @@ def check_router_status(tgen): daemons.append("bgpd") if "zebra" in result: daemons.append("zebra") + if "pimd" in result: + daemons.append("pimd") rnode.startDaemons(daemons) @@ -748,7 +751,14 @@ def start_topology(tgen, daemon=None): router.load_config( TopoRouter.RD_OSPF, "{}/{}/ospfd.conf".format(TMPDIR, rname) ) - # Starting routers + + if daemon and "pimd" in daemon: + # Loading empty pimd.conf file to router, to start the pim deamon + router.load_config( + TopoRouter.RD_PIM, "{}/{}/pimd.conf".format(TMPDIR, rname) + ) + + # Starting routers logger.info("Starting all routers once topology is created") tgen.start_router() @@ -835,9 +845,201 @@ def topo_daemons(tgen, topo): if "ospf" in topo["routers"][rtr] and "ospfd" not in daemon_list: daemon_list.append("ospfd") + for val in topo["routers"][rtr]["links"].values(): + if "pim" in val and "pimd" not in daemon_list: + daemon_list.append("pimd") + break + return daemon_list +def add_interfaces_to_vlan(tgen, input_dict): + """ + Add interfaces to VLAN, we need vlan pakcage to be installed on machine + + * `tgen`: tgen onject + * `input_dict` : interfaces to be added to vlans + + input_dict= { + "r1":{ + "vlan":{ + VLAN_1: [{ + intf_r1_s1: { + "ip": "10.1.1.1", + "subnet": "255.255.255.0 + } + }] + } + } + } + + add_interfaces_to_vlan(tgen, input_dict) + + """ + + router_list = tgen.routers() + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + if "vlan" in input_dict[dut]: + for vlan, interfaces in input_dict[dut]["vlan"].items(): + for intf_dict in interfaces: + for interface, data in intf_dict.items(): + # Adding interface to VLAN + cmd = "vconfig add {} {}".format(interface, vlan) + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + rnode.run(cmd) + + vlan_intf = "{}.{}".format(interface, vlan) + + ip = data["ip"] + subnet = data["subnet"] + + # Bringing interface up + cmd = "ip link set up {}".format(vlan_intf) + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + rnode.run(cmd) + + # Assigning IP address + cmd = "ifconfig {} {} netmask {}".format(vlan_intf, ip, subnet) + logger.info("[DUT: %s]: Running command: %s", dut, cmd) + rnode.run(cmd) + + +def tcpdump_capture_start( + tgen, + router, + intf, + protocol=None, + grepstr=None, + timeout=0, + options=None, + cap_file=None, + background=True, +): + """ + API to capture network packets using tcp dump. + + Packages used : + + Parameters + ---------- + * `tgen`: topogen object. + * `router`: router on which ping has to be performed. + * `intf` : interface for capture. + * `protocol` : protocol for which packet needs to be captured. + * `grepstr` : string to filter out tcp dump output. + * `timeout` : Time for which packet needs to be captured. + * `options` : options for TCP dump, all tcpdump options can be used. + * `cap_file` : filename to store capture dump. + * `background` : Make tcp dump run in back ground. + + Usage + ----- + tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20, + options='-A -vv -x > r2bgp.txt ') + Returns + ------- + 1) True for successful capture + 2) errormsg - when tcp dump fails + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[router] + + if timeout > 0: + cmd = "timeout {}".format(timeout) + else: + cmd = "" + + cmdargs = "{} tcpdump".format(cmd) + + if intf: + cmdargs += " -i {}".format(str(intf)) + if protocol: + cmdargs += " {}".format(str(protocol)) + if options: + cmdargs += " -s 0 {}".format(str(options)) + + if cap_file: + file_name = os.path.join(LOGDIR, tgen.modname, router, cap_file) + cmdargs += " -w {}".format(str(file_name)) + # Remove existing capture file + rnode.run("rm -rf {}".format(file_name)) + + if grepstr: + cmdargs += ' | grep "{}"'.format(str(grepstr)) + + logger.info("Running tcpdump command: [%s]", cmdargs) + if not background: + rnode.run(cmdargs) + else: + rnode.run("nohup {} & /dev/null 2>&1".format(cmdargs)) + + # Check if tcpdump process is running + if background: + result = rnode.run("pgrep tcpdump") + logger.debug("ps -ef | grep tcpdump \n {}".format(result)) + + if not result: + errormsg = "tcpdump is not running {}".format("tcpdump") + return errormsg + else: + logger.info("Packet capture started on %s: interface %s", router, intf) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def tcpdump_capture_stop(tgen, router): + """ + API to capture network packets using tcp dump. + + Packages used : + + Parameters + ---------- + * `tgen`: topogen object. + * `router`: router on which ping has to be performed. + * `intf` : interface for capture. + * `protocol` : protocol for which packet needs to be captured. + * `grepstr` : string to filter out tcp dump output. + * `timeout` : Time for which packet needs to be captured. + * `options` : options for TCP dump, all tcpdump options can be used. + * `cap2file` : filename to store capture dump. + * `bakgrnd` : Make tcp dump run in back ground. + + Usage + ----- + tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20, + options='-A -vv -x > r2bgp.txt ') + Returns + ------- + 1) True for successful capture + 2) errormsg - when tcp dump fails + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[router] + + # Check if tcpdump process is running + result = rnode.run("ps -ef | grep tcpdump") + logger.debug("ps -ef | grep tcpdump \n {}".format(result)) + + if not re_search(r"{}".format("tcpdump"), result): + errormsg = "tcpdump is not running {}".format("tcpdump") + return errormsg + else: + ppid = tgen.net.nameToNode[rnode.name].pid + rnode.run("set +m; pkill -P %s tcpdump &> /dev/null" % ppid) + logger.info("Stopped tcpdump capture") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + ############################################# # Common APIs, will be used by all protocols ############################################# @@ -1137,11 +1339,11 @@ def generate_ips(network, no_of_ips): """ Returns list of IPs. based on start_ip and no_of_ips + * `network` : from here the ip will start generating, start_ip will be * `no_of_ips` : these many IPs will be generated """ - ipaddress_list = [] if type(network) is not list: network = [network] @@ -1293,7 +1495,6 @@ def retry(attempts=3, wait=2, return_is_str=True, initial_wait=0, return_is_dict _wait = kwargs.pop("wait", wait) _attempts = kwargs.pop("attempts", attempts) _attempts = int(_attempts) - expected = True if _attempts < 0: raise ValueError("attempts must be 0 or greater") @@ -1303,12 +1504,10 @@ def retry(attempts=3, wait=2, return_is_str=True, initial_wait=0, return_is_dict _return_is_str = kwargs.pop("return_is_str", return_is_str) _return_is_dict = kwargs.pop("return_is_str", return_is_dict) + _expected = kwargs.setdefault("expected", True) + kwargs.pop("expected") for i in range(1, _attempts + 1): try: - _expected = kwargs.setdefault("expected", True) - if _expected is False: - expected = _expected - kwargs.pop("expected") ret = func(*args, **kwargs) logger.debug("Function returned %s", ret) if _return_is_str and isinstance(ret, bool) and _expected: @@ -1320,11 +1519,11 @@ def retry(attempts=3, wait=2, return_is_str=True, initial_wait=0, return_is_dict if _return_is_dict and isinstance(ret, dict): return ret - if _attempts == i and expected: + if _attempts == i: generate_support_bundle() return ret except Exception as err: - if _attempts == i and expected: + if _attempts == i: generate_support_bundle() logger.info("Max number of attempts (%r) reached", _attempts) raise @@ -1366,6 +1565,17 @@ def step(msg, reset=False): _step(msg, reset) +def do_countdown(secs): + """ + Countdown timer display + """ + for i in range(secs, 0, -1): + sys.stdout.write("{} ".format(str(i))) + sys.stdout.flush() + sleep(1) + return + + ############################################# # These APIs, will used by testcase ############################################# @@ -2855,11 +3065,7 @@ def verify_rib( errormsg = ( "[DUT: {}]: tag value {}" " is not matched for" - " route {} in RIB \n".format( - dut, - _tag, - st_rt, - ) + " route {} in RIB \n".format(dut, _tag, st_rt,) ) return errormsg @@ -2876,11 +3082,7 @@ def verify_rib( errormsg = ( "[DUT: {}]: metric value " "{} is not matched for " - "route {} in RIB \n".format( - dut, - metric, - st_rt, - ) + "route {} in RIB \n".format(dut, metric, st_rt,) ) return errormsg @@ -3919,3 +4121,215 @@ def required_linux_kernel_version(required_version): ) return error_msg return True + + +def iperfSendIGMPJoin( + tgen, server, bindToAddress, l4Type="UDP", join_interval=1, inc_step=0, repeat=0 +): + """ + Run iperf to send IGMP join and traffic + + Parameters: + ----------- + * `tgen` : Topogen object + * `l4Type`: string, one of [ TCP, UDP ] + * `server`: iperf server, from where IGMP join would be sent + * `bindToAddress`: bind to , an interface or multicast + address + * `join_interval`: seconds between periodic bandwidth reports + * `inc_step`: increamental steps, by default 0 + * `repeat`: Repetition of group, by default 0 + + returns: + -------- + errormsg or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[server] + + iperfArgs = "iperf -s " + + # UDP/TCP + if l4Type == "UDP": + iperfArgs += "-u " + + iperfCmd = iperfArgs + # Group address range to cover + if bindToAddress: + if type(bindToAddress) is not list: + Address = [] + start = ipaddress.IPv4Address(frr_unicode(bindToAddress)) + + Address = [start] + next_ip = start + + count = 1 + while count < repeat: + next_ip += inc_step + Address.append(next_ip) + count += 1 + bindToAddress = Address + + for bindTo in bindToAddress: + iperfArgs = iperfCmd + iperfArgs += "-B %s " % bindTo + + # Join interval + if join_interval: + iperfArgs += "-i %d " % join_interval + + iperfArgs += " &>/dev/null &" + # Run iperf command to send IGMP join + logger.debug("[DUT: {}]: Running command: [{}]".format(server, iperfArgs)) + output = rnode.run("set +m; {} sleep 0.5".format(iperfArgs)) + + # Check if iperf process is running + if output: + pid = output.split()[1] + rnode.run("touch /var/run/frr/iperf_server.pid") + rnode.run("echo %s >> /var/run/frr/iperf_server.pid" % pid) + else: + errormsg = "IGMP join is not sent for {}. Error: {}".format(bindTo, output) + logger.error(output) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def iperfSendTraffic( + tgen, + client, + bindToAddress, + ttl, + time=0, + l4Type="UDP", + inc_step=0, + repeat=0, + mappedAddress=None, +): + """ + Run iperf to send IGMP join and traffic + + Parameters: + ----------- + * `tgen` : Topogen object + * `l4Type`: string, one of [ TCP, UDP ] + * `client`: iperf client, from where iperf traffic would be sent + * `bindToAddress`: bind to , an interface or multicast + address + * `ttl`: time to live + * `time`: time in seconds to transmit for + * `inc_step`: increamental steps, by default 0 + * `repeat`: Repetition of group, by default 0 + * `mappedAddress`: Mapped Interface ip address + + returns: + -------- + errormsg or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[client] + + iperfArgs = "iperf -c " + + iperfCmd = iperfArgs + # Group address range to cover + if bindToAddress: + if type(bindToAddress) is not list: + Address = [] + start = ipaddress.IPv4Address(frr_unicode(bindToAddress)) + + Address = [start] + next_ip = start + + count = 1 + while count < repeat: + next_ip += inc_step + Address.append(next_ip) + count += 1 + bindToAddress = Address + + for bindTo in bindToAddress: + iperfArgs = iperfCmd + iperfArgs += "%s " % bindTo + + # Mapped Interface IP + if mappedAddress: + iperfArgs += "-B %s " % mappedAddress + + # UDP/TCP + if l4Type == "UDP": + iperfArgs += "-u -b 0.012m " + + # TTL + if ttl: + iperfArgs += "-T %d " % ttl + + # Time + if time: + iperfArgs += "-t %d " % time + + iperfArgs += " &>/dev/null &" + + # Run iperf command to send multicast traffic + logger.debug("[DUT: {}]: Running command: [{}]".format(client, iperfArgs)) + output = rnode.run("set +m; {} sleep 0.5".format(iperfArgs)) + + # Check if iperf process is running + if output: + pid = output.split()[1] + rnode.run("touch /var/run/frr/iperf_client.pid") + rnode.run("echo %s >> /var/run/frr/iperf_client.pid" % pid) + else: + errormsg = "Multicast traffic is not sent for {}. Error {}".format( + bindTo, output + ) + logger.error(output) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def kill_iperf(tgen, dut=None, action=None): + """ + Killing iperf process if running for any router in topology + Parameters: + ----------- + * `tgen` : Topogen object + * `dut` : Any iperf hostname to send igmp prune + * `action`: to kill igmp join iperf action is remove_join + to kill traffic iperf action is remove_traffic + + Usage + ---- + kill_iperf(tgen, dut ="i6", action="remove_join") + + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + router_list = tgen.routers() + for router, rnode in router_list.items(): + # Run iperf command to send IGMP join + pid_client = rnode.run("cat /var/run/frr/iperf_client.pid") + pid_server = rnode.run("cat /var/run/frr/iperf_server.pid") + if action == "remove_join": + pids = pid_server + elif action == "remove_traffic": + pids = pid_client + else: + pids = "\n".join([pid_client, pid_server]) + for pid in pids.split("\n"): + pid = pid.strip() + if pid.isdigit(): + cmd = "set +m; kill -9 %s &> /dev/null" % pid + logger.debug("[DUT: {}]: Running command: [{}]".format(router, cmd)) + rnode.run(cmd) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) diff --git a/tests/topotests/lib/pim.py b/tests/topotests/lib/pim.py new file mode 100644 index 0000000000..6bb1326519 --- /dev/null +++ b/tests/topotests/lib/pim.py @@ -0,0 +1,3389 @@ +# Copyright (c) 2019 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. + +import sys +import os +import re +import datetime +import traceback +import pytest +from time import sleep +from copy import deepcopy +from lib.topolog import logger + +# Import common_config to use commomnly used APIs +from lib.common_config import ( + create_common_configuration, + InvalidCLIError, + retry, + run_frr_cmd, +) + +#### +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 + + 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": { + "pim": { + "disable" : ["l1-i1-eth1"], + "rp": [{ + "rp_addr" : "1.0.3.17". + "keep-alive-timer": "100" + "group_addr_range": ["224.1.1.0/24", "225.1.1.0/24"] + "prefix-list": "pf_list_1" + "delete": True + }] + } + } + } + + + 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(): + result = _enable_disable_pim(tgen, topo, input_dict, router, build) + + if "pim" not in input_dict[router]: + logger.debug("Router %s: 'pim' is not present in " "input_dict", router) + continue + + if result is True: + if "rp" not in input_dict[router]["pim"]: + continue + + result = _create_pim_config( + tgen, topo, input_dict, router, build, load_config + ) + if result is not True: + return False + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def _create_pim_config(tgen, topo, input_dict, router, build=False, load_config=False): + """ + Helper API to create pim configuration. + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `input_dict` : Input dict data, required when configuring from testcase + * `router` : router id to be configured. + * `build` : Only for initial setup phase this is set as True. + + Returns + ------- + True or False + """ + + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + try: + + pim_data = input_dict[router]["pim"] + + for dut in tgen.routers(): + if "pim" not in input_dict[router]: + continue + + for destLink, data in topo[dut]["links"].items(): + if "pim" not in data: + continue + + if "rp" in pim_data: + config_data = [] + rp_data = pim_data["rp"] + + 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, + ) + + return False + rp_addr = rp_dict.setdefault("rp_addr", None) + + # Keep alive Timer + keep_alive_timer = rp_dict.setdefault("keep_alive_timer", None) + + # Group Address range to cover + if "group_addr_range" not in rp_dict and build: + logger.error( + "Router %s:'Group Address range to cover'" + " not present in input_dict/JSON", + router, + ) + + return False + group_addr_range = rp_dict.setdefault("group_addr_range", None) + + # Group prefix-list filter + prefix_list = rp_dict.setdefault("prefix_list", None) + + # Delete rp config + del_action = rp_dict.setdefault("delete", False) + + if keep_alive_timer: + cmd = "ip pim rp keep-alive-timer {}".format(keep_alive_timer) + config_data.append(cmd) + + if del_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + if rp_addr: + if group_addr_range: + if type(group_addr_range) is not list: + group_addr_range = [group_addr_range] + + for grp_addr in group_addr_range: + cmd = "ip pim rp {} {}".format(rp_addr, grp_addr) + config_data.append(cmd) + + 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 + ) + config_data.append(cmd) + + if del_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + result = create_common_configuration( + tgen, dut, config_data, "pim", build, load_config + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def create_igmp_config(tgen, topo, input_dict=None, build=False): + """ + API to configure igmp 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": { + "igmp": { + "interfaces": { + "r1-r0-eth0" :{ + "igmp":{ + "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 "igmp" not in input_dict[router]: + logger.debug("Router %s: 'igmp' is not present in " "input_dict", router) + continue + + igmp_data = input_dict[router]["igmp"] + + if "interfaces" in igmp_data: + config_data = [] + intf_data = igmp_data["interfaces"] + + for intf_name in intf_data.keys(): + cmd = "interface {}".format(intf_name) + config_data.append(cmd) + protocol = "igmp" + del_action = intf_data[intf_name]["igmp"].setdefault("delete", False) + cmd = "ip igmp" + if del_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + del_attr = intf_data[intf_name]["igmp"].setdefault("delete_attr", False) + for attribute, data in intf_data[intf_name]["igmp"].items(): + if attribute == "version": + cmd = "ip {} {} {}".format(protocol, attribute, data) + if del_action: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + if attribute == "join": + for group in data: + cmd = "ip {} {} {}".format(protocol, attribute, group) + if del_attr: + cmd = "no {}".format(cmd) + config_data.append(cmd) + + if attribute == "query": + for query, value in data.items(): + if query != "delete": + cmd = "ip {} {} {}".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(tgen, topo, input_dict, router, build=False): + """ + Helper API to enable or disable pim on interfaces + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `input_dict` : Input dict data, required when configuring from testcase + * `router` : router id to be configured. + * `build` : Only for initial setup phase this is set as True. + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + try: + config_data = [] + + enable_flag = True + # Disable pim on interface + if "pim" in input_dict[router]: + if "disable" in input_dict[router]["pim"]: + enable_flag = False + interfaces = input_dict[router]["pim"]["disable"] + + if type(interfaces) is not list: + interfaces = [interfaces] + + for interface in interfaces: + cmd = "interface {}".format(interface) + config_data.append(cmd) + config_data.append("no ip pim") + + # Enable pim on interface + if enable_flag: + for destRouterLink, data in sorted(topo[router]["links"].items()): + if "pim" in data and data["pim"] == "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("ip pim") + + result = create_common_configuration( + tgen, router, config_data, "interface_config", build=build + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def add_rp_interfaces_and_pim_config(tgen, topo, interface, rp, rp_mapping): + """ + Add physical interfaces tp RP for all the RPs + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `interface` : RP interface + * `rp` : rp for given topology + * `rp_mapping` : dictionary of all groups and RPs + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + + for group, rp_list in rp_mapping.items(): + for _rp in rp_list: + config_data.append("interface {}".format(interface)) + config_data.append("ip address {}".format(_rp)) + config_data.append("ip pim") + + result = create_common_configuration( + tgen, rp, config_data, "interface_config" + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def find_rp_details(tgen, topo): + """ + Find who is RP in topology and returns list of RPs + + Parameters: + ----------- + * `tgen` : Topogen object + * `topo` : json file data + + returns: + -------- + errormsg or True + """ + + rp_details = {} + + router_list = tgen.routers() + topo_data = topo["routers"] + + for router in router_list.keys(): + + if "pim" not in topo_data[router]: + continue + + pim_data = topo_data[router]["pim"] + if "rp" in pim_data: + rp_data = pim_data["rp"] + for rp_dict in rp_data: + # ip address of RP + rp_addr = rp_dict["rp_addr"] + + for link, data in topo["routers"][router]["links"].items(): + if data["ipv4"].split("/")[0] == rp_addr: + rp_details[router] = rp_addr + + return rp_details + + +def configure_pim_force_expire(tgen, topo, input_dict, build=False): + """ + Helper API to create pim configuration. + + 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 ={ + "l1": { + "pim": { + "force_expire":{ + "10.0.10.1": ["255.1.1.1"] + } + } + } + } + + result = create_pim_config(tgen, topo, input_dict) + + Returns + ------- + True or False + """ + + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + try: + + for dut in input_dict.keys(): + if "pim" not in input_dict[dut]: + continue + + pim_data = input_dict[dut]["pim"] + + if "force_expire" in pim_data: + config_data = [] + force_expire_data = pim_data["force_expire"] + + for source, groups in force_expire_data.items(): + if type(groups) is not list: + groups = [groups] + + for group in groups: + cmd = "ip pim force-expire source {} group {}".format( + source, group + ) + config_data.append(cmd) + + result = create_common_configuration( + tgen, dut, config_data, "pim", build=build + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +############################################# +# Verification APIs +############################################# +def verify_pim_neighbors(tgen, topo, dut=None, iface=None): + """ + Verify all PIM neighbors are up and running, config is verified + using "show ip pim neighbor" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : dut info + * `iface` : link for which PIM nbr need to check + + Usage + ----- + result = verify_pim_neighbors(tgen, topo, dut, link) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for router in tgen.routers(): + if dut is not None and dut != router: + continue + + rnode = tgen.routers()[router] + show_ip_pim_neighbor_json = rnode.vtysh_cmd( + "show ip pim neighbor json", isjson=True + ) + + for destLink, data in topo["routers"][router]["links"].items(): + if iface is not None and iface != data["interface"]: + continue + + if "type" in data and data["type"] == "loopback": + continue + + if "pim" not in data: + continue + + if "pim" in data and data["pim"] == "enable": + local_interface = data["interface"] + + if "-" in destLink: + # Spliting and storing destRouterLink data in tempList + tempList = destLink.split("-") + + # destRouter + destLink = tempList.pop(0) + + # Current Router Link + tempList.insert(0, router) + curRouter = "-".join(tempList) + else: + curRouter = router + if destLink not in topo["routers"]: + continue + data = topo["routers"][destLink]["links"][curRouter] + if "type" in data and data["type"] == "loopback": + continue + + if "pim" not in data: + continue + + logger.info("[DUT: %s]: Verifying PIM neighbor status:", router) + + if "pim" in data and data["pim"] == "enable": + pim_nh_intf_ip = data["ipv4"].split("/")[0] + + # Verifying PIM neighbor + if local_interface in show_ip_pim_neighbor_json: + if show_ip_pim_neighbor_json[local_interface]: + if ( + show_ip_pim_neighbor_json[local_interface][pim_nh_intf_ip][ + "neighbor" + ] + != pim_nh_intf_ip + ): + errormsg = ( + "[DUT %s]: Local interface: %s, PIM" + " neighbor check failed " + "Expected neighbor: %s, Found neighbor:" + " %s" + % ( + router, + local_interface, + pim_nh_intf_ip, + show_ip_pim_neighbor_json[local_interface][ + pim_nh_intf_ip + ]["neighbor"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: Local interface: %s, Found" + " expected PIM neighbor %s", + router, + local_interface, + pim_nh_intf_ip, + ) + else: + errormsg = ( + "[DUT %s]: Local interface: %s, and" + "interface ip: %s is not found in " + "PIM neighbor " % (router, local_interface, pim_nh_intf_ip) + ) + return errormsg + else: + errormsg = ( + "[DUT %s]: Local interface: %s, is not " + "present in PIM neighbor " % (router, local_interface) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=21, wait=2, return_is_str=True) +def verify_igmp_groups(tgen, dut, interface, group_addresses): + """ + Verify IGMP groups are received from an intended interface + by running "show ip igmp groups" command + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `interface`: interface, from which IGMP groups would be received + * `group_addresses`: IGMP group address + + Usage + ----- + dut = "r1" + interface = "r1-r0-eth0" + group_address = "225.1.1.1" + result = verify_igmp_groups(tgen, dut, interface, group_address) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying IGMP groups received:", dut) + show_ip_igmp_json = run_frr_cmd(rnode, "show ip igmp groups json", isjson=True) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + if interface in show_ip_igmp_json: + show_ip_igmp_json = show_ip_igmp_json[interface]["groups"] + else: + errormsg = ( + "[DUT %s]: Verifying IGMP group received" + " from interface %s [FAILED]!! " % (dut, interface) + ) + return errormsg + + found = False + for grp_addr in group_addresses: + for index in show_ip_igmp_json: + if index["group"] == grp_addr: + found = True + break + if found is not True: + errormsg = ( + "[DUT %s]: Verifying IGMP group received" + " from interface %s [FAILED]!! " + " Expected not found: %s" % (dut, interface, grp_addr) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying IGMP group %s received " + "from interface %s [PASSED]!! ", + dut, + grp_addr, + interface, + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_upstream_iif( + tgen, dut, iif, src_address, group_addresses, joinState=None, refCount=1 +): + """ + Verify upstream inbound interface is updated correctly + by running "show ip pim upstream" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `iif`: inbound interface + * `src_address`: source address + * `group_addresses`: IGMP group address + * `joinState`: upstream join state + * `refCount`: refCount value + + Usage + ----- + dut = "r1" + iif = "r1-r0-eth0" + src_address = "*" + group_address = "225.1.1.1" + result = verify_upstream_iif(tgen, dut, iif, src_address, group_address, + state, refCount) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info( + "[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] + + if type(iif) is not list: + iif = [iif] + + for grp_addr in group_addresses: + # Verify group address + if grp_addr not in show_ip_pim_upstream_json: + errormsg = "[DUT %s]: Verifying upstream" " for group %s [FAILED]!!" % ( + dut, + grp_addr, + ) + return errormsg + group_addr_json = show_ip_pim_upstream_json[grp_addr] + + # Verify source address + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying upstream" " for (%s,%s) [FAILED]!!" % ( + dut, + src_address, + grp_addr, + ) + return errormsg + + # Verify Inbound Interface + found = False + for in_interface in iif: + if group_addr_json[src_address]["inboundInterface"] == in_interface: + if refCount > 0: + logger.info( + "[DUT %s]: Verifying refCount " + "for (%s,%s) [PASSED]!! " + " Found Expected: %s", + dut, + src_address, + grp_addr, + group_addr_json[src_address]["refCount"], + ) + found = True + if found: + if joinState is None: + if group_addr_json[src_address]["joinState"] != "Joined": + errormsg = ( + "[DUT %s]: Verifying iif " + "(Inbound Interface) for (%s,%s) and" + " joinState :%s [FAILED]!! " + " Expected: %s, Found: %s" + % ( + dut, + src_address, + grp_addr, + group_addr_json[src_address]["joinState"], + in_interface, + group_addr_json[src_address]["inboundInterface"], + ) + ) + return errormsg + + elif group_addr_json[src_address]["joinState"] != joinState: + errormsg = ( + "[DUT %s]: Verifying iif " + "(Inbound Interface) for (%s,%s) and" + " joinState :%s [FAILED]!! " + " Expected: %s, Found: %s" + % ( + dut, + src_address, + grp_addr, + group_addr_json[src_address]["joinState"], + in_interface, + group_addr_json[src_address]["inboundInterface"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying iif(Inbound Interface)" + " for (%s,%s) and joinState is %s [PASSED]!! " + " Found Expected: (%s)", + dut, + src_address, + grp_addr, + group_addr_json[src_address]["joinState"], + group_addr_json[src_address]["inboundInterface"], + ) + if not found: + errormsg = ( + "[DUT %s]: Verifying iif " + "(Inbound Interface) for (%s, %s) " + "[FAILED]!! " + " Expected: %s, Found: %s" + % ( + dut, + src_address, + grp_addr, + in_interface, + group_addr_json[src_address]["inboundInterface"], + ) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=6, wait=2, return_is_str=True) +def verify_join_state_and_timer(tgen, dut, iif, src_address, group_addresses): + """ + Verify join state is updated correctly and join timer is + running with the help of "show ip pim upstream" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `iif`: inbound interface + * `src_address`: source address + * `group_addresses`: IGMP group address + + Usage + ----- + dut = "r1" + iif = "r1-r0-eth0" + group_address = "225.1.1.1" + result = verify_join_state_and_timer(tgen, dut, iif, group_address) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + errormsg = "" + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info( + "[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_addr in group_addresses: + # Verify group address + if grp_addr not in show_ip_pim_upstream_json: + errormsg = "[DUT %s]: Verifying upstream" " for group %s [FAILED]!!" % ( + dut, + grp_addr, + ) + return errormsg + + group_addr_json = show_ip_pim_upstream_json[grp_addr] + + # Verify source address + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying upstream" " for (%s,%s) [FAILED]!!" % ( + dut, + src_address, + grp_addr, + ) + return errormsg + + # Verify join state + joinState = group_addr_json[src_address]["joinState"] + if joinState != "Joined": + error = ( + "[DUT %s]: Verifying join state for" + " (%s,%s) [FAILED]!! " + " Expected: %s, Found: %s" + % (dut, src_address, grp_addr, "Joined", joinState) + ) + errormsg = errormsg + "\n" + str(error) + else: + logger.info( + "[DUT %s]: Verifying join state for" + " (%s,%s) [PASSED]!! " + " Found Expected: %s", + dut, + src_address, + grp_addr, + joinState, + ) + + # Verify join timer + joinTimer = group_addr_json[src_address]["joinTimer"] + if not re.match(r"(\d{2}):(\d{2}):(\d{2})", joinTimer): + error = ( + "[DUT %s]: Verifying join timer for" + " (%s,%s) [FAILED]!! " + " Expected: %s, Found: %s", + dut, + src_address, + grp_addr, + "join timer should be running", + joinTimer, + ) + errormsg = errormsg + "\n" + str(error) + else: + logger.info( + "[DUT %s]: Verifying join timer is running" + " for (%s,%s) [PASSED]!! " + " Found Expected: %s", + dut, + src_address, + grp_addr, + joinTimer, + ) + + if errormsg != "": + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=41, wait=2, return_is_dict=True) +def verify_ip_mroutes( + tgen, dut, src_address, group_addresses, iif, oil, return_uptime=False, mwait=0 +): + """ + Verify ip mroutes and make sure (*, G)/(S, G) is present in mroutes + by running "show ip pim upstream" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `src_address`: source address + * `group_addresses`: IGMP group address + * `iif`: Incoming interface + * `oil`: Outgoing interface + * `return_uptime`: If True, return uptime dict, default is False + * `mwait`: Wait time, default is 0 + + + Usage + ----- + dut = "r1" + group_address = "225.1.1.1" + result = verify_ip_mroutes(tgen, dut, src_address, group_address) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + 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] + + if not isinstance(iif, list) and iif is not "none": + iif = [iif] + + if not isinstance(oil, list) and oil is not "none": + oil = [oil] + + for grp_addr in group_addresses: + if grp_addr not in show_ip_mroute_json: + errormsg = "[DUT %s]: Verifying (%s, %s) mroute," "[FAILED]!! " % ( + dut, + src_address, + grp_addr, + ) + return errormsg + else: + if return_uptime: + uptime_dict[grp_addr] = {} + + group_addr_json = show_ip_mroute_json[grp_addr] + + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying (%s, %s) mroute," "[FAILED]!! " % ( + dut, + src_address, + grp_addr, + ) + return errormsg + else: + if return_uptime: + uptime_dict[grp_addr][src_address] = {} + + mroutes = group_addr_json[src_address] + + if mroutes["installed"] != 0: + logger.info( + "[DUT %s]: mroute (%s,%s) is installed", dut, src_address, grp_addr + ) + + if "oil" not in mroutes: + if oil == "none" and mroutes["iif"] in iif: + logger.info( + "[DUT %s]: Verifying (%s, %s) mroute," + " [PASSED]!! Found Expected: " + "(iif: %s, oil: %s, installed: (%s,%s))", + dut, + src_address, + grp_addr, + mroutes["iif"], + oil, + src_address, + grp_addr, + ) + else: + errormsg = ( + "[DUT %s]: Verifying (%s, %s) mroute," + " [FAILED]!! " + "Expected: (oil: %s, installed:" + " (%s,%s)) Found: ( oil: none, " + "installed: (%s,%s))" + % ( + dut, + src_address, + grp_addr, + oil, + src_address, + grp_addr, + src_address, + grp_addr, + ) + ) + + return errormsg + + else: + found = False + for route, data in mroutes["oil"].items(): + if route in oil and route != "pimreg": + if ( + data["source"] == src_address + and data["group"] == grp_addr + and data["inboundInterface"] in iif + and data["outboundInterface"] in oil + ): + if return_uptime: + + uptime_dict[grp_addr][src_address] = data["upTime"] + + logger.info( + "[DUT %s]: Verifying (%s, %s)" + " mroute, [PASSED]!! " + "Found Expected: " + "(iif: %s, oil: %s, installed:" + " (%s,%s)", + dut, + src_address, + grp_addr, + data["inboundInterface"], + data["outboundInterface"], + data["source"], + data["group"], + ) + found = True + break + else: + continue + + if not found: + errormsg = ( + "[DUT %s]: Verifying (%s, %s)" + " mroute [FAILED]!! " + "Expected in: (iif: %s, oil: %s," + " installed: (%s,%s)) Found: " + "(iif: %s, oil: %s, " + "installed: (%s,%s))" + % ( + dut, + src_address, + grp_addr, + iif, + oil, + src_address, + grp_addr, + data["inboundInterface"], + data["outboundInterface"], + data["source"], + data["group"], + ) + ) + return errormsg + + else: + errormsg = "[DUT %s]: mroute (%s,%s) is not installed" % ( + dut, + src_address, + grp_addr, + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True if return_uptime == False else uptime_dict + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_rp_info( + tgen, topo, dut, group_addresses, oif=None, rp=None, source=None, iamrp=None +): + """ + Verify pim rp info by running "show ip pim rp-info" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo`: JSON file handler + * `dut`: device under test + * `group_addresses`: IGMP group address + * `oif`: outbound interface name + * `rp`: RP address + * `source`: Source of RP + * `iamrp`: User defined RP + + Usage + ----- + dut = "r1" + result = verify_pim_rp_info(tgen, topo, dut, group_address, + rp=rp, source="BSR") + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + 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_addr in group_addresses: + if rp is None: + rp_details = find_rp_details(tgen, topo) + + if dut in rp_details: + iamRP = True + else: + iamRP = False + else: + show_ip_route_json = run_frr_cmd( + rnode, "show ip route connected json", isjson=True + ) + for _rp in show_ip_route_json.keys(): + if rp == _rp.split("/")[0]: + iamRP = True + break + else: + iamRP = False + + if rp not in show_ip_rp_info_json: + 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 oif is not None: + found = False + if rp_json["outboundInterface"] not in oif: + errormsg = ( + "[DUT %s]: Verifying OIF " + "for group %s and RP %s [FAILED]!! " + "Expected interfaces: (%s)," + " Found: (%s)" + % (dut, grp_addr, rp, oif, rp_json["outboundInterface"]) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying OIF " + "for group %s and RP %s [PASSED]!! " + "Found Expected: (%s)" + % (dut, grp_addr, rp, rp_json["outboundInterface"]) + ) + + if source is not None: + if rp_json["source"] != source: + errormsg = ( + "[DUT %s]: Verifying SOURCE " + "for group %s and RP %s [FAILED]!! " + "Expected: (%s)," + " Found: (%s)" % (dut, grp_addr, rp, source, rp_json["source"]) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying SOURCE " + "for group %s and RP %s [PASSED]!! " + "Found Expected: (%s)" % (dut, grp_addr, rp, rp_json["source"]) + ) + + if rp_json["group"] == grp_addr and iamrp is not None: + if iamRP: + if rp_json["iAmRP"]: + logger.info( + "[DUT %s]: Verifying group " + "and iAmRP [PASSED]!!" + " Found Expected: (%s, %s:%s)", + dut, + grp_addr, + "iAmRP", + rp_json["iAmRP"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying group" + "%s and iAmRP [FAILED]!! " + "Expected: (iAmRP: %s)," + " Found: (iAmRP: %s)" + % (dut, grp_addr, "true", rp_json["iAmRP"]) + ) + return errormsg + + if not iamRP: + if rp_json["iAmRP"] == False: + logger.info( + "[DUT %s]: Verifying group " + "and iAmNotRP [PASSED]!!" + " Found Expected: (%s, %s:%s)", + dut, + grp_addr, + "iAmRP", + rp_json["iAmRP"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying group" + "%s and iAmRP [FAILED]!! " + "Expected: (iAmRP: %s)," + " Found: (iAmRP: %s)" + % (dut, grp_addr, "false", rp_json["iAmRP"]) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_state( + tgen, dut, iif, oil, group_addresses, src_address=None, installed_fl=None +): + """ + Verify pim state by running "show ip pim state" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `iif`: inbound interface + * `oil`: outbound interface + * `group_addresses`: IGMP group address + * `src_address`: source address, default = None + * installed_fl` : Installed flag + + Usage + ----- + dut = "r1" + iif = "r1-r3-eth1" + oil = "r1-r0-eth0" + group_address = "225.1.1.1" + result = verify_pim_state(tgen, dut, iif, oil, group_address) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + 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_addr in group_addresses: + if src_address is None: + src_address = "*" + pim_state_json = show_pim_state_json[grp_addr][src_address] + else: + pim_state_json = show_pim_state_json[grp_addr][src_address] + + if pim_state_json["Installed"] == installed_fl: + logger.info( + "[DUT %s]: group %s is installed flag: %s", + dut, + grp_addr, + pim_state_json["Installed"], + ) + for interface, data in pim_state_json[iif].items(): + if interface != oil: + continue + + # Verify iif, oil and installed state + if ( + data["group"] == grp_addr + and data["installed"] == installed_fl + and data["inboundInterface"] == iif + and data["outboundInterface"] == oil + ): + logger.info( + "[DUT %s]: Verifying pim state for group" + " %s [PASSED]!! Found Expected: " + "(iif: %s, oil: %s, installed: %s) ", + dut, + grp_addr, + data["inboundInterface"], + data["outboundInterface"], + data["installed"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying pim state for group" + " %s, [FAILED]!! Expected: " + "(iif: %s, oil: %s, installed: %s) ", + "Found: (iif: %s, oil: %s, installed: %s)" + % ( + dut, + grp_addr, + iif, + oil, + "1", + data["inboundInterface"], + data["outboundInterface"], + data["installed"], + ), + ) + return errormsg + else: + errormsg = "[DUT %s]: %s install flag value not as expected" % ( + dut, + grp_addr, + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def verify_pim_interface_traffic(tgen, input_dict): + """ + Verify ip pim interface traffice by running + "show ip pim interface traffic" cli + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict(dict)`: defines DUT, what and from which interfaces + traffic needs to be verified + Usage + ----- + input_dict = { + "r1": { + "r1-r0-eth0": { + "helloRx": 0, + "helloTx": 1, + "joinRx": 0, + "joinTx": 0 + } + } + } + + result = verify_pim_interface_traffic(tgen, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + output_dict = {} + for dut in input_dict.keys(): + if dut not in tgen.routers(): + continue + + 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 + ) + + output_dict[dut] = {} + for intf, data in input_dict[dut].items(): + interface_json = show_pim_intf_traffic_json[intf] + for state in data: + + # Verify Tx/Rx + if state in interface_json: + output_dict[dut][state] = interface_json[state] + else: + errormsg = ( + "[DUT %s]: %s is not present" + "for interface %s [FAILED]!! " % (dut, state, intf) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return output_dict + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_interface(tgen, topo, dut): + """ + Verify all PIM interface are up and running, config is verified + using "show ip pim interface" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : device under test + + Usage + ----- + result = verify_pim_interfacetgen, topo, dut) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for router in tgen.routers(): + if router != dut: + continue + + logger.info("[DUT: %s]: Verifying PIM interface status:", dut) + + rnode = tgen.routers()[dut] + show_ip_pim_interface_json = run_frr_cmd( + rnode, "show ip pim interface json", isjson=True + ) + + for destLink, data in topo["routers"][dut]["links"].items(): + if "type" in data and data["type"] == "loopback": + continue + + if "pim" in data and data["pim"] == "enable": + pim_interface = data["interface"] + pim_intf_ip = data["ipv4"].split("/")[0] + + if pim_interface in show_ip_pim_interface_json: + pim_intf_json = show_ip_pim_interface_json[pim_interface] + + # Verifying PIM interface + if ( + pim_intf_json["address"] != pim_intf_ip + and pim_intf_json["state"] != "up" + ): + errormsg = ( + "[DUT %s]: PIM interface: %s " + "PIM interface ip: %s, status check " + "[FAILED]!! Expected : %s, Found : %s" + % ( + dut, + pim_interface, + pim_intf_ip, + pim_interface, + pim_intf_json["state"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: PIM interface: %s, " + "interface ip: %s, status: %s" + " [PASSED]!!", + dut, + pim_interface, + pim_intf_ip, + pim_intf_json["state"], + ) + else: + errormsg = ( + "[DUT %s]: PIM interface: %s " + "PIM interface ip: %s, is not present " + % (dut, pim_interface, pim_intf_ip,) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def clear_ip_pim_interface_traffic(tgen, topo): + """ + Clear ip pim interface traffice by running + "clear ip pim interface traffic" cli + + Parameters + ---------- + * `tgen`: topogen object + Usage + ----- + + result = clear_ip_pim_interface_traffic(tgen, topo) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for dut in tgen.routers(): + if "pim" not in topo["routers"][dut]: + continue + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Clearing pim interface traffic", dut) + result = run_frr_cmd(rnode, "clear ip pim interface traffic") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +def clear_ip_pim_interfaces(tgen, dut): + """ + Clear ip pim interface by running + "clear ip pim interfaces" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: Device Under Test + Usage + ----- + + result = clear_ip_pim_interfaces(tgen, dut) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + nh_before_clear = {} + nh_after_clear = {} + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verify pim neighbor before pim" " neighbor clear", dut) + # To add uptime initially + sleep(10) + run_json_before = run_frr_cmd(rnode, "show ip pim neighbor json", isjson=True) + + for key, value in run_json_before.items(): + if bool(value): + for _key, _value in value.items(): + nh_before_clear[key] = _value["upTime"] + + # Clearing PIM neighbors + logger.info("[DUT: %s]: Clearing pim interfaces", dut) + run_frr_cmd(rnode, "clear ip pim interfaces") + + logger.info("[DUT: %s]: Verify pim neighbor after pim" " neighbor clear", dut) + + found = False + + # Waiting for maximum 60 sec + fail_intf = [] + for retry in range(1, 13): + logger.info("[DUT: %s]: Waiting for 5 sec for PIM neighbors" " to come up", dut) + sleep(5) + run_json_after = run_frr_cmd(rnode, "show ip pim neighbor json", isjson=True) + found = True + for pim_intf in nh_before_clear.keys(): + if pim_intf not in run_json_after or not run_json_after[pim_intf]: + found = False + fail_intf.append(pim_intf) + + if found is True: + break + else: + errormsg = ( + "[DUT: %s]: pim neighborship is not formed for %s" + "after clear_ip_pim_interfaces %s [FAILED!!]", + dut, + fail_intf, + ) + return errormsg + + for key, value in run_json_after.items(): + if bool(value): + for _key, _value in value.items(): + nh_after_clear[key] = _value["upTime"] + + # Verify uptime for neighbors + for pim_intf in nh_before_clear.keys(): + d1 = datetime.datetime.strptime(nh_before_clear[pim_intf], "%H:%M:%S") + d2 = datetime.datetime.strptime(nh_after_clear[pim_intf], "%H:%M:%S") + if d2 >= d1: + errormsg = ( + "[DUT: %s]: PIM neighborship is not cleared for", + " interface %s [FAILED!!]", + dut, + pim_intf, + ) + + logger.info("[DUT: %s]: PIM neighborship is cleared [PASSED!!]") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +def clear_ip_igmp_interfaces(tgen, dut): + """ + Clear ip igmp interfaces by running + "clear ip igmp interfaces" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + + Usage + ----- + dut = "r1" + result = clear_ip_igmp_interfaces(tgen, dut) + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + group_before_clear = {} + group_after_clear = {} + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: IGMP group uptime before clear" " igmp groups:", dut) + igmp_json = run_frr_cmd(rnode, "show ip igmp groups json", isjson=True) + + total_groups_before_clear = igmp_json["totalGroups"] + + for key, value in igmp_json.items(): + if type(value) is not dict: + continue + + groups = value["groups"] + group = groups[0]["group"] + uptime = groups[0]["uptime"] + group_before_clear[group] = uptime + + logger.info("[DUT: %s]: Clearing ip igmp interfaces", dut) + result = run_frr_cmd(rnode, "clear ip igmp interfaces") + + # Waiting for maximum 60 sec + for retry in range(1, 13): + logger.info( + "[DUT: %s]: Waiting for 5 sec for igmp interfaces" " to come up", dut + ) + sleep(5) + igmp_json = run_frr_cmd(rnode, "show ip igmp groups json", isjson=True) + + total_groups_after_clear = igmp_json["totalGroups"] + + if total_groups_before_clear == total_groups_after_clear: + break + + for key, value in igmp_json.items(): + if type(value) is not dict: + continue + + groups = value["groups"] + group = groups[0]["group"] + uptime = groups[0]["uptime"] + group_after_clear[group] = uptime + + # Verify uptime for groups + for group in group_before_clear.keys(): + d1 = datetime.datetime.strptime(group_before_clear[group], "%H:%M:%S") + d2 = datetime.datetime.strptime(group_after_clear[group], "%H:%M:%S") + if d2 >= d1: + errormsg = ("[DUT: %s]: IGMP group is not cleared", " [FAILED!!]", dut) + + logger.info("[DUT: %s]: IGMP group is cleared [PASSED!!]") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +@retry(attempts=10, wait=2, return_is_str=True) +def clear_ip_mroute_verify(tgen, dut): + """ + Clear ip mroute by running "clear ip mroute" cli and verify + mroutes are up again after mroute clear + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: Device Under Test + Usage + ----- + + result = clear_ip_mroute_verify(tgen, dut) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + mroute_before_clear = {} + mroute_after_clear = {} + + rnode = tgen.routers()[dut] + + # sleep(60) + logger.info("[DUT: %s]: IP mroutes uptime before clear", dut) + mroute_json_1 = run_frr_cmd(rnode, "show ip mroute json", isjson=True) + + for group in mroute_json_1.keys(): + mroute_before_clear[group] = {} + for key in mroute_json_1[group].keys(): + for _key, _value in mroute_json_1[group][key]["oil"].items(): + if _key != "pimreg": + mroute_before_clear[group][key] = _value["upTime"] + + logger.info("[DUT: %s]: Clearing ip mroute", dut) + result = run_frr_cmd(rnode, "clear ip mroute") + + # RFC 3376: 8.2. Query Interval - Default: 125 seconds + # So waiting for maximum 130 sec to get the igmp report + for retry in range(1, 26): + logger.info("[DUT: %s]: Waiting for 2 sec for mroutes" " to come up", dut) + sleep(5) + keys_json1 = mroute_json_1.keys() + mroute_json_2 = run_frr_cmd(rnode, "show ip mroute json", isjson=True) + + if bool(mroute_json_2): + keys_json2 = mroute_json_2.keys() + + for group in mroute_json_2.keys(): + flag = False + for key in mroute_json_2[group].keys(): + if "oil" not in mroute_json_2[group]: + continue + + for _key, _value in mroute_json_2[group][key]["oil"].items(): + if _key != "pimreg" and keys_json1 == keys_json2: + break + flag = True + if flag: + break + else: + continue + + for group in mroute_json_2.keys(): + mroute_after_clear[group] = {} + for key in mroute_json_2[group].keys(): + for _key, _value in mroute_json_2[group][key]["oil"].items(): + if _key != "pimreg": + mroute_after_clear[group][key] = _value["upTime"] + + # Verify uptime for mroute + for group in mroute_before_clear.keys(): + for source in mroute_before_clear[group].keys(): + if set(mroute_before_clear[group]) != set(mroute_after_clear[group]): + errormsg = ( + "[DUT: %s]: mroute (%s, %s) has not come" + " up after mroute clear [FAILED!!]" % (dut, source, group) + ) + return errormsg + + d1 = datetime.datetime.strptime( + mroute_before_clear[group][source], "%H:%M:%S" + ) + d2 = datetime.datetime.strptime( + mroute_after_clear[group][source], "%H:%M:%S" + ) + if d2 >= d1: + errormsg = "[DUT: %s]: IP mroute is not cleared" " [FAILED!!]" % (dut) + + logger.info("[DUT: %s]: IP mroute is cleared [PASSED!!]", dut) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return True + + +def clear_ip_mroute(tgen, dut=None): + """ + Clear ip mroute by running "clear ip mroute" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test, default None + + Usage + ----- + clear_ip_mroute(tgen, dut) + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + router_list = tgen.routers() + for router, rnode in router_list.items(): + if dut is not None and router != dut: + continue + + logger.debug("[DUT: %s]: Clearing ip mroute", router) + rnode.vtysh_cmd("clear ip mroute") + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + +def reconfig_interfaces(tgen, topo, senderRouter, receiverRouter, packet=None): + """ + Configure interface ip for sender and receiver routers + as per bsr packet + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `senderRouter` : Sender router + * `receiverRouter` : Receiver router + * `packet` : BSR packet in raw format + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + + src_ip = topo["routers"][senderRouter]["bsm"]["bsr_packets"][packet]["src_ip"] + dest_ip = topo["routers"][senderRouter]["bsm"]["bsr_packets"][packet]["dest_ip"] + + for destLink, data in topo["routers"][senderRouter]["links"].items(): + if "type" in data and data["type"] == "loopback": + continue + + if "pim" in data and data["pim"] == "enable": + sender_interface = data["interface"] + sender_interface_ip = data["ipv4"] + + config_data.append("interface {}".format(sender_interface)) + config_data.append("no ip address {}".format(sender_interface_ip)) + config_data.append("ip address {}".format(src_ip)) + + result = create_common_configuration( + tgen, senderRouter, config_data, "interface_config" + ) + if result is not True: + return False + + config_data = [] + links = topo["routers"][destLink]["links"] + pim_neighbor = {key: links[key] for key in [senderRouter]} + + data = pim_neighbor[senderRouter] + if "type" in data and data["type"] == "loopback": + continue + + if "pim" in data and data["pim"] == "enable": + receiver_interface = data["interface"] + receiver_interface_ip = data["ipv4"] + + config_data.append("interface {}".format(receiver_interface)) + config_data.append("no ip address {}".format(receiver_interface_ip)) + config_data.append("ip address {}".format(dest_ip)) + + result = create_common_configuration( + tgen, receiverRouter, config_data, "interface_config" + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: reconfig_interfaces()") + return result + + +def add_rp_interfaces_and_pim_config(tgen, topo, interface, rp, rp_mapping): + """ + Add physical interfaces tp RP for all the RPs + + Parameters + ---------- + * `tgen` : Topogen object + * `topo` : json file data + * `interface` : RP interface + * `rp` : rp for given topology + * `rp_mapping` : dictionary of all groups and RPs + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + + for group, rp_list in rp_mapping.items(): + for _rp in rp_list: + config_data.append("interface {}".format(interface)) + config_data.append("ip address {}".format(_rp)) + config_data.append("ip pim") + + result = create_common_configuration( + tgen, rp, config_data, "interface_config" + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: add_rp_interfaces_and_pim_config()") + return result + + +def scapy_send_bsr_raw_packet( + tgen, topo, senderRouter, receiverRouter, packet=None, interval=1, count=1 +): + """ + Using scapy Raw() method to send BSR raw packet from one FRR + to other + + Parameters: + ----------- + * `tgen` : Topogen object + * `topo` : json file data + * `senderRouter` : Sender router + * `receiverRouter` : Receiver router + * `packet` : BSR packet in raw format + * `interval` : Interval between the packets + * `count` : Number of packets to be sent + + returns: + -------- + errormsg or True + """ + + global CWD + result = "" + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + rnode = tgen.routers()[senderRouter] + + for destLink, data in topo["routers"][senderRouter]["links"].items(): + if "type" in data and data["type"] == "loopback": + continue + + if "pim" in data and data["pim"] == "enable": + sender_interface = data["interface"] + + packet = topo["routers"][senderRouter]["bsm"]["bsr_packets"][packet]["data"] + + if interval > 1 or count > 1: + cmd = ( + "nohup /usr/bin/python {}/send_bsr_packet.py '{}' '{}' " + "--interval={} --count={} &".format( + CWD, packet, sender_interface, interval, count + ) + ) + else: + cmd = ( + "/usr/bin/python {}/send_bsr_packet.py '{}' '{}' " + "--interval={} --count={}".format( + CWD, packet, sender_interface, interval, count + ) + ) + + logger.info("Scapy cmd: \n %s", cmd) + result = rnode.run(cmd) + + if result == "": + return result + + logger.debug("Exiting lib API: scapy_send_bsr_raw_packet") + return True + + +def find_rp_from_bsrp_info(tgen, dut, bsr, grp=None): + """ + Find which RP is having lowest prioriy and returns rp IP + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `bsr`: BSR address + * 'grp': Group Address + + Usage + ----- + dut = "r1" + result = verify_pim_rp_info(tgen, dut, bsr) + + Returns: + dictionary: group and RP, which has to be installed as per + lowest priority or highest priority + """ + + rp_details = {} + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Fetching rp details from bsrp-info", dut) + bsrp_json = run_frr_cmd(rnode, "show ip pim bsrp-info json", isjson=True) + + if grp not in bsrp_json: + return {} + + for group, rp_data in bsrp_json.items(): + if group == "BSR Address" and bsrp_json["BSR Address"] == bsr: + continue + + if group != grp: + continue + + rp_priority = {} + rp_hash = {} + + for rp, value in rp_data.items(): + if rp == "Pending RP count": + continue + rp_priority[value["Rp Address"]] = value["Rp Priority"] + rp_hash[value["Rp Address"]] = value["Hash Val"] + + priority_dict = dict(zip(rp_priority.values(), rp_priority.keys())) + hash_dict = dict(zip(rp_hash.values(), rp_hash.keys())) + + # RP with lowest priority + if len(priority_dict) != 1: + rp_p, lowest_priority = sorted(rp_priority.items(), key=lambda x: x[1])[0] + rp_details[group] = rp_p + + # RP with highest hash value + if len(priority_dict) == 1: + rp_h, highest_hash = sorted(rp_hash.items(), key=lambda x: x[1])[-1] + rp_details[group] = rp_h + + # RP with highest IP address + if len(priority_dict) == 1 and len(hash_dict) == 1: + rp_details[group] = sorted(rp_priority.keys())[-1] + + return rp_details + + +@retry(attempts=6, wait=2, return_is_str=True) +def verify_pim_grp_rp_source(tgen, topo, dut, grp_addr, rp_source, rpadd=None): + """ + Verify pim rp info by running "show ip pim rp-info" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo`: JSON file handler + * `dut`: device under test + * `grp_addr`: IGMP group address + * 'rp_source': source from which rp installed + * 'rpadd': rp address + + Usage + ----- + dut = "r1" + group_address = "225.1.1.1" + rp_source = "BSR" + result = verify_pim_rp_and_source(tgen, topo, dut, group_address, rp_source) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + 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 rpadd != None: + rp_json = show_ip_rp_info_json[rpadd] + if rp_json[0]["group"] == grp_addr: + if rp_json[0]["source"] == rp_source: + logger.info( + "[DUT %s]: Verifying Group and rp_source [PASSED]" + "Found Expected: %s, %s" + % (dut, rp_json[0]["group"], rp_json[0]["source"]) + ) + return True + else: + errormsg = ( + "[DUT %s]: Verifying Group and rp_source [FAILED]" + "Expected (%s, %s) " + "Found (%s, %s)" + % ( + dut, + grp_addr, + rp_source, + rp_json[0]["group"], + rp_json[0]["source"], + ) + ) + return errormsg + errormsg = ( + "[DUT %s]: Verifying Group and rp_source [FAILED]" + "Expected: %s, %s but not found" % (dut, grp_addr, rp_source) + ) + return errormsg + + for rp in show_ip_rp_info_json: + rp_json = show_ip_rp_info_json[rp] + logger.info("%s", rp_json) + if rp_json[0]["group"] == grp_addr: + if rp_json[0]["source"] == rp_source: + logger.info( + "[DUT %s]: Verifying Group and rp_source [PASSED]" + "Found Expected: %s, %s" + % (dut, rp_json[0]["group"], rp_json[0]["source"]) + ) + return True + else: + errormsg = ( + "[DUT %s]: Verifying Group and rp_source [FAILED]" + "Expected (%s, %s) " + "Found (%s, %s)" + % ( + dut, + grp_addr, + rp_source, + rp_json[0]["group"], + rp_json[0]["source"], + ) + ) + return errormsg + + errormsg = ( + "[DUT %s]: Verifying Group and rp_source [FAILED]" + "Expected: %s, %s but not found" % (dut, grp_addr, rp_source) + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + + return errormsg + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_bsr(tgen, topo, dut, bsr_ip): + """ + Verify all PIM interface are up and running, config is verified + using "show ip pim interface" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : device under test + * 'bsr' : bsr ip to be verified + + Usage + ----- + result = verify_pim_bsr(tgen, topo, dut, bsr_ip) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for router in tgen.routers(): + if router != dut: + continue + + logger.info("[DUT: %s]: Verifying PIM bsr status:", dut) + + rnode = tgen.routers()[dut] + pim_bsr_json = rnode.vtysh_cmd("show ip pim bsr json", isjson=True) + + logger.info("show_ip_pim_bsr_json: \n %s", pim_bsr_json) + + # Verifying PIM bsr + if pim_bsr_json["bsr"] != bsr_ip: + errormsg = ( + "[DUT %s]:" + "bsr status: not found" + "[FAILED]!! Expected : %s, Found : %s" + % (dut, bsr_ip, pim_bsr_json["bsr"]) + ) + return errormsg + + logger.info( + "[DUT %s]:" " bsr status: found, Address :%s" " [PASSED]!!", + dut, + pim_bsr_json["bsr"], + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_ip_pim_upstream_rpf(tgen, topo, dut, interface, group_addresses, rp=None): + """ + Verify IP PIM upstream rpf, config is verified + using "show ip pim neighbor" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo` : json file data + * `dut` : devuce under test + * `interface` : upstream interface + * `group_addresses` : list of group address for which upstream info + needs to be checked + * `rp` : RP address + + Usage + ----- + result = verify_ip_pim_upstream_rpf(gen, topo, dut, interface, + group_addresses, rp=None) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if "pim" in topo["routers"][dut]: + + logger.info("[DUT: %s]: Verifying ip pim upstream rpf:", dut) + + rnode = tgen.routers()[dut] + show_ip_pim_upstream_rpf_json = rnode.vtysh_cmd( + "show ip pim upstream-rpf json", isjson=True + ) + + logger.info( + "show_ip_pim_upstream_rpf_json: \n %s", show_ip_pim_upstream_rpf_json + ) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + for grp_addr in group_addresses: + for destLink, data in topo["routers"][dut]["links"].items(): + if "type" in data and data["type"] == "loopback": + continue + + if "pim" not in topo["routers"][destLink]: + continue + + # Verify RP info + if rp is None: + rp_details = find_rp_details(tgen, topo) + else: + rp_details = {dut: ip} + rp_details[dut] = rp + + if dut in rp_details: + pim_nh_intf_ip = topo["routers"][dut]["links"]["lo"]["ipv4"].split( + "/" + )[0] + else: + if destLink not in interface: + continue + + links = topo["routers"][destLink]["links"] + pim_neighbor = {key: links[key] for key in [dut]} + + data = pim_neighbor[dut] + if "pim" in data and data["pim"] == "enable": + pim_nh_intf_ip = data["ipv4"].split("/")[0] + + upstream_rpf_json = show_ip_pim_upstream_rpf_json[grp_addr]["*"] + + # Verifying ip pim upstream rpf + if ( + upstream_rpf_json["rpfInterface"] == interface + and upstream_rpf_json["ribNexthop"] != pim_nh_intf_ip + ): + errormsg = ( + "[DUT %s]: Verifying group: %s, " + "rpf interface: %s, " + " rib Nexthop check [FAILED]!!" + "Expected: %s, Found: %s" + % ( + dut, + grp_addr, + interface, + pim_nh_intf_ip, + upstream_rpf_json["ribNexthop"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying group: %s," + " rpf interface: %s, " + " rib Nexthop: %s [PASSED]!!", + dut, + grp_addr, + interface, + pim_nh_intf_ip, + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +def enable_disable_pim_unicast_bsm(tgen, router, intf, enable=True): + """ + Helper API to enable or disable pim bsm on interfaces + + Parameters + ---------- + * `tgen` : Topogen object + * `router` : router id to be configured. + * `intf` : Interface to be configured + * `enable` : this flag denotes if config should be enabled or disabled + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + cmd = "interface {}".format(intf) + config_data.append(cmd) + + if enable == True: + config_data.append("ip pim unicast-bsm") + else: + config_data.append("no ip pim unicast-bsm") + + result = create_common_configuration( + tgen, router, config_data, "interface_config", build=False + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +def enable_disable_pim_bsm(tgen, router, intf, enable=True): + """ + Helper API to enable or disable pim bsm on interfaces + + Parameters + ---------- + * `tgen` : Topogen object + * `router` : router id to be configured. + * `intf` : Interface to be configured + * `enable` : this flag denotes if config should be enabled or disabled + + Returns + ------- + True or False + """ + result = False + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + try: + config_data = [] + cmd = "interface {}".format(intf) + config_data.append(cmd) + + if enable is True: + config_data.append("ip pim bsm") + else: + config_data.append("no ip pim bsm") + + result = create_common_configuration( + tgen, router, config_data, "interface_config", build=False + ) + if result is not True: + return False + + except InvalidCLIError: + # Traceback + errormsg = traceback.format_exc() + logger.error(errormsg) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return result + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_ip_pim_join(tgen, topo, dut, interface, group_addresses, src_address=None): + """ + Verify ip pim join by running "show ip pim join" cli + + Parameters + ---------- + * `tgen`: topogen object + * `topo`: JSON file handler + * `dut`: device under test + * `interface`: interface name, from which PIM join would come + * `group_addresses`: IGMP group address + * `src_address`: Source address + + Usage + ----- + dut = "r1" + interface = "r1-r0-eth0" + group_address = "225.1.1.1" + result = verify_ip_pim_join(tgen, dut, star, group_address, interface) + + Returns + ------- + errormsg(str) or True + """ + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying pim join", dut) + show_pim_join_json = run_frr_cmd(rnode, "show ip pim join json", isjson=True) + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + for grp_addr in group_addresses: + # Verify if IGMP is enabled in DUT + if "igmp" not in topo["routers"][dut]: + pim_join = True + else: + pim_join = False + + interface_json = show_pim_join_json[interface] + + grp_addr = grp_addr.split("/")[0] + for source, data in interface_json[grp_addr].items(): + + # Verify pim join + if pim_join: + if data["group"] == grp_addr and data["channelJoinName"] == "JOIN": + logger.info( + "[DUT %s]: Verifying pim join for group: %s" + "[PASSED]!! Found Expected: (%s)", + dut, + grp_addr, + data["channelJoinName"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying pim join for group: %s" + "[FAILED]!! Expected: (%s) " + "Found: (%s)" % (dut, grp_addr, "JOIN", data["channelJoinName"]) + ) + return errormsg + + if not pim_join: + if data["group"] == grp_addr and data["channelJoinName"] == "NOINFO": + logger.info( + "[DUT %s]: Verifying pim join for group: %s" + "[PASSED]!! Found Expected: (%s)", + dut, + grp_addr, + data["channelJoinName"], + ) + else: + errormsg = ( + "[DUT %s]: Verifying pim join for group: %s" + "[FAILED]!! Expected: (%s) " + "Found: (%s)" + % (dut, grp_addr, "NOINFO", data["channelJoinName"]) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=31, wait=2, return_is_dict=True) +def verify_igmp_config(tgen, input_dict, stats_return=False): + """ + Verify igmp interface details, verifying following configs: + timerQueryInterval + timerQueryResponseIntervalMsec + lastMemberQueryCount + timerLastMemberQueryMsec + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict` : Input dict data, required to verify + timer + * `stats_return`: If user wants API to return statistics + + Usage + ----- + input_dict ={ + "l1": { + "igmp": { + "interfaces": { + "l1-i1-eth1": { + "igmp": { + "query": { + "query-interval" : 200, + "query-max-response-time" : 100 + }, + "statistics": { + "queryV2" : 2, + "reportV2" : 1 + } + } + } + } + } + } + } + result = verify_igmp_config(tgen, input_dict, stats_return) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + for interface, data in input_dict[dut]["igmp"]["interfaces"].items(): + + statistics = False + report = False + if "statistics" in input_dict[dut]["igmp"]["interfaces"][interface]["igmp"]: + statistics = True + cmd = "show ip igmp statistics" + else: + cmd = "show ip igmp" + + logger.info( + "[DUT: %s]: Verifying IGMP interface %s detail:", dut, interface + ) + + if statistics: + if ( + "report" + in input_dict[dut]["igmp"]["interfaces"][interface]["igmp"][ + "statistics" + ] + ): + report = True + + if statistics and report: + show_ip_igmp_intf_json = run_frr_cmd( + rnode, "{} json".format(cmd, interface), isjson=True + ) + intf_detail_json = show_ip_igmp_intf_json["global"] + else: + show_ip_igmp_intf_json = run_frr_cmd( + rnode, "{} interface {} json".format(cmd, interface), isjson=True + ) + + if not report: + if interface not in show_ip_igmp_intf_json: + errormsg = ( + "[DUT %s]: IGMP interface: %s " + " is not present in CLI output " + "[FAILED]!! " % (dut, interface) + ) + return errormsg + + else: + intf_detail_json = show_ip_igmp_intf_json[interface] + + if stats_return: + igmp_stats = {} + + if "statistics" in data["igmp"]: + if stats_return: + igmp_stats["statistics"] = {} + for query, value in data["igmp"]["statistics"].items(): + if query == "queryV2": + # Verifying IGMP interface queryV2 statistics + if stats_return: + igmp_stats["statistics"][query] = intf_detail_json[ + "queryV2" + ] + + else: + if intf_detail_json["queryV2"] != value: + errormsg = ( + "[DUT %s]: IGMP interface: %s " + " queryV2 statistics verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % ( + dut, + interface, + value, + intf_detail_json["queryV2"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " + "queryV2 statistics is %s", + dut, + interface, + value, + ) + + if query == "reportV2": + # Verifying IGMP interface timerV2 statistics + if stats_return: + igmp_stats["statistics"][query] = intf_detail_json[ + "reportV2" + ] + + else: + if intf_detail_json["reportV2"] <= value: + errormsg = ( + "[DUT %s]: IGMP reportV2 " + "statistics verification " + "[FAILED]!! Expected : %s " + "or more, Found : %s" + % ( + dut, + interface, + value, + intf_detail_json["reportV2"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP reportV2 " "statistics is %s", + dut, + intf_detail_json["reportV2"], + ) + + if "query" in data["igmp"]: + for query, value in data["igmp"]["query"].items(): + if query == "query-interval": + # Verifying IGMP interface query interval timer + if intf_detail_json["timerQueryInterval"] != value: + errormsg = ( + "[DUT %s]: IGMP interface: %s " + " query-interval verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % ( + dut, + interface, + value, + intf_detail_json["timerQueryInterval"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " "query-interval is %s", + dut, + interface, + value, + ) + + if query == "query-max-response-time": + # Verifying IGMP interface query max response timer + if ( + intf_detail_json["timerQueryResponseIntervalMsec"] + != value * 100 + ): + errormsg = ( + "[DUT %s]: IGMP interface: %s " + "query-max-response-time " + "verification [FAILED]!!" + " Expected : %s, Found : %s" + % ( + dut, + interface, + value * 1000, + intf_detail_json["timerQueryResponseIntervalMsec"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " + "query-max-response-time is %s ms", + dut, + interface, + value * 100, + ) + + if query == "last-member-query-count": + # Verifying IGMP interface last member query count + if intf_detail_json["lastMemberQueryCount"] != value: + errormsg = ( + "[DUT %s]: IGMP interface: %s " + "last-member-query-count " + "verification [FAILED]!!" + " Expected : %s, Found : %s" + % ( + dut, + interface, + value, + intf_detail_json["lastMemberQueryCount"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " + "last-member-query-count is %s ms", + dut, + interface, + value * 1000, + ) + + if query == "last-member-query-interval": + # Verifying IGMP interface last member query interval + if ( + intf_detail_json["timerLastMemberQueryMsec"] + != value * 100 * intf_detail_json["lastMemberQueryCount"] + ): + errormsg = ( + "[DUT %s]: IGMP interface: %s " + "last-member-query-interval " + "verification [FAILED]!!" + " Expected : %s, Found : %s" + % ( + dut, + interface, + value * 1000, + intf_detail_json["timerLastMemberQueryMsec"], + ) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " + "last-member-query-interval is %s ms", + dut, + interface, + value * intf_detail_json["lastMemberQueryCount"] * 100, + ) + + if "version" in data["igmp"]: + # Verifying IGMP interface state is up + if intf_detail_json["state"] != "up": + errormsg = ( + "[DUT %s]: IGMP interface: %s " + " state: %s verification " + "[FAILED]!!" % (dut, interface, intf_detail_json["state"]) + ) + return errormsg + + logger.info( + "[DUT %s]: IGMP interface: %s " "state: %s", + dut, + interface, + intf_detail_json["state"], + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True if stats_return == False else igmp_stats + + +@retry(attempts=31, wait=2, return_is_str=True) +def verify_pim_config(tgen, input_dict): + """ + Verify pim interface details, verifying following configs: + drPriority + helloPeriod + helloReceived + helloSend + drAddress + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict` : Input dict data, required to verify + timer + + Usage + ----- + input_dict ={ + "l1": { + "igmp": { + "interfaces": { + "l1-i1-eth1": { + "pim": { + "drPriority" : 10, + "helloPeriod" : 5 + } + } + } + } + } + } + } + result = verify_pim_config(tgen, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + for dut in input_dict.keys(): + rnode = tgen.routers()[dut] + + for interface, data in input_dict[dut]["pim"]["interfaces"].items(): + + logger.info("[DUT: %s]: Verifying PIM interface %s detail:", dut, interface) + + show_ip_igmp_intf_json = run_frr_cmd( + rnode, "show ip pim interface {} json".format(interface), isjson=True + ) + + if interface not in show_ip_igmp_intf_json: + errormsg = ( + "[DUT %s]: PIM interface: %s " + " is not present in CLI output " + "[FAILED]!! " % (dut, interface) + ) + return errormsg + + intf_detail_json = show_ip_igmp_intf_json[interface] + + for config, value in data.items(): + if config == "helloPeriod": + # Verifying PIM interface helloPeriod + if intf_detail_json["helloPeriod"] != value: + errormsg = ( + "[DUT %s]: PIM interface: %s " + " helloPeriod verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % (dut, interface, value, intf_detail_json["helloPeriod"]) + ) + return errormsg + + logger.info( + "[DUT %s]: PIM interface: %s " "helloPeriod is %s", + dut, + interface, + value, + ) + + if config == "drPriority": + # Verifying PIM interface drPriority + if intf_detail_json["drPriority"] != value: + errormsg = ( + "[DUT %s]: PIM interface: %s " + " drPriority verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % (dut, interface, value, intf_detail_json["drPriority"]) + ) + return errormsg + + logger.info( + "[DUT %s]: PIM interface: %s " "drPriority is %s", + dut, + interface, + value, + ) + + if config == "drAddress": + # Verifying PIM interface drAddress + if intf_detail_json["drAddress"] != value: + errormsg = ( + "[DUT %s]: PIM interface: %s " + " drAddress verification " + "[FAILED]!! Expected : %s," + " Found : %s" + % (dut, interface, value, intf_detail_json["drAddress"]) + ) + return errormsg + + logger.info( + "[DUT %s]: PIM interface: %s " "drAddress is %s", + dut, + interface, + value, + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + +@retry(attempts=21, wait=2, return_is_dict=True) +def verify_multicast_traffic(tgen, input_dict, return_traffic=False): + """ + Verify multicast traffic by running + "show multicast traffic count json" cli + + Parameters + ---------- + * `tgen`: topogen object + * `input_dict(dict)`: defines DUT, what and for which interfaces + traffic needs to be verified + * `return_traffic`: returns traffic stats + Usage + ----- + input_dict = { + "r1": { + "traffic_received": ["r1-r0-eth0"], + "traffic_sent": ["r1-r0-eth0"] + } + } + + result = verify_multicast_traffic(tgen, input_dict) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + traffic_dict = {} + for dut in input_dict.keys(): + if dut not in tgen.routers(): + continue + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying multicast " "traffic", dut) + + show_multicast_traffic_json = run_frr_cmd( + rnode, "show ip multicast count json", isjson=True + ) + + for traffic_type, interfaces in input_dict[dut].items(): + traffic_dict[traffic_type] = {} + if traffic_type == "traffic_received": + for interface in interfaces: + traffic_dict[traffic_type][interface] = {} + interface_json = show_multicast_traffic_json[interface] + + if interface_json["pktsIn"] == 0 and interface_json["bytesIn"] == 0: + errormsg = ( + "[DUT %s]: Multicast traffic is " + "not received on interface %s " + "PktsIn: %s, BytesIn: %s " + "[FAILED]!!" + % ( + dut, + interface, + interface_json["pktsIn"], + interface_json["bytesIn"], + ) + ) + return errormsg + + elif ( + interface_json["pktsIn"] != 0 and interface_json["bytesIn"] != 0 + ): + + traffic_dict[traffic_type][interface][ + "pktsIn" + ] = interface_json["pktsIn"] + traffic_dict[traffic_type][interface][ + "bytesIn" + ] = interface_json["bytesIn"] + + logger.info( + "[DUT %s]: Multicast traffic is " + "received on interface %s " + "PktsIn: %s, BytesIn: %s " + "[PASSED]!!" + % ( + dut, + interface, + interface_json["pktsIn"], + interface_json["bytesIn"], + ) + ) + + else: + errormsg = ( + "[DUT %s]: Multicast traffic interface %s:" + " Miss-match in " + "PktsIn: %s, BytesIn: %s" + "[FAILED]!!" + % ( + dut, + interface, + interface_json["pktsIn"], + interface_json["bytesIn"], + ) + ) + return errormsg + + if traffic_type == "traffic_sent": + traffic_dict[traffic_type] = {} + for interface in interfaces: + traffic_dict[traffic_type][interface] = {} + interface_json = show_multicast_traffic_json[interface] + + if ( + interface_json["pktsOut"] == 0 + and interface_json["bytesOut"] == 0 + ): + errormsg = ( + "[DUT %s]: Multicast traffic is " + "not received on interface %s " + "PktsIn: %s, BytesIn: %s" + "[FAILED]!!" + % ( + dut, + interface, + interface_json["pktsOut"], + interface_json["bytesOut"], + ) + ) + return errormsg + + elif ( + interface_json["pktsOut"] != 0 + and interface_json["bytesOut"] != 0 + ): + + traffic_dict[traffic_type][interface][ + "pktsOut" + ] = interface_json["pktsOut"] + traffic_dict[traffic_type][interface][ + "bytesOut" + ] = interface_json["bytesOut"] + + logger.info( + "[DUT %s]: Multicast traffic is " + "received on interface %s " + "PktsOut: %s, BytesOut: %s " + "[PASSED]!!" + % ( + dut, + interface, + interface_json["pktsOut"], + interface_json["bytesOut"], + ) + ) + else: + errormsg = ( + "[DUT %s]: Multicast traffic interface %s:" + " Miss-match in " + "PktsOut: %s, BytesOut: %s " + "[FAILED]!!" + % ( + dut, + interface, + interface_json["pktsOut"], + interface_json["bytesOut"], + ) + ) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True if return_traffic == False else traffic_dict + + +def get_refCount_for_mroute(tgen, dut, iif, src_address, group_addresses): + """ + Verify upstream inbound interface is updated correctly + by running "show ip pim upstream" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `iif`: inbound interface + * `src_address`: source address + * `group_addresses`: IGMP group address + + Usage + ----- + dut = "r1" + iif = "r1-r0-eth0" + src_address = "*" + group_address = "225.1.1.1" + result = get_refCount_for_mroute(tgen, dut, iif, src_address, + group_address) + + Returns + ------- + refCount(int) + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + refCount = 0 + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying refCount for mroutes: ", 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_addr in group_addresses: + # Verify group address + if grp_addr not in show_ip_pim_upstream_json: + errormsg = "[DUT %s]: Verifying upstream" " for group %s [FAILED]!!" % ( + dut, + grp_addr, + ) + return errormsg + group_addr_json = show_ip_pim_upstream_json[grp_addr] + + # Verify source address + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying upstream" " for (%s,%s) [FAILED]!!" % ( + dut, + src_address, + grp_addr, + ) + return errormsg + + # Verify Inbound Interface + if group_addr_json[src_address]["inboundInterface"] == iif: + refCount = group_addr_json[src_address]["refCount"] + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return refCount + + +@retry(attempts=21, wait=2, return_is_str=True) +def verify_multicast_flag_state(tgen, dut, src_address, group_addresses, flag): + """ + Verify flag state for mroutes and make sure (*, G)/(S, G) are having + coorect flags by running "show ip mroute" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + * `src_address`: source address + * `group_addresses`: IGMP group address + * `flag`: flag state, needs to be verified + + Usage + ----- + dut = "r1" + flag = "SC" + group_address = "225.1.1.1" + result = verify_multicast_flag_state(tgen, dut, src_address, + group_address, flag) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + logger.info("[DUT: %s]: Verifying flag state for mroutes", dut) + show_ip_mroute_json = run_frr_cmd(rnode, "show ip mroute json", isjson=True) + + if bool(show_ip_mroute_json) == False: + error_msg = "[DUT %s]: mroutes are not present or flushed out !!" % (dut) + return error_msg + + if type(group_addresses) is not list: + group_addresses = [group_addresses] + + for grp_addr in group_addresses: + if grp_addr not in show_ip_mroute_json: + errormsg = ( + "[DUT %s]: Verifying (%s, %s) mroute," "[FAILED]!! ", + dut, + src_address, + grp_addr, + ) + return errormsg + else: + group_addr_json = show_ip_mroute_json[grp_addr] + + if src_address not in group_addr_json: + errormsg = "[DUT %s]: Verifying (%s, %s) mroute," "[FAILED]!! " % ( + dut, + src_address, + grp_addr, + ) + return errormsg + else: + mroutes = group_addr_json[src_address] + + if mroutes["installed"] != 0: + logger.info( + "[DUT %s]: mroute (%s,%s) is installed", dut, src_address, grp_addr + ) + + if mroutes["flags"] != flag: + errormsg = ( + "[DUT %s]: Verifying flag for (%s, %s) " + "mroute [FAILED]!! " + "Expected: %s Found: %s" + % (dut, src_address, grp_addr, flag, mroutes["flags"]) + ) + return errormsg + + logger.info( + "[DUT %s]: Verifying flag for (%s, %s)" + " mroute, [PASSED]!! " + "Found Expected: %s", + dut, + src_address, + grp_addr, + mroutes["flags"], + ) + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True diff --git a/tests/topotests/lib/topojson.py b/tests/topotests/lib/topojson.py index 0e59f90a20..88e6f78b92 100644 --- a/tests/topotests/lib/topojson.py +++ b/tests/topotests/lib/topojson.py @@ -43,6 +43,7 @@ from lib.common_config import ( create_vrf_cfg, ) +from lib.pim import create_pim_config, create_igmp_config from lib.bgp import create_router_bgp from lib.ospf import create_router_ospf @@ -68,20 +69,18 @@ def build_topo_from_json(tgen, topo): topo["switches"].keys(), key=lambda x: int(re_search("\d+", x).group(0)) ) - listRouters = ROUTER_LIST[:] - listSwitches = SWITCH_LIST[:] + listRouters = sorted(ROUTER_LIST[:]) + listSwitches = sorted(SWITCH_LIST[:]) listAllRouters = deepcopy(listRouters) dictSwitches = {} for routerN in ROUTER_LIST: logger.info("Topo: Add router {}".format(routerN)) tgen.add_router(routerN) - listRouters.append(routerN) for switchN in SWITCH_LIST: logger.info("Topo: Add switch {}".format(switchN)) dictSwitches[switchN] = tgen.add_switch(switchN) - listSwitches.append(switchN) if "ipv4base" in topo: ipv4Next = ipaddress.IPv4Address(topo["link_ip_start"]["ipv4"]) @@ -101,18 +100,8 @@ def build_topo_from_json(tgen, topo): curRouter = listRouters.pop(0) # Physical Interfaces if "links" in topo["routers"][curRouter]: - - def link_sort(x): - if x == "lo": - return 0 - elif "link" in x: - return int(x.split("-link")[1]) - else: - return int(re_search("\d+", x).group(0)) - for destRouterLink, data in sorted( - topo["routers"][curRouter]["links"].items(), - key=lambda x: link_sort(x[0]), + topo["routers"][curRouter]["links"].iteritems() ): currRouter_lo_json = topo["routers"][curRouter]["links"][destRouterLink] # Loopback interfaces @@ -321,6 +310,8 @@ def build_config_from_json(tgen, topo, save_bkup=True): ("prefix_lists", create_prefix_lists), ("bgp_community_list", create_bgp_community_lists), ("route_maps", create_route_maps), + ("pim", create_pim_config), + ("igmp", create_igmp_config), ("bgp", create_router_bgp), ("ospf", create_router_ospf), ]