summaryrefslogtreecommitdiff
path: root/tests/topotests/lib/common_config.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/topotests/lib/common_config.py')
-rw-r--r--tests/topotests/lib/common_config.py1420
1 files changed, 922 insertions, 498 deletions
diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py
index 6a02e50127..cf8efdea16 100644
--- a/tests/topotests/lib/common_config.py
+++ b/tests/topotests/lib/common_config.py
@@ -18,55 +18,43 @@
# OF THIS SOFTWARE.
#
+import ipaddress
+import json
+import os
+import platform
+import socket
+import subprocess
+import sys
+import traceback
from collections import OrderedDict
-from datetime import datetime, timedelta
-from time import sleep
from copy import deepcopy
-from subprocess import call
-from subprocess import STDOUT as SUB_STDOUT
-from subprocess import PIPE as SUB_PIPE
-from subprocess import Popen
+from datetime import datetime, timedelta
from functools import wraps
from re import search as re_search
-from tempfile import mkdtemp
-
-import os
-import sys
-import traceback
-import socket
-import ipaddress
-import platform
+from time import sleep
try:
# Imports from python2
- from StringIO import StringIO
import ConfigParser as configparser
except ImportError:
# Imports from python3
- from io import StringIO
import configparser
-from lib.topolog import logger, logger_config
+from lib.micronet import comm_error
from lib.topogen import TopoRouter, get_topogen
-from lib.topotest import interface_set_status, version_cmp, frr_unicode
+from lib.topolog import get_logger, logger
+from lib.topotest import frr_unicode, interface_set_status, version_cmp
+from lib import topotest
FRRCFG_FILE = "frr_json.conf"
FRRCFG_BKUP_FILE = "frr_json_initial.conf"
ERROR_LIST = ["Malformed", "Failure", "Unknown", "Incomplete"]
-ROUTER_LIST = []
####
CD = os.path.dirname(os.path.realpath(__file__))
PYTESTINI_PATH = os.path.join(CD, "../pytest.ini")
-# Creating tmp dir with testsuite name to avoid conflict condition when
-# multiple testsuites run together. All temporary files would be created
-# in this dir and this dir would be removed once testsuite run is
-# completed
-LOGDIR = "/tmp/topotests/"
-TMPDIR = None
-
# NOTE: to save execution logs to log file frrtest_log_dir must be configured
# in `pytest.ini`.
config = configparser.ConfigParser()
@@ -138,17 +126,22 @@ DEBUG_LOGS = {
],
}
+g_iperf_client_procs = {}
+g_iperf_server_procs = {}
+
+
def is_string(value):
try:
return isinstance(value, basestring)
except NameError:
return isinstance(value, str)
+
if config.has_option("topogen", "verbosity"):
loglevel = config.get("topogen", "verbosity")
- loglevel = loglevel.upper()
+ loglevel = loglevel.lower()
else:
- loglevel = "INFO"
+ loglevel = "info"
if config.has_option("topogen", "frrtest_log_dir"):
frrtest_log_dir = config.get("topogen", "frrtest_log_dir")
@@ -157,8 +150,8 @@ if config.has_option("topogen", "frrtest_log_dir"):
frrtest_log_file = frrtest_log_dir + logfile_name + str(time_stamp)
print("frrtest_log_file..", frrtest_log_file)
- logger = logger_config.get_logger(
- name="test_execution_logs", log_level=loglevel, target=frrtest_log_file
+ logger = get_logger(
+ "test_execution_logs", log_level=loglevel, target=frrtest_log_file
)
print("Logs will be sent to logfile: {}".format(frrtest_log_file))
@@ -218,8 +211,6 @@ def set_seq_id(obj_type, router, id, obj_name):
class InvalidCLIError(Exception):
"""Raise when the CLI command is wrong"""
- pass
-
def run_frr_cmd(rnode, cmd, isjson=False):
"""
@@ -235,14 +226,12 @@ def run_frr_cmd(rnode, cmd, isjson=False):
if True:
if isjson:
- logger.debug(ret_data)
- print_data = rnode.vtysh_cmd(cmd.rstrip("json"), isjson=False)
+ print_data = json.dumps(ret_data)
else:
print_data = ret_data
-
logger.info(
- "Output for command [ %s] on router %s:\n%s",
- cmd.rstrip("json"),
+ "Output for command [%s] on router %s:\n%s",
+ cmd,
rnode.name,
print_data,
)
@@ -278,25 +267,27 @@ def apply_raw_config(tgen, input_dict):
True or errormsg
"""
- result = True
+ rlist = []
+
for router_name in input_dict.keys():
config_cmd = input_dict[router_name]["raw_config"]
if not isinstance(config_cmd, list):
config_cmd = [config_cmd]
- frr_cfg_file = "{}/{}/{}".format(TMPDIR, router_name, FRRCFG_FILE)
+ frr_cfg_file = "{}/{}/{}".format(tgen.logdir, router_name, FRRCFG_FILE)
with open(frr_cfg_file, "w") as cfg:
for cmd in config_cmd:
cfg.write("{}\n".format(cmd))
- result = load_config_to_router(tgen, router_name)
+ rlist.append(router_name)
- return result
+ # Load config on all routers
+ return load_config_to_routers(tgen, rlist)
-def create_common_configuration(
- tgen, router, data, config_type=None, build=False, load_config=True
+def create_common_configurations(
+ tgen, config_dict, config_type=None, build=False, load_config=True
):
"""
API to create object of class FRRConfig and also create frr_json.conf
@@ -305,8 +296,8 @@ def create_common_configuration(
Parameters
----------
* `tgen`: tgen object
- * `data`: Configuration data saved in a list.
- * `router` : router id to be configured.
+ * `config_dict`: Configuration data saved in a dict of { router: config-list }
+ * `routers` : list of router id to be configured.
* `config_type` : Syntactic information while writing configuration. Should
be one of the value as mentioned in the config_map below.
* `build` : Only for initial setup phase this is set as True
@@ -314,9 +305,6 @@ def create_common_configuration(
-------
True or False
"""
- TMPDIR = os.path.join(LOGDIR, tgen.modname)
-
- fname = "{}/{}/{}".format(TMPDIR, router, FRRCFG_FILE)
config_map = OrderedDict(
{
@@ -342,27 +330,53 @@ def create_common_configuration(
else:
mode = "w"
- try:
- frr_cfg_fd = open(fname, mode)
- if config_type:
- frr_cfg_fd.write(config_map[config_type])
- for line in data:
- frr_cfg_fd.write("{} \n".format(str(line)))
- frr_cfg_fd.write("\n")
-
- except IOError as err:
- logger.error(
- "Unable to open FRR Config File. error(%s): %s" % (err.errno, err.strerror)
- )
- return False
- finally:
- frr_cfg_fd.close()
+ routers = config_dict.keys()
+ for router in routers:
+ fname = "{}/{}/{}".format(tgen.logdir, router, FRRCFG_FILE)
+ try:
+ frr_cfg_fd = open(fname, mode)
+ if config_type:
+ frr_cfg_fd.write(config_map[config_type])
+ for line in config_dict[router]:
+ frr_cfg_fd.write("{} \n".format(str(line)))
+ frr_cfg_fd.write("\n")
+
+ except IOError as err:
+ logger.error("Unable to open FRR Config '%s': %s" % (fname, str(err)))
+ return False
+ finally:
+ frr_cfg_fd.close()
# If configuration applied from build, it will done at last
+ result = True
if not build and load_config:
- load_config_to_router(tgen, router)
+ result = load_config_to_routers(tgen, routers)
- return True
+ return result
+
+
+def create_common_configuration(
+ tgen, router, data, config_type=None, build=False, load_config=True
+):
+ """
+ API to create object of class FRRConfig and also create frr_json.conf
+ file. It will create interface and common configurations and save it to
+ frr_json.conf and load to router
+ Parameters
+ ----------
+ * `tgen`: tgen object
+ * `data`: Configuration data saved in a list.
+ * `router` : router id to be configured.
+ * `config_type` : Syntactic information while writing configuration. Should
+ be one of the value as mentioned in the config_map below.
+ * `build` : Only for initial setup phase this is set as True
+ Returns
+ -------
+ True or False
+ """
+ return create_common_configurations(
+ tgen, {router: data}, config_type, build, load_config
+ )
def kill_router_daemons(tgen, router, daemons, save_config=True):
@@ -457,6 +471,40 @@ def check_router_status(tgen):
return True
+def save_initial_config_on_routers(tgen):
+ """Save current configuration on routers to FRRCFG_BKUP_FILE.
+
+ FRRCFG_BKUP_FILE is the file that will be restored when `reset_config_on_routers()`
+ is called.
+
+ Parameters
+ ----------
+ * `tgen` : Topogen object
+ """
+ router_list = tgen.routers()
+ target_cfg_fmt = tgen.logdir + "/{}/frr_json_initial.conf"
+
+ # Get all running configs in parallel
+ procs = {}
+ for rname in router_list:
+ logger.info("Fetching running config for router %s", rname)
+ procs[rname] = router_list[rname].popen(
+ ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
+ stdin=None,
+ stdout=open(target_cfg_fmt.format(rname), "w"),
+ stderr=subprocess.PIPE,
+ )
+ for rname, p in procs.items():
+ _, error = p.communicate()
+ if p.returncode:
+ logger.error(
+ "Get running config for %s failed %d: %s", rname, p.returncode, error
+ )
+ raise InvalidCLIError(
+ "vtysh show running error on {}: {}".format(rname, error)
+ )
+
+
def reset_config_on_routers(tgen, routerName=None):
"""
Resets configuration on routers to the snapshot created using input JSON
@@ -470,145 +518,337 @@ def reset_config_on_routers(tgen, routerName=None):
logger.debug("Entering API: reset_config_on_routers")
+ tgen.cfg_gen += 1
+ gen = tgen.cfg_gen
+
+ # Trim the router list if needed
router_list = tgen.routers()
- for rname in ROUTER_LIST:
- if routerName and routerName != rname:
- continue
+ if routerName:
+ if routerName not in router_list:
+ logger.warning(
+ "Exiting API: reset_config_on_routers: no router %s",
+ routerName,
+ exc_info=True,
+ )
+ return True
+ router_list = {routerName: router_list[routerName]}
+
+ delta_fmt = tgen.logdir + "/{}/delta-{}.conf"
+ # FRRCFG_BKUP_FILE
+ target_cfg_fmt = tgen.logdir + "/{}/frr_json_initial.conf"
+ run_cfg_fmt = tgen.logdir + "/{}/frr-{}.sav"
+
+ #
+ # Get all running configs in parallel
+ #
+ procs = {}
+ for rname in router_list:
+ logger.info("Fetching running config for router %s", rname)
+ procs[rname] = router_list[rname].popen(
+ ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
+ stdin=None,
+ stdout=open(run_cfg_fmt.format(rname, gen), "w"),
+ stderr=subprocess.PIPE,
+ )
+ for rname, p in procs.items():
+ _, error = p.communicate()
+ if p.returncode:
+ logger.error(
+ "Get running config for %s failed %d: %s", rname, p.returncode, error
+ )
+ raise InvalidCLIError(
+ "vtysh show running error on {}: {}".format(rname, error)
+ )
- router = router_list[rname]
- logger.info("Configuring router %s to initial test configuration", rname)
+ #
+ # Get all delta's in parallel
+ #
+ procs = {}
+ for rname in router_list:
+ logger.info(
+ "Generating delta for router %s to new configuration (gen %d)", rname, gen
+ )
+ procs[rname] = tgen.net.popen(
+ [
+ "/usr/lib/frr/frr-reload.py",
+ "--test-reset",
+ "--input",
+ run_cfg_fmt.format(rname, gen),
+ "--test",
+ target_cfg_fmt.format(rname),
+ ],
+ stdin=None,
+ stdout=open(delta_fmt.format(rname, gen), "w"),
+ stderr=subprocess.PIPE,
+ )
+ for rname, p in procs.items():
+ _, error = p.communicate()
+ if p.returncode:
+ logger.error(
+ "Delta file creation for %s failed %d: %s", rname, p.returncode, error
+ )
+ raise InvalidCLIError("frr-reload error for {}: {}".format(rname, error))
- cfg = router.run("vtysh -c 'show running'")
- fname = "{}/{}/frr.sav".format(TMPDIR, rname)
- dname = "{}/{}/delta.conf".format(TMPDIR, rname)
- f = open(fname, "w")
- for line in cfg.split("\n"):
- line = line.strip()
+ #
+ # Apply all the deltas in parallel
+ #
+ procs = {}
+ for rname in router_list:
+ logger.info("Applying delta config on router %s", rname)
- if (
- line == "Building configuration..."
- or line == "Current configuration:"
- or not line
- ):
- continue
- f.write(line)
- f.write("\n")
-
- f.close()
- run_cfg_file = "{}/{}/frr.sav".format(TMPDIR, rname)
- init_cfg_file = "{}/{}/frr_json_initial.conf".format(TMPDIR, rname)
- command = "/usr/lib/frr/frr-reload.py --test --test-reset --input {} {} > {}".format(
- run_cfg_file, init_cfg_file, dname
+ procs[rname] = router_list[rname].popen(
+ ["/usr/bin/env", "vtysh", "-f", delta_fmt.format(rname, gen)],
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
)
- result = call(command, shell=True, stderr=SUB_STDOUT, stdout=SUB_PIPE)
+ for rname, p in procs.items():
+ output, _ = p.communicate()
+ vtysh_command = "vtysh -f {}".format(delta_fmt.format(rname, gen))
+ if not p.returncode:
+ router_list[rname].logger.info(
+ '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
+ vtysh_command, output
+ )
+ )
+ else:
+ router_list[rname].logger.warning(
+ '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
+ vtysh_command, output
+ )
+ )
+ logger.error(
+ "Delta file apply for %s failed %d: %s", rname, p.returncode, output
+ )
- # Assert if command fail
- if result > 0:
- logger.error("Delta file creation failed. Command executed %s", command)
- with open(run_cfg_file, "r") as fd:
- logger.info(
- "Running configuration saved in %s is:\n%s", run_cfg_file, fd.read()
+ # We really need to enable this failure; however, currently frr-reload.py
+ # producing invalid "no" commands as it just preprends "no", but some of the
+ # command forms lack matching values (e.g., final values). Until frr-reload
+ # is fixed to handle this (or all the CLI no forms are adjusted) we can't
+ # fail tests.
+ # raise InvalidCLIError("frr-reload error for {}: {}".format(rname, output))
+
+ #
+ # Optionally log all new running config if "show_router_config" is defined in
+ # "pytest.ini"
+ #
+ if show_router_config:
+ procs = {}
+ for rname in router_list:
+ logger.info("Fetching running config for router %s", rname)
+ procs[rname] = router_list[rname].popen(
+ ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+ for rname, p in procs.items():
+ output, _ = p.communicate()
+ if p.returncode:
+ logger.warning(
+ "Get running config for %s failed %d: %s",
+ rname,
+ p.returncode,
+ output,
)
- with open(init_cfg_file, "r") as fd:
+ else:
logger.info(
- "Test configuration saved in %s is:\n%s", init_cfg_file, fd.read()
+ "Configuration on router %s after reset:\n%s", rname, output
)
- err_cmd = ["/usr/bin/vtysh", "-m", "-f", run_cfg_file]
- result = Popen(err_cmd, stdout=SUB_PIPE, stderr=SUB_PIPE)
- output = result.communicate()
- for out_data in output:
- temp_data = out_data.decode("utf-8").lower()
- for out_err in ERROR_LIST:
- if out_err.lower() in temp_data:
- logger.error(
- "Found errors while validating data in" " %s", run_cfg_file
- )
- raise InvalidCLIError(out_data)
- raise InvalidCLIError("Unknown error in %s", output)
-
- delta = StringIO()
- with open(dname, "r") as f:
- delta.write(f.read())
-
- output = router.vtysh_multicmd(delta.getvalue(), pretty_output=False)
-
- delta.close()
- delta = StringIO()
- cfg = router.run("vtysh -c 'show running'")
- for line in cfg.split("\n"):
- line = line.strip()
- delta.write(line)
- delta.write("\n")
-
- # Router current configuration to log file or console if
- # "show_router_config" is defined in "pytest.ini"
- if show_router_config:
- logger.info("Configuration on router {} after reset:".format(rname))
- logger.info(delta.getvalue())
- delta.close()
-
logger.debug("Exiting API: reset_config_on_routers")
return True
-def load_config_to_router(tgen, routerName, save_bkup=False):
+def prep_load_config_to_routers(tgen, *config_name_list):
+ """Create common config for `load_config_to_routers`.
+
+ The common config file is constructed from the list of sub-config files passed as
+ position arguments to this function. Each entry in `config_name_list` is looked for
+ under the router sub-directory in the test directory and those files are
+ concatenated together to create the common config. e.g.,
+
+ # Routers are "r1" and "r2", test file is `example/test_example_foo.py`
+ prepare_load_config_to_routers(tgen, "bgpd.conf", "ospfd.conf")
+
+ When the above call is made the files in
+
+ example/r1/bgpd.conf
+ example/r1/ospfd.conf
+
+ Are concat'd together into a single config file that will be loaded on r1, and
+
+ example/r2/bgpd.conf
+ example/r2/ospfd.conf
+
+ Are concat'd together into a single config file that will be loaded on r2 when
+ the call to `load_config_to_routers` is made.
"""
- Loads configuration on router from the file FRRCFG_FILE.
+
+ routers = tgen.routers()
+ for rname, router in routers.items():
+ destname = "{}/{}/{}".format(tgen.logdir, rname, FRRCFG_FILE)
+ wmode = "w"
+ for cfbase in config_name_list:
+ script_dir = os.environ["PYTEST_TOPOTEST_SCRIPTDIR"]
+ confname = os.path.join(script_dir, "{}/{}".format(rname, cfbase))
+ with open(confname, "r") as cf:
+ with open(destname, wmode) as df:
+ df.write(cf.read())
+ wmode = "a"
+
+
+def load_config_to_routers(tgen, routers, save_bkup=False):
+ """
+ Loads configuration on routers from the file FRRCFG_FILE.
Parameters
----------
* `tgen` : Topogen object
- * `routerName` : router for which configuration to be loaded
+ * `routers` : routers for which configuration is to be loaded
* `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
+ Returns
+ -------
+ True or False
"""
- logger.debug("Entering API: load_config_to_router")
+ logger.debug("Entering API: load_config_to_routers")
- router_list = tgen.routers()
- for rname in ROUTER_LIST:
- if routerName and rname != routerName:
+ tgen.cfg_gen += 1
+ gen = tgen.cfg_gen
+
+ base_router_list = tgen.routers()
+ router_list = {}
+ for router in routers:
+ if router not in base_router_list:
continue
+ router_list[router] = base_router_list[router]
+
+ frr_cfg_file_fmt = tgen.logdir + "/{}/" + FRRCFG_FILE
+ frr_cfg_save_file_fmt = tgen.logdir + "/{}/{}-" + FRRCFG_FILE
+ frr_cfg_bkup_fmt = tgen.logdir + "/{}/" + FRRCFG_BKUP_FILE
+ procs = {}
+ for rname in router_list:
router = router_list[rname]
try:
- frr_cfg_file = "{}/{}/{}".format(TMPDIR, rname, FRRCFG_FILE)
- frr_cfg_bkup = "{}/{}/{}".format(TMPDIR, rname, FRRCFG_BKUP_FILE)
+ frr_cfg_file = frr_cfg_file_fmt.format(rname)
+ frr_cfg_save_file = frr_cfg_save_file_fmt.format(rname, gen)
+ frr_cfg_bkup = frr_cfg_bkup_fmt.format(rname)
with open(frr_cfg_file, "r+") as cfg:
data = cfg.read()
logger.info(
- "Applying following configuration on router"
- " {}:\n{}".format(rname, data)
+ "Applying following configuration on router %s (gen: %d):\n%s",
+ rname,
+ gen,
+ data,
)
+ # Always save a copy of what we just did
+ with open(frr_cfg_save_file, "w") as bkup:
+ bkup.write(data)
if save_bkup:
with open(frr_cfg_bkup, "w") as bkup:
bkup.write(data)
+ procs[rname] = router_list[rname].popen(
+ ["/usr/bin/env", "vtysh", "-f", frr_cfg_file],
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+ except IOError as err:
+ logger.error(
+ "Unable to open config File. error(%s): %s", err.errno, err.strerror
+ )
+ return False
+ except Exception as error:
+ logger.error("Unable to apply config on %s: %s", rname, str(error))
+ return False
+
+ errors = []
+ for rname, p in procs.items():
+ output, _ = p.communicate()
+ frr_cfg_file = frr_cfg_file_fmt.format(rname)
+ vtysh_command = "vtysh -f " + frr_cfg_file
+ if not p.returncode:
+ router_list[rname].logger.info(
+ '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
+ vtysh_command, output
+ )
+ )
+ else:
+ router_list[rname].logger.error(
+ '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
+ vtysh_command, output
+ )
+ )
+ logger.error(
+ "Config apply for %s failed %d: %s", rname, p.returncode, output
+ )
+ # We can't thorw an exception here as we won't clear the config file.
+ errors.append(
+ InvalidCLIError(
+ "load_config_to_routers error for {}: {}".format(rname, output)
+ )
+ )
- output = router.vtysh_multicmd(data, pretty_output=False)
- for out_err in ERROR_LIST:
- if out_err.lower() in output.lower():
- raise InvalidCLIError("%s" % output)
+ # Empty the config file or we append to it next time through.
+ with open(frr_cfg_file, "r+") as cfg:
+ cfg.truncate(0)
+
+ # Router current configuration to log file or console if
+ # "show_router_config" is defined in "pytest.ini"
+ if show_router_config:
+ procs = {}
+ for rname in router_list:
+ procs[rname] = router_list[rname].popen(
+ ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+ for rname, p in procs.items():
+ output, _ = p.communicate()
+ if p.returncode:
+ logger.warning(
+ "Get running config for %s failed %d: %s",
+ rname,
+ p.returncode,
+ output,
+ )
+ else:
+ logger.info("New configuration for router %s:\n%s", rname, output)
- cfg.truncate(0)
+ logger.debug("Exiting API: load_config_to_routers")
+ return not errors
- except IOError as err:
- errormsg = (
- "Unable to open config File. error(%s):" " %s",
- (err.errno, err.strerror),
- )
- return errormsg
- # Router current configuration to log file or console if
- # "show_router_config" is defined in "pytest.ini"
- if show_router_config:
- logger.info("New configuration for router {}:".format(rname))
- new_config = router.run("vtysh -c 'show running'")
- logger.info(new_config)
+def load_config_to_router(tgen, routerName, save_bkup=False):
+ """
+ Loads configuration on router from the file FRRCFG_FILE.
- logger.debug("Exiting API: load_config_to_router")
- return True
+ Parameters
+ ----------
+ * `tgen` : Topogen object
+ * `routerName` : router for which configuration to be loaded
+ * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
+ """
+ return load_config_to_routers(tgen, [routerName], save_bkup)
+def reset_with_new_configs(tgen, *cflist):
+ """Reset the router to initial config, then load new configs.
+
+ Resets routers to the initial config state (see `save_initial_config_on_routers()
+ and `reset_config_on_routers()` `), then concat list of router sub-configs together
+ and load onto the routers (see `prep_load_config_to_routers()` and
+ `load_config_to_routers()`)
+ """
+ routers = tgen.routers()
+
+ reset_config_on_routers(tgen)
+ prep_load_config_to_routers(tgen, *cflist)
+ load_config_to_routers(tgen, tgen.routers(), save_bkup=False)
+
def get_frr_ipv6_linklocal(tgen, router, intf=None, vrf=None):
"""
@@ -648,37 +888,38 @@ def get_frr_ipv6_linklocal(tgen, router, intf=None, vrf=None):
else:
cmd = "show interface"
for chk_ll in range(0, 60):
- sleep(1/4)
+ sleep(1 / 4)
ifaces = router_list[router].run('vtysh -c "{}"'.format(cmd))
# Fix newlines (make them all the same)
- ifaces = ('\n'.join(ifaces.splitlines()) + '\n').splitlines()
+ ifaces = ("\n".join(ifaces.splitlines()) + "\n").splitlines()
interface = None
ll_per_if_count = 0
for line in ifaces:
# Interface name
- m = re_search('Interface ([a-zA-Z0-9-]+) is', line)
+ m = re_search("Interface ([a-zA-Z0-9-]+) is", line)
if m:
interface = m.group(1).split(" ")[0]
ll_per_if_count = 0
# Interface ip
- m1 = re_search('inet6 (fe80[:a-fA-F0-9]+[\/0-9]+)',
- line)
+ m1 = re_search("inet6 (fe80[:a-fA-F0-9]+/[0-9]+)", line)
if m1:
local = m1.group(1)
ll_per_if_count += 1
if ll_per_if_count > 1:
- linklocal += [["%s-%s" %
- (interface, ll_per_if_count), local]]
+ linklocal += [["%s-%s" % (interface, ll_per_if_count), local]]
else:
linklocal += [[interface, local]]
try:
if linklocal:
if intf:
- return [_linklocal[1] for _linklocal in linklocal if _linklocal[0]==intf][0].\
- split("/")[0]
+ return [
+ _linklocal[1]
+ for _linklocal in linklocal
+ if _linklocal[0] == intf
+ ][0].split("/")[0]
return linklocal
except IndexError:
continue
@@ -696,28 +937,23 @@ def generate_support_bundle():
tgen = get_topogen()
router_list = tgen.routers()
- test_name = os.environ.get('PYTEST_CURRENT_TEST').split(':')[-1].split(' ')[0]
-
- TMPDIR = os.path.join(LOGDIR, tgen.modname)
+ test_name = os.environ.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0]
bundle_procs = {}
for rname, rnode in router_list.items():
logger.info("Spawn collection of support bundle for %s", rname)
- rnode.run("mkdir -p /var/log/frr")
- bundle_procs[rname] = tgen.net[rname].popen(
+ dst_bundle = "{}/{}/support_bundles/{}".format(tgen.logdir, rname, test_name)
+ rnode.run("mkdir -p " + dst_bundle)
+
+ gen_sup_cmd = [
"/usr/lib/frr/generate_support_bundle.py",
- stdin=None,
- stdout=SUB_PIPE,
- stderr=SUB_PIPE,
- )
+ "--log-dir=" + dst_bundle,
+ ]
+ bundle_procs[rname] = tgen.net[rname].popen(gen_sup_cmd, stdin=None)
for rname, rnode in router_list.items():
- dst_bundle = "{}/{}/support_bundles/{}".format(TMPDIR, rname, test_name)
- src_bundle = "/var/log/frr"
-
+ logger.info("Waiting on support bundle for %s", rname)
output, error = bundle_procs[rname].communicate()
-
- logger.info("Saving support bundle for %s", rname)
if output:
logger.info(
"Output from collecting support bundle for %s:\n%s", rname, output
@@ -726,9 +962,6 @@ def generate_support_bundle():
logger.warning(
"Error from collecting support bundle for %s:\n%s", rname, error
)
- rnode.run("rm -rf {}".format(dst_bundle))
- rnode.run("mkdir -p {}".format(dst_bundle))
- rnode.run("mv -f {}/* {}".format(src_bundle, dst_bundle))
return True
@@ -740,21 +973,19 @@ def start_topology(tgen, daemon=None):
* `tgen` : topogen object
"""
- global TMPDIR, ROUTER_LIST
# Starting topology
tgen.start_topology()
# Starting daemons
router_list = tgen.routers()
- ROUTER_LIST = sorted(
+ routers_sorted = sorted(
router_list.keys(), key=lambda x: int(re_search("[0-9]+", x).group(0))
)
- TMPDIR = os.path.join(LOGDIR, tgen.modname)
linux_ver = ""
router_list = tgen.routers()
- for rname in ROUTER_LIST:
+ for rname in routers_sorted:
router = router_list[rname]
# It will help in debugging the failures, will give more details on which
@@ -764,49 +995,51 @@ def start_topology(tgen, daemon=None):
logger.info("Logging platform related details: \n %s \n", linux_ver)
try:
- os.chdir(TMPDIR)
-
- # Creating router named dir and empty zebra.conf bgpd.conf files
- # inside the current directory
- if os.path.isdir("{}".format(rname)):
- os.system("rm -rf {}".format(rname))
- os.mkdir("{}".format(rname))
- os.system("chmod -R go+rw {}".format(rname))
- os.chdir("{}/{}".format(TMPDIR, rname))
- os.system("touch zebra.conf bgpd.conf")
- else:
- os.mkdir("{}".format(rname))
- os.system("chmod -R go+rw {}".format(rname))
- os.chdir("{}/{}".format(TMPDIR, rname))
- os.system("touch zebra.conf bgpd.conf")
+ os.chdir(tgen.logdir)
+
+ # # Creating router named dir and empty zebra.conf bgpd.conf files
+ # # inside the current directory
+ # if os.path.isdir("{}".format(rname)):
+ # os.system("rm -rf {}".format(rname))
+ # os.mkdir("{}".format(rname))
+ # os.system("chmod -R go+rw {}".format(rname))
+ # os.chdir("{}/{}".format(tgen.logdir, rname))
+ # os.system("touch zebra.conf bgpd.conf")
+ # else:
+ # os.mkdir("{}".format(rname))
+ # os.system("chmod -R go+rw {}".format(rname))
+ # os.chdir("{}/{}".format(tgen.logdir, rname))
+ # os.system("touch zebra.conf bgpd.conf")
except IOError as err:
logger.error("I/O error({0}): {1}".format(err.errno, err.strerror))
# Loading empty zebra.conf file to router, to start the zebra daemon
router.load_config(
- TopoRouter.RD_ZEBRA, "{}/{}/zebra.conf".format(TMPDIR, rname)
+ TopoRouter.RD_ZEBRA, "{}/{}/zebra.conf".format(tgen.logdir, rname)
)
# Loading empty bgpd.conf file to router, to start the bgp daemon
- router.load_config(TopoRouter.RD_BGP, "{}/{}/bgpd.conf".format(TMPDIR, rname))
+ router.load_config(
+ TopoRouter.RD_BGP, "{}/{}/bgpd.conf".format(tgen.logdir, rname)
+ )
if daemon and "ospfd" in daemon:
# Loading empty ospf.conf file to router, to start the bgp daemon
router.load_config(
- TopoRouter.RD_OSPF, "{}/{}/ospfd.conf".format(TMPDIR, rname)
+ TopoRouter.RD_OSPF, "{}/{}/ospfd.conf".format(tgen.logdir, rname)
)
if daemon and "ospf6d" in daemon:
# Loading empty ospf.conf file to router, to start the bgp daemon
router.load_config(
- TopoRouter.RD_OSPF6, "{}/{}/ospf6d.conf".format(TMPDIR, rname)
+ TopoRouter.RD_OSPF6, "{}/{}/ospf6d.conf".format(tgen.logdir, rname)
)
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)
+ TopoRouter.RD_PIM, "{}/{}/pimd.conf".format(tgen.logdir, rname)
)
# Starting routers
@@ -881,18 +1114,21 @@ def number_to_column(routerName):
return ord(routerName[0]) - 97
-def topo_daemons(tgen, topo):
+def topo_daemons(tgen, topo=None):
"""
Returns daemon list required for the suite based on topojson.
"""
daemon_list = []
+ if topo is None:
+ topo = tgen.json_topo
+
router_list = tgen.routers()
- ROUTER_LIST = sorted(
+ routers_sorted = sorted(
router_list.keys(), key=lambda x: int(re_search("[0-9]+", x).group(0))
)
- for rtr in ROUTER_LIST:
+ for rtr in routers_sorted:
if "ospf" in topo["routers"][rtr] and "ospfd" not in daemon_list:
daemon_list.append("ospfd")
@@ -937,29 +1173,35 @@ def add_interfaces_to_vlan(tgen, input_dict):
router_list = tgen.routers()
for dut in input_dict.keys():
- rnode = tgen.routers()[dut]
+ rnode = router_list[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)
+ vlan_intf = "{}.{}".format(interface, vlan)
+ cmd = "ip link add link {} name {} type vlan id {}".format(
+ interface, vlan_intf, 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)
+ 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)
+ ifaddr = ipaddress.ip_interface(
+ u"{}/{}".format(
+ frr_unicode(data["ip"]), frr_unicode(data["subnet"])
+ )
+ )
+
+ cmd = "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
+ ifaddr.version, vlan_intf, ifaddr
+ )
logger.info("[DUT: %s]: Running command: %s", dut, cmd)
rnode.run(cmd)
@@ -1004,7 +1246,7 @@ def tcpdump_capture_start(
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
- rnode = tgen.routers()[router]
+ rnode = tgen.gears[router]
if timeout > 0:
cmd = "timeout {}".format(timeout)
@@ -1021,7 +1263,7 @@ def tcpdump_capture_start(
cmdargs += " -s 0 {}".format(str(options))
if cap_file:
- file_name = os.path.join(LOGDIR, tgen.modname, router, cap_file)
+ file_name = os.path.join(tgen.logdir, router, cap_file)
cmdargs += " -w {}".format(str(file_name))
# Remove existing capture file
rnode.run("rm -rf {}".format(file_name))
@@ -1033,7 +1275,9 @@ def tcpdump_capture_start(
if not background:
rnode.run(cmdargs)
else:
- rnode.run("nohup {} & /dev/null 2>&1".format(cmdargs))
+ # XXX this & is bogus doesn't work
+ # rnode.run("nohup {} & /dev/null 2>&1".format(cmdargs))
+ rnode.run("nohup {} > /dev/null 2>&1".format(cmdargs))
# Check if tcpdump process is running
if background:
@@ -1080,7 +1324,7 @@ def tcpdump_capture_stop(tgen, router):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
- rnode = tgen.routers()[router]
+ rnode = tgen.gears[router]
# Check if tcpdump process is running
result = rnode.run("ps -ef | grep tcpdump")
@@ -1090,6 +1334,7 @@ def tcpdump_capture_stop(tgen, router):
errormsg = "tcpdump is not running {}".format("tcpdump")
return errormsg
else:
+ # XXX this doesn't work with micronet
ppid = tgen.net.nameToNode[rnode.name].pid
rnode.run("set +m; pkill -P %s tcpdump &> /dev/null" % ppid)
logger.info("Stopped tcpdump capture")
@@ -1137,6 +1382,8 @@ def create_debug_log_config(tgen, input_dict, build=False):
result = False
try:
+ debug_config_dict = {}
+
for router in input_dict.keys():
debug_config = []
if "debug" in input_dict[router]:
@@ -1147,7 +1394,7 @@ def create_debug_log_config(tgen, input_dict, build=False):
log_file = debug_dict.setdefault("log_file", None)
if log_file:
- _log_file = os.path.join(LOGDIR, tgen.modname, log_file)
+ _log_file = os.path.join(tgen.logdir, log_file)
debug_config.append("log file {} \n".format(_log_file))
if type(enable_logs) is list:
@@ -1167,10 +1414,12 @@ def create_debug_log_config(tgen, input_dict, build=False):
for daemon, debug_logs in disable_logs.items():
for debug_log in debug_logs:
debug_config.append("no {}".format(debug_log))
+ if debug_config:
+ debug_config_dict[router] = debug_config
- result = create_common_configuration(
- tgen, router, debug_config, "debug_log_config", build=build
- )
+ result = create_common_configurations(
+ tgen, debug_config_dict, "debug_log_config", build=build
+ )
except InvalidCLIError:
# Traceback
errormsg = traceback.format_exc()
@@ -1248,11 +1497,13 @@ def create_vrf_cfg(tgen, topo, input_dict=None, build=False):
input_dict = deepcopy(input_dict)
try:
+ config_data_dict = {}
+
for c_router, c_data in input_dict.items():
- rnode = tgen.routers()[c_router]
+ rnode = tgen.gears[c_router]
+ config_data = []
if "vrfs" in c_data:
for vrf in c_data["vrfs"]:
- config_data = []
del_action = vrf.setdefault("delete", False)
name = vrf.setdefault("name", None)
table_id = vrf.setdefault("id", None)
@@ -1329,9 +1580,12 @@ def create_vrf_cfg(tgen, topo, input_dict=None, build=False):
cmd = "no vni {}".format(del_vni)
config_data.append(cmd)
- result = create_common_configuration(
- tgen, c_router, config_data, "vrf", build=build
- )
+ if config_data:
+ config_data_dict[c_router] = config_data
+
+ result = create_common_configurations(
+ tgen, config_data_dict, "vrf", build=build
+ )
except InvalidCLIError:
# Traceback
@@ -1361,18 +1615,22 @@ def create_interface_in_kernel(
to create
"""
- rnode = tgen.routers()[dut]
+ rnode = tgen.gears[dut]
if create:
- cmd = "sudo ip link add name {} type dummy".format(name)
+ cmd = "ip link show {0} >/dev/null || ip link add {0} type dummy".format(name)
rnode.run(cmd)
- addr_type = validate_ip_address(ip_addr)
- if addr_type == "ipv4":
- cmd = "ifconfig {} {} netmask {}".format(name, ip_addr, netmask)
+ if not netmask:
+ ifaddr = ipaddress.ip_interface(frr_unicode(ip_addr))
else:
- cmd = "ifconfig {} inet6 add {}/{}".format(name, ip_addr, netmask)
-
+ ifaddr = ipaddress.ip_interface(
+ u"{}/{}".format(frr_unicode(ip_addr), frr_unicode(netmask))
+ )
+ cmd = "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
+ ifaddr.version, name, ifaddr
+ )
+ logger.info("[DUT: %s]: Running command: %s", dut, cmd)
rnode.run(cmd)
if vrf:
@@ -1394,7 +1652,7 @@ def shutdown_bringup_interface_in_kernel(tgen, dut, intf_name, ifaceaction=False
ineterface
"""
- rnode = tgen.routers()[dut]
+ rnode = tgen.gears[dut]
cmd = "ip link set dev"
if ifaceaction:
@@ -1560,7 +1818,7 @@ def find_interface_with_greater_ip(topo, router, loopback=True, interface=True):
def write_test_header(tc_name):
- """ Display message at beginning of test case"""
+ """Display message at beginning of test case"""
count = 20
logger.info("*" * (len(tc_name) + count))
step("START -> Testcase : %s" % tc_name, reset=True)
@@ -1568,7 +1826,7 @@ def write_test_header(tc_name):
def write_test_footer(tc_name):
- """ Display message at end of test case"""
+ """Display message at end of test case"""
count = 21
logger.info("=" * (len(tc_name) + count))
logger.info("Testcase : %s -> PASSED", tc_name)
@@ -1596,17 +1854,20 @@ def interface_status(tgen, topo, input_dict):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
try:
- global frr_cfg
+ rlist = []
+
for router in input_dict.keys():
interface_list = input_dict[router]["interface_list"]
status = input_dict[router].setdefault("status", "up")
for intf in interface_list:
- rnode = tgen.routers()[router]
+ rnode = tgen.gears[router]
interface_set_status(rnode, intf, status)
- # Load config to router
- load_config_to_router(tgen, router)
+ rlist.append(router)
+
+ # Load config to routers
+ load_config_to_routers(tgen, rlist)
except Exception as e:
errormsg = traceback.format_exc()
@@ -1649,7 +1910,9 @@ def retry(retry_timeout, initial_wait=0, expected=True, diag_pct=0.75):
_diag_pct = kwargs.pop("diag_pct", diag_pct)
start_time = datetime.now()
- retry_until = datetime.now() + timedelta(seconds=_retry_timeout + _initial_wait)
+ retry_until = datetime.now() + timedelta(
+ seconds=_retry_timeout + _initial_wait
+ )
if initial_wait > 0:
logger.info("Waiting for [%s]s as initial delay", initial_wait)
@@ -1670,10 +1933,13 @@ def retry(retry_timeout, initial_wait=0, expected=True, diag_pct=0.75):
# Positive result, but happened after timeout failure, very important to
# note for fixing tests.
- logger.warning("RETRY DIAGNOSTIC: SUCCEED after FAILED with requested timeout of %.1fs; however, succeeded in %.1fs, investigate timeout timing",
- _retry_timeout, (datetime.now() - start_time).total_seconds())
+ logger.warning(
+ "RETRY DIAGNOSTIC: SUCCEED after FAILED with requested timeout of %.1fs; however, succeeded in %.1fs, investigate timeout timing",
+ _retry_timeout,
+ (datetime.now() - start_time).total_seconds(),
+ )
if isinstance(saved_failure, Exception):
- raise saved_failure # pylint: disable=E0702
+ raise saved_failure # pylint: disable=E0702
return saved_failure
except Exception as error:
@@ -1681,16 +1947,20 @@ def retry(retry_timeout, initial_wait=0, expected=True, diag_pct=0.75):
ret = error
if seconds_left < 0 and saved_failure:
- logger.info("RETRY DIAGNOSTIC: Retry timeout reached, still failing")
+ logger.info(
+ "RETRY DIAGNOSTIC: Retry timeout reached, still failing"
+ )
if isinstance(saved_failure, Exception):
- raise saved_failure # pylint: disable=E0702
+ raise saved_failure # pylint: disable=E0702
return saved_failure
if seconds_left < 0:
logger.info("Retry timeout of %ds reached", _retry_timeout)
saved_failure = ret
- retry_extra_delta = timedelta(seconds=seconds_left + _retry_timeout * _diag_pct)
+ retry_extra_delta = timedelta(
+ seconds=seconds_left + _retry_timeout * _diag_pct
+ )
retry_until = datetime.now() + retry_extra_delta
seconds_left = retry_extra_delta.total_seconds()
@@ -1704,11 +1974,17 @@ def retry(retry_timeout, initial_wait=0, expected=True, diag_pct=0.75):
return saved_failure
if saved_failure:
- logger.info("RETRY DIAG: [failure] Sleeping %ds until next retry with %.1f retry time left - too see if timeout was too short",
- retry_sleep, seconds_left)
+ logger.info(
+ "RETRY DIAG: [failure] Sleeping %ds until next retry with %.1f retry time left - too see if timeout was too short",
+ retry_sleep,
+ seconds_left,
+ )
else:
- logger.info("Sleeping %ds until next retry with %.1f retry time left",
- retry_sleep, seconds_left)
+ logger.info(
+ "Sleeping %ds until next retry with %.1f retry time left",
+ retry_sleep,
+ seconds_left,
+ )
sleep(retry_sleep)
func_retry._original = func
@@ -1795,6 +2071,8 @@ def create_interfaces_cfg(tgen, topo, build=False):
topo = deepcopy(topo)
try:
+ interface_data_dict = {}
+
for c_router, c_data in topo.items():
interface_data = []
for destRouterLink, data in sorted(c_data["links"].items()):
@@ -1830,12 +2108,13 @@ def create_interfaces_cfg(tgen, topo, build=False):
interface_data.append("ipv6 address {}".format(intf_addr))
# Wait for vrf interfaces to get link local address once they are up
- if not destRouterLink == 'lo' and 'vrf' in topo[c_router][
- 'links'][destRouterLink]:
- vrf = topo[c_router]['links'][destRouterLink]['vrf']
- intf = topo[c_router]['links'][destRouterLink]['interface']
- ll = get_frr_ipv6_linklocal(tgen, c_router, intf=intf,
- vrf = vrf)
+ if (
+ not destRouterLink == "lo"
+ and "vrf" in topo[c_router]["links"][destRouterLink]
+ ):
+ vrf = topo[c_router]["links"][destRouterLink]["vrf"]
+ intf = topo[c_router]["links"][destRouterLink]["interface"]
+ ll = get_frr_ipv6_linklocal(tgen, c_router, intf=intf, vrf=vrf)
if "ipv6-link-local" in data:
intf_addr = c_data["links"][destRouterLink]["ipv6-link-local"]
@@ -1851,7 +2130,7 @@ def create_interfaces_cfg(tgen, topo, build=False):
"network",
"priority",
"cost",
- "mtu_ignore"
+ "mtu_ignore",
]
if "ospf" in data:
interface_data += _create_interfaces_ospf_cfg(
@@ -1859,12 +2138,14 @@ def create_interfaces_cfg(tgen, topo, build=False):
)
if "ospf6" in data:
interface_data += _create_interfaces_ospf_cfg(
- "ospf6", c_data, data, ospf_keywords
+ "ospf6", c_data, data, ospf_keywords + ["area"]
)
+ if interface_data:
+ interface_data_dict[c_router] = interface_data
- result = create_common_configuration(
- tgen, c_router, interface_data, "interface_config", build=build
- )
+ result = create_common_configurations(
+ tgen, interface_data_dict, "interface_config", build=build
+ )
except InvalidCLIError:
# Traceback
@@ -1923,6 +2204,8 @@ def create_static_routes(tgen, input_dict, build=False):
input_dict = deepcopy(input_dict)
try:
+ static_routes_list_dict = {}
+
for router in input_dict.keys():
if "static_routes" not in input_dict[router]:
errormsg = "static_routes not present in input_dict"
@@ -1978,9 +2261,12 @@ def create_static_routes(tgen, input_dict, build=False):
static_routes_list.append(cmd)
- result = create_common_configuration(
- tgen, router, static_routes_list, "static_route", build=build
- )
+ if static_routes_list:
+ static_routes_list_dict[router] = static_routes_list
+
+ result = create_common_configurations(
+ tgen, static_routes_list_dict, "static_route", build=build
+ )
except InvalidCLIError:
# Traceback
@@ -2037,6 +2323,8 @@ def create_prefix_lists(tgen, input_dict, build=False):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
result = False
try:
+ config_data_dict = {}
+
for router in input_dict.keys():
if "prefix_lists" not in input_dict[router]:
errormsg = "prefix_lists not present in input_dict"
@@ -2083,9 +2371,12 @@ def create_prefix_lists(tgen, input_dict, build=False):
cmd = "no {}".format(cmd)
config_data.append(cmd)
- result = create_common_configuration(
- tgen, router, config_data, "prefix_list", build=build
- )
+ if config_data:
+ config_data_dict[router] = config_data
+
+ result = create_common_configurations(
+ tgen, config_data_dict, "prefix_list", build=build
+ )
except InvalidCLIError:
# Traceback
@@ -2181,6 +2472,8 @@ def create_route_maps(tgen, input_dict, build=False):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
input_dict = deepcopy(input_dict)
try:
+ rmap_data_dict = {}
+
for router in input_dict.keys():
if "route_maps" not in input_dict[router]:
logger.debug("route_maps not present in input_dict")
@@ -2259,6 +2552,7 @@ def create_route_maps(tgen, input_dict, build=False):
nexthop = set_data.setdefault("nexthop", None)
origin = set_data.setdefault("origin", None)
ext_comm_list = set_data.setdefault("extcommunity", {})
+ metrictype = set_data.setdefault("metric-type", {})
# Local Preference
if local_preference:
@@ -2266,6 +2560,10 @@ def create_route_maps(tgen, input_dict, build=False):
"set local-preference {}".format(local_preference)
)
+ # Metric-Type
+ if metrictype:
+ rmap_data.append("set metric-type {}\n".format(metrictype))
+
# Metric
if metric:
rmap_data.append("set metric {} \n".format(metric))
@@ -2458,9 +2756,12 @@ def create_route_maps(tgen, input_dict, build=False):
cmd = "match metric {}".format(metric)
rmap_data.append(cmd)
- result = create_common_configuration(
- tgen, router, rmap_data, "route_maps", build=build
- )
+ if rmap_data:
+ rmap_data_dict[router] = rmap_data
+
+ result = create_common_configurations(
+ tgen, rmap_data_dict, "route_maps", build=build
+ )
except InvalidCLIError:
# Traceback
@@ -2535,6 +2836,8 @@ def create_bgp_community_lists(tgen, input_dict, build=False):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
input_dict = deepcopy(input_dict)
try:
+ config_data_dict = {}
+
for router in input_dict.keys():
if "bgp_community_lists" not in input_dict[router]:
errormsg = "bgp_community_lists not present in input_dict"
@@ -2571,9 +2874,12 @@ def create_bgp_community_lists(tgen, input_dict, build=False):
config_data.append(cmd)
- result = create_common_configuration(
- tgen, router, config_data, "bgp_community_list", build=build
- )
+ if config_data:
+ config_data_dict[router] = config_data
+
+ result = create_common_configurations(
+ tgen, config_data_dict, "bgp_community_list", build=build
+ )
except InvalidCLIError:
# Traceback
@@ -2636,7 +2942,7 @@ def addKernelRoute(
logger.debug("Entering lib API: addKernelRoute()")
- rnode = tgen.routers()[router]
+ rnode = tgen.gears[router]
if type(group_addr_range) is not list:
group_addr_range = [group_addr_range]
@@ -2671,6 +2977,8 @@ def addKernelRoute(
ip, mask = grp_addr.split("/")
if mask == "32" or mask == "128":
grp_addr = ip
+ else:
+ mask = "32" if addr_type == "ipv4" else "128"
if not re_search(r"{}".format(grp_addr), result) and mask != "0":
errormsg = (
@@ -2718,7 +3026,7 @@ def configure_vxlan(tgen, input_dict):
router_list = tgen.routers()
for dut in input_dict.keys():
- rnode = tgen.routers()[dut]
+ rnode = router_list[dut]
if "vxlan" in input_dict[dut]:
for vxlan_dict in input_dict[dut]["vxlan"]:
@@ -2817,7 +3125,7 @@ def configure_brctl(tgen, topo, input_dict):
router_list = tgen.routers()
for dut in input_dict.keys():
- rnode = tgen.routers()[dut]
+ rnode = router_list[dut]
if "brctl" in input_dict[dut]:
for brctl_dict in input_dict[dut]["brctl"]:
@@ -2903,10 +3211,10 @@ def configure_interface_mac(tgen, input_dict):
router_list = tgen.routers()
for dut in input_dict.keys():
- rnode = tgen.routers()[dut]
+ rnode = router_list[dut]
for intf, mac in input_dict[dut].items():
- cmd = "ifconfig {} hw ether {}".format(intf, mac)
+ cmd = "ip link set {} address {}".format(intf, mac)
logger.info("[DUT: %s]: Running command: %s", dut, cmd)
try:
@@ -3374,7 +3682,11 @@ def verify_fib_routes(tgen, addr_type, dut, input_dict, next_hop=None):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
router_list = tgen.routers()
+ if dut not in router_list:
+ return
+
for routerInput in input_dict.keys():
+ # XXX replace with router = dut; rnode = router_list[dut]
for router, rnode in router_list.items():
if router != dut:
continue
@@ -3619,11 +3931,11 @@ def verify_admin_distance_for_static_routes(tgen, input_dict):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
+ router_list = tgen.routers()
for router in input_dict.keys():
- if router not in tgen.routers():
+ if router not in router_list:
continue
-
- rnode = tgen.routers()[router]
+ rnode = router_list[router]
for static_route in input_dict[router]["static_routes"]:
addr_type = validate_ip_address(static_route["network"])
@@ -3701,11 +4013,12 @@ def verify_prefix_lists(tgen, input_dict):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
+ router_list = tgen.routers()
for router in input_dict.keys():
- if router not in tgen.routers():
+ if router not in router_list:
continue
- rnode = tgen.routers()[router]
+ rnode = router_list[router]
# Show ip prefix list
show_prefix_list = run_frr_cmd(rnode, "show ip prefix-list")
@@ -3764,11 +4077,12 @@ def verify_route_maps(tgen, input_dict):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
+ router_list = tgen.routers()
for router in input_dict.keys():
- if router not in tgen.routers():
+ if router not in router_list:
continue
- rnode = tgen.routers()[router]
+ rnode = router_list[router]
# Show ip route-map
show_route_maps = rnode.vtysh_cmd("show route-map")
@@ -3817,10 +4131,11 @@ def verify_bgp_community(tgen, addr_type, router, network, input_dict=None):
"""
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
- if router not in tgen.routers():
+ router_list = tgen.routers()
+ if router not in router_list:
return False
- rnode = tgen.routers()[router]
+ rnode = router_list[router]
logger.debug(
"Verifying BGP community attributes on dut %s: for %s " "network %s",
@@ -3947,11 +4262,12 @@ def verify_create_community_list(tgen, input_dict):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
+ router_list = tgen.routers()
for router in input_dict.keys():
- if router not in tgen.routers():
+ if router not in router_list:
continue
- rnode = tgen.routers()[router]
+ rnode = router_list[router]
logger.info("Verifying large-community is created for dut %s:", router)
@@ -4002,7 +4318,7 @@ def verify_cli_json(tgen, input_dict):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
for dut in input_dict.keys():
- rnode = tgen.routers()[dut]
+ rnode = tgen.gears[dut]
for cli in input_dict[dut]["cli"]:
logger.info(
@@ -4064,7 +4380,7 @@ def verify_evpn_vni(tgen, input_dict):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
for dut in input_dict.keys():
- rnode = tgen.routers()[dut]
+ rnode = tgen.gears[dut]
logger.info("[DUT: %s]: Verifying evpn vni details :", dut)
@@ -4182,7 +4498,7 @@ def verify_vrf_vni(tgen, input_dict):
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
for dut in input_dict.keys():
- rnode = tgen.routers()[dut]
+ rnode = tgen.gears[dut]
logger.info("[DUT: %s]: Verifying vrf vni details :", dut)
@@ -4286,216 +4602,275 @@ def required_linux_kernel_version(required_version):
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 <host>, 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
- """
+class HostApplicationHelper(object):
+ """Helper to track and cleanup per-host based test processes."""
- logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
+ def __init__(self, tgen=None, base_cmd=None):
+ self.base_cmd_str = ""
+ self.host_procs = {}
+ self.tgen = None
+ self.set_base_cmd(base_cmd if base_cmd else [])
+ if tgen is not None:
+ self.init(tgen)
- rnode = tgen.routers()[server]
+ def __enter__(self):
+ self.init()
+ return self
- iperfArgs = "iperf -s "
+ def __exit__(self, type, value, traceback):
+ self.cleanup()
- # UDP/TCP
- if l4Type == "UDP":
- iperfArgs += "-u "
+ def __str__(self):
+ return "HostApplicationHelper({})".format(self.base_cmd_str)
- 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)
+ def set_base_cmd(self, base_cmd):
+ assert isinstance(base_cmd, list) or isinstance(base_cmd, tuple)
+ self.base_cmd = base_cmd
+ if base_cmd:
+ self.base_cmd_str = " ".join(base_cmd)
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 <host>, 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 "
+ self.base_cmd_str = ""
- iperfCmd = iperfArgs
- # Group address range to cover
- if bindToAddress:
- if type(bindToAddress) is not list:
- Address = []
- start = ipaddress.IPv4Address(frr_unicode(bindToAddress))
+ def init(self, tgen=None):
+ """Initialize the helper with tgen if needed.
- 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
+ If overridden, need to handle multiple entries but one init. Will be called on
+ object creation if tgen is supplied. Will be called again on __enter__ so should
+ not re-init if already inited.
+ """
+ if self.tgen:
+ assert tgen is None or self.tgen == tgen
+ else:
+ self.tgen = tgen
- # Time
- if time:
- iperfArgs += "-t %d " % time
+ def started_proc(self, host, p):
+ """Called after process started on host.
- iperfArgs += " &>/dev/null &"
+ Return value is passed to `stopping_proc` method."""
+ logger.debug("%s: Doing nothing after starting process", self)
+ return False
- # 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))
+ def stopping_proc(self, host, p, info):
+ """Called after process started on host."""
+ logger.debug("%s: Doing nothing before stopping process", self)
+
+ def _add_host_proc(self, host, p):
+ v = self.started_proc(host, p)
+
+ if host not in self.host_procs:
+ self.host_procs[host] = []
+ logger.debug("%s: %s: tracking process %s", self, host, p)
+ self.host_procs[host].append((p, v))
+
+ def stop_host(self, host):
+ """Stop the process on the host.
+
+ Override to do additional cleanup."""
+ if host in self.host_procs:
+ hlogger = self.tgen.net[host].logger
+ for p, v in self.host_procs[host]:
+ self.stopping_proc(host, p, v)
+ logger.debug("%s: %s: terminating process %s", self, host, p.pid)
+ hlogger.debug("%s: %s: terminating process %s", self, host, p.pid)
+ rc = p.poll()
+ if rc is not None:
+ logger.error(
+ "%s: %s: process early exit %s: %s",
+ self,
+ host,
+ p.pid,
+ comm_error(p),
+ )
+ hlogger.error(
+ "%s: %s: process early exit %s: %s",
+ self,
+ host,
+ p.pid,
+ comm_error(p),
+ )
+ else:
+ p.terminate()
+ p.wait()
+ logger.debug(
+ "%s: %s: terminated process %s: %s",
+ self,
+ host,
+ p.pid,
+ comm_error(p),
+ )
+ hlogger.debug(
+ "%s: %s: terminated process %s: %s",
+ self,
+ host,
+ p.pid,
+ comm_error(p),
+ )
- # 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
+ del self.host_procs[host]
- logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
- return True
+ def stop_all_hosts(self):
+ hosts = set(self.host_procs)
+ for host in hosts:
+ self.stop_host(host)
+ def cleanup(self):
+ self.stop_all_hosts()
-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
+ def run(self, host, cmd_args, **kwargs):
+ cmd = list(self.base_cmd)
+ cmd.extend(cmd_args)
+ p = self.tgen.gears[host].popen(cmd, **kwargs)
+ assert p.poll() is None
+ self._add_host_proc(host, p)
+ return p
- Usage
- ----
- kill_iperf(tgen, dut ="i6", action="remove_join")
+ def check_procs(self):
+ """Check that all current processes are running, log errors if not.
- """
+ Returns: List of stopped processes."""
+ procs = []
- logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
+ logger.debug("%s: checking procs on hosts %s", self, self.host_procs.keys())
- 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)
+ for host in self.host_procs:
+ hlogger = self.tgen.net[host].logger
+ for p, _ in self.host_procs[host]:
+ logger.debug("%s: checking %s proc %s", self, host, p)
+ rc = p.poll()
+ if rc is None:
+ continue
+ logger.error(
+ "%s: %s proc exited: %s", self, host, comm_error(p), exc_info=True
+ )
+ hlogger.error(
+ "%s: %s proc exited: %s", self, host, comm_error(p), exc_info=True
+ )
+ procs.append(p)
+ return procs
+
+
+class IPerfHelper(HostApplicationHelper):
+ def __str__(self):
+ return "IPerfHelper()"
+
+ def run_join(
+ self,
+ host,
+ join_addr,
+ l4Type="UDP",
+ join_interval=1,
+ join_intf=None,
+ join_towards=None,
+ ):
+ """
+ Use iperf to send IGMP join and listen to traffic
+
+ Parameters:
+ -----------
+ * `host`: iperf host from where IGMP join would be sent
+ * `l4Type`: string, one of [ TCP, UDP ]
+ * `join_addr`: multicast address (or addresses) to join to
+ * `join_interval`: seconds between periodic bandwidth reports
+ * `join_intf`: the interface to bind the join to
+ * `join_towards`: router whos interface to bind the join to
+
+ returns: Success (bool)
+ """
+
+ iperf_path = self.tgen.net.get_exec_path("iperf")
+
+ assert join_addr
+ if not isinstance(join_addr, list) and not isinstance(join_addr, tuple):
+ join_addr = [ipaddress.IPv4Address(frr_unicode(join_addr))]
+
+ for bindTo in join_addr:
+ iperf_args = [iperf_path, "-s"]
+
+ if l4Type == "UDP":
+ iperf_args.append("-u")
+
+ iperf_args.append("-B")
+ if join_towards:
+ to_intf = frr_unicode(
+ self.tgen.json_topo["routers"][host]["links"][join_towards][
+ "interface"
+ ]
+ )
+ iperf_args.append("{}%{}".format(str(bindTo), to_intf))
+ elif join_intf:
+ iperf_args.append("{}%{}".format(str(bindTo), join_intf))
+ else:
+ iperf_args.append(str(bindTo))
+
+ if join_interval:
+ iperf_args.append("-i")
+ iperf_args.append(str(join_interval))
+
+ p = self.run(host, iperf_args)
+ if p.poll() is not None:
+ logger.error("IGMP join failed on %s: %s", bindTo, comm_error(p))
+ return False
+ return True
+
+ def run_traffic(
+ self, host, sentToAddress, ttl, time=0, l4Type="UDP", bind_towards=None
+ ):
+ """
+ Run iperf to send IGMP join and traffic
+
+ Parameters:
+ -----------
+ * `host`: iperf host to send traffic from
+ * `l4Type`: string, one of [ TCP, UDP ]
+ * `sentToAddress`: multicast address to send traffic to
+ * `ttl`: time to live
+ * `time`: time in seconds to transmit for
+ * `bind_towards`: Router who's interface the source ip address is got from
+
+ returns: Success (bool)
+ """
+
+ iperf_path = self.tgen.net.get_exec_path("iperf")
+
+ if sentToAddress and not isinstance(sentToAddress, list):
+ sentToAddress = [ipaddress.IPv4Address(frr_unicode(sentToAddress))]
+
+ for sendTo in sentToAddress:
+ iperf_args = [iperf_path, "-c", sendTo]
+
+ # Bind to Interface IP
+ if bind_towards:
+ ifaddr = frr_unicode(
+ self.tgen.json_topo["routers"][host]["links"][bind_towards]["ipv4"]
+ )
+ ipaddr = ipaddress.IPv4Interface(ifaddr).ip
+ iperf_args.append("-B")
+ iperf_args.append(str(ipaddr))
+
+ # UDP/TCP
+ if l4Type == "UDP":
+ iperf_args.append("-u")
+ iperf_args.append("-b")
+ iperf_args.append("0.012m")
+
+ # TTL
+ if ttl:
+ iperf_args.append("-T")
+ iperf_args.append(str(ttl))
+
+ # Time
+ if time:
+ iperf_args.append("-t")
+ iperf_args.append(str(time))
+
+ p = self.run(host, iperf_args)
+ if p.poll() is not None:
+ logger.error(
+ "mcast traffic send failed for %s: %s", sendTo, comm_error(p)
+ )
+ return False
- logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
+ return True
def verify_ip_nht(tgen, input_dict):
@@ -4528,14 +4903,15 @@ def verify_ip_nht(tgen, input_dict):
logger.debug("Entering lib API: verify_ip_nht()")
+ router_list = tgen.routers()
for router in input_dict.keys():
- if router not in tgen.routers():
+ if router not in router_list:
continue
- rnode = tgen.routers()[router]
+ rnode = router_list[router]
nh_list = input_dict[router]
- if validate_ip_address(nh_list.keys()[0]) is "ipv6":
+ if validate_ip_address(next(iter(nh_list))) == "ipv6":
show_ip_nht = run_frr_cmd(rnode, "show ipv6 nht")
else:
show_ip_nht = run_frr_cmd(rnode, "show ip nht")
@@ -4550,3 +4926,51 @@ def verify_ip_nht(tgen, input_dict):
logger.debug("Exiting lib API: verify_ip_nht()")
return False
+
+
+def scapy_send_raw_packet(tgen, topo, senderRouter, intf, packet=None):
+ """
+ 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
+ * `packet` : packet in raw format
+
+ returns:
+ --------
+ errormsg or True
+ """
+
+ global CD
+ result = ""
+ logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
+ sender_interface = intf
+ rnode = tgen.routers()[senderRouter]
+
+ for destLink, data in topo["routers"][senderRouter]["links"].items():
+ if "type" in data and data["type"] == "loopback":
+ continue
+
+ if not packet:
+ packet = topo["routers"][senderRouter]["pkt"]["test_packets"][packet][
+ "data"
+ ]
+
+ python3_path = tgen.net.get_exec_path(["python3", "python"])
+ script_path = os.path.join(CD, "send_bsr_packet.py")
+ cmd = "{} {} '{}' '{}' --interval=1 --count=1".format(
+ python3_path, script_path, packet, sender_interface
+ )
+
+ logger.info("Scapy cmd: \n %s", cmd)
+ result = rnode.run(cmd)
+
+ if result == "":
+ return result
+
+ logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
+ return True