mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-05-25 02:50:36 +00:00
tests: micronet: update infra
Signed-off-by: Christian Hopps <chopps@labn.net>
This commit is contained in:
parent
6a5433ef0b
commit
4958158787
6
.pylintrc
Normal file
6
.pylintrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[MASTER]
|
||||||
|
init-hook="import sys; sys.path.insert(0, '..')"
|
||||||
|
signature-mutators=common_config.retry,retry
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=I,C,R,W
|
@ -108,10 +108,10 @@ def setup_module(mod):
|
|||||||
for rname, router in router_list.items():
|
for rname, router in router_list.items():
|
||||||
# create VRF rx-bfd-cust1 and link rx-eth0 to rx-bfd-cust1
|
# create VRF rx-bfd-cust1 and link rx-eth0 to rx-bfd-cust1
|
||||||
for cmd in cmds:
|
for cmd in cmds:
|
||||||
output = tgen.net[rname].cmd(cmd.format(rname))
|
output = tgen.net[rname].cmd_raises(cmd.format(rname))
|
||||||
if rname == "r2":
|
if rname == "r2":
|
||||||
for cmd in cmds2:
|
for cmd in cmds2:
|
||||||
output = tgen.net[rname].cmd(cmd.format(rname))
|
output = tgen.net[rname].cmd_raises(cmd.format(rname))
|
||||||
|
|
||||||
for rname, router in router_list.items():
|
for rname, router in router_list.items():
|
||||||
router.load_config(
|
router.load_config(
|
||||||
|
@ -30,6 +30,9 @@ test_evpn_mh.py: Testing EVPN multihoming
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import json
|
import json
|
||||||
import platform
|
import platform
|
||||||
@ -599,18 +602,20 @@ def test_evpn_ead_update():
|
|||||||
def ping_anycast_gw(tgen):
|
def ping_anycast_gw(tgen):
|
||||||
# ping the anycast gw from the local and remote hosts to populate
|
# ping the anycast gw from the local and remote hosts to populate
|
||||||
# the mac address on the PEs
|
# the mac address on the PEs
|
||||||
|
python3_path = tgen.net.get_exec_path(["python3", "python"])
|
||||||
script_path = os.path.abspath(os.path.join(CWD, "../lib/scapy_sendpkt.py"))
|
script_path = os.path.abspath(os.path.join(CWD, "../lib/scapy_sendpkt.py"))
|
||||||
intf = "torbond"
|
intf = "torbond"
|
||||||
ipaddr = "45.0.0.1"
|
ipaddr = "45.0.0.1"
|
||||||
ping_cmd = [
|
ping_cmd = [
|
||||||
|
python3_path,
|
||||||
script_path,
|
script_path,
|
||||||
"--imports=Ether,ARP",
|
"--imports=Ether,ARP",
|
||||||
"--interface=" + intf,
|
"--interface=" + intf,
|
||||||
"'Ether(dst=\"ff:ff:ff:ff:ff:ff\")/ARP(pdst=\"{}\")'".format(ipaddr)
|
'Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="{}")'.format(ipaddr)
|
||||||
]
|
]
|
||||||
for name in ("hostd11", "hostd21"):
|
for name in ("hostd11", "hostd21"):
|
||||||
host = tgen.net[name]
|
host = tgen.net.hosts[name]
|
||||||
stdout = host.cmd(ping_cmd)
|
_, stdout, _ = host.cmd_status(ping_cmd, warn=False, stderr=subprocess.STDOUT)
|
||||||
stdout = stdout.strip()
|
stdout = stdout.strip()
|
||||||
if stdout:
|
if stdout:
|
||||||
host.logger.debug("%s: arping on %s for %s returned: %s", name, intf, ipaddr, stdout)
|
host.logger.debug("%s: arping on %s for %s returned: %s", name, intf, ipaddr, stdout)
|
||||||
|
@ -117,8 +117,6 @@ NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"}
|
|||||||
LOOPBACK_1 = {
|
LOOPBACK_1 = {
|
||||||
"ipv4": "10.0.0.7/24",
|
"ipv4": "10.0.0.7/24",
|
||||||
"ipv6": "fd00:0:0:1::7/64",
|
"ipv6": "fd00:0:0:1::7/64",
|
||||||
"ipv4_mask": "255.255.255.0",
|
|
||||||
"ipv6_mask": None,
|
|
||||||
}
|
}
|
||||||
LOOPBACK_2 = {
|
LOOPBACK_2 = {
|
||||||
"ipv4": "10.0.0.16/24",
|
"ipv4": "10.0.0.16/24",
|
||||||
|
@ -6,15 +6,23 @@ import glob
|
|||||||
import os
|
import os
|
||||||
import pdb
|
import pdb
|
||||||
import re
|
import re
|
||||||
import pytest
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
from lib.topogen import get_topogen, diagnose_env
|
import pytest
|
||||||
from lib.topotest import json_cmp_result
|
import lib.fixtures
|
||||||
from lib.topotest import g_extra_config as topotest_extra_config
|
from lib import topolog
|
||||||
|
from lib.micronet import Commander
|
||||||
|
from lib.micronet_cli import cli
|
||||||
|
from lib.micronet_compat import Mininet, cleanup_current, cleanup_previous
|
||||||
|
from lib.topogen import diagnose_env, get_topogen
|
||||||
from lib.topolog import logger
|
from lib.topolog import logger
|
||||||
|
from lib.topotest import g_extra_config as topotest_extra_config
|
||||||
|
from lib.topotest import json_cmp_result
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
|
|
||||||
leak_check_ok = True
|
leak_check_ok = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
leak_check_ok = False
|
leak_check_ok = False
|
||||||
@ -31,6 +39,12 @@ def pytest_addoption(parser):
|
|||||||
help="Configure address sanitizer to abort process on error",
|
help="Configure address sanitizer to abort process on error",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.addoption(
|
||||||
|
"--cli-on-error",
|
||||||
|
action="store_true",
|
||||||
|
help="Mininet cli on test failure",
|
||||||
|
)
|
||||||
|
|
||||||
parser.addoption(
|
parser.addoption(
|
||||||
"--gdb-breakpoints",
|
"--gdb-breakpoints",
|
||||||
metavar="SYMBOL[,SYMBOL...]",
|
metavar="SYMBOL[,SYMBOL...]",
|
||||||
@ -50,17 +64,28 @@ def pytest_addoption(parser):
|
|||||||
)
|
)
|
||||||
|
|
||||||
parser.addoption(
|
parser.addoption(
|
||||||
"--mininet-on-error",
|
"--pause",
|
||||||
action="store_true",
|
|
||||||
help="Mininet cli on test failure",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.addoption(
|
|
||||||
"--pause-after",
|
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Pause after each test",
|
help="Pause after each test",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.addoption(
|
||||||
|
"--pause-on-error",
|
||||||
|
action="store_true",
|
||||||
|
help="Do not pause after (disables default when --shell or -vtysh given)",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.addoption(
|
||||||
|
"--no-pause-on-error",
|
||||||
|
dest="pause_on_error",
|
||||||
|
action="store_false",
|
||||||
|
help="Do not pause after (disables default when --shell or -vtysh given)",
|
||||||
|
)
|
||||||
|
|
||||||
|
rundir_help="directory for running in and log files"
|
||||||
|
parser.addini("rundir", rundir_help, default="/tmp/topotests")
|
||||||
|
parser.addoption("--rundir", metavar="DIR", help=rundir_help)
|
||||||
|
|
||||||
parser.addoption(
|
parser.addoption(
|
||||||
"--shell",
|
"--shell",
|
||||||
metavar="ROUTER[,ROUTER...]",
|
metavar="ROUTER[,ROUTER...]",
|
||||||
@ -120,7 +145,7 @@ def check_for_memleaks():
|
|||||||
latest = []
|
latest = []
|
||||||
existing = []
|
existing = []
|
||||||
if tgen is not None:
|
if tgen is not None:
|
||||||
logdir = "/tmp/topotests/{}".format(tgen.modname)
|
logdir = tgen.logdir
|
||||||
if hasattr(tgen, "valgrind_existing_files"):
|
if hasattr(tgen, "valgrind_existing_files"):
|
||||||
existing = tgen.valgrind_existing_files
|
existing = tgen.valgrind_existing_files
|
||||||
latest = glob.glob(os.path.join(logdir, "*.valgrind.*"))
|
latest = glob.glob(os.path.join(logdir, "*.valgrind.*"))
|
||||||
@ -132,7 +157,7 @@ def check_for_memleaks():
|
|||||||
vfcontent = vf.read()
|
vfcontent = vf.read()
|
||||||
match = re.search(r"ERROR SUMMARY: (\d+) errors", vfcontent)
|
match = re.search(r"ERROR SUMMARY: (\d+) errors", vfcontent)
|
||||||
if match and match.group(1) != "0":
|
if match and match.group(1) != "0":
|
||||||
emsg = '{} in {}'.format(match.group(1), vfile)
|
emsg = "{} in {}".format(match.group(1), vfile)
|
||||||
leaks.append(emsg)
|
leaks.append(emsg)
|
||||||
|
|
||||||
if leaks:
|
if leaks:
|
||||||
@ -142,6 +167,16 @@ def check_for_memleaks():
|
|||||||
logger.error("Memleaks found:\n\t" + "\n\t".join(leaks))
|
logger.error("Memleaks found:\n\t" + "\n\t".join(leaks))
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_runtest_logstart(nodeid, location):
|
||||||
|
# location is (filename, lineno, testname)
|
||||||
|
topolog.logstart(nodeid, location, topotest_extra_config["rundir"])
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_runtest_logfinish(nodeid, location):
|
||||||
|
# location is (filename, lineno, testname)
|
||||||
|
topolog.logfinish(nodeid, location)
|
||||||
|
|
||||||
|
|
||||||
def pytest_runtest_call():
|
def pytest_runtest_call():
|
||||||
"""
|
"""
|
||||||
This function must be run after setup_module(), it does standarized post
|
This function must be run after setup_module(), it does standarized post
|
||||||
@ -151,7 +186,7 @@ def pytest_runtest_call():
|
|||||||
tgen = get_topogen()
|
tgen = get_topogen()
|
||||||
if tgen is not None:
|
if tgen is not None:
|
||||||
# Allow user to play with the setup.
|
# Allow user to play with the setup.
|
||||||
tgen.mininet_cli()
|
tgen.cli()
|
||||||
|
|
||||||
pytest.exit("the topology executed successfully")
|
pytest.exit("the topology executed successfully")
|
||||||
|
|
||||||
@ -176,8 +211,56 @@ def pytest_configure(config):
|
|||||||
Assert that the environment is correctly configured, and get extra config.
|
Assert that the environment is correctly configured, and get extra config.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not diagnose_env():
|
if "PYTEST_XDIST_WORKER" not in os.environ:
|
||||||
pytest.exit("environment has errors, please read the logs")
|
os.environ["PYTEST_XDIST_MODE"] = config.getoption("dist", "no")
|
||||||
|
os.environ["PYTEST_TOPOTEST_WORKER"] = ""
|
||||||
|
is_xdist = os.environ["PYTEST_XDIST_MODE"] != "no"
|
||||||
|
is_worker = False
|
||||||
|
else:
|
||||||
|
os.environ["PYTEST_TOPOTEST_WORKER"] = os.environ["PYTEST_XDIST_WORKER"]
|
||||||
|
is_xdist = True
|
||||||
|
is_worker = True
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Set some defaults for the pytest.ini [pytest] section
|
||||||
|
# ---------------------------------------------------
|
||||||
|
|
||||||
|
rundir = config.getoption("--rundir")
|
||||||
|
if not rundir:
|
||||||
|
rundir = config.getini("rundir")
|
||||||
|
if not rundir:
|
||||||
|
rundir = "/tmp/topotests"
|
||||||
|
if not config.getoption("--junitxml"):
|
||||||
|
config.option.xmlpath = os.path.join(rundir, "topotests.xml")
|
||||||
|
xmlpath = config.option.xmlpath
|
||||||
|
|
||||||
|
# Save an existing topotest.xml
|
||||||
|
if os.path.exists(xmlpath):
|
||||||
|
fmtime = time.localtime(os.path.getmtime(xmlpath))
|
||||||
|
suffix = "-" + time.strftime("%Y%m%d%H%M%S", fmtime)
|
||||||
|
commander = Commander("pytest")
|
||||||
|
mv_path = commander.get_exec_path("mv")
|
||||||
|
commander.cmd_status([mv_path, xmlpath, xmlpath + suffix])
|
||||||
|
|
||||||
|
topotest_extra_config["rundir"] = rundir
|
||||||
|
|
||||||
|
# Set the log_file (exec) to inside the rundir if not specified
|
||||||
|
if not config.getoption("--log-file") and not config.getini("log_file"):
|
||||||
|
config.option.log_file = os.path.join(rundir, "exec.log")
|
||||||
|
|
||||||
|
# Turn on live logging if user specified verbose and the config has a CLI level set
|
||||||
|
if config.getoption("--verbose") and not is_xdist and not config.getini("log_cli"):
|
||||||
|
if config.getoption("--log-cli-level", None) is None:
|
||||||
|
# By setting the CLI option to the ini value it enables log_cli=1
|
||||||
|
cli_level = config.getini("log_cli_level")
|
||||||
|
if cli_level is not None:
|
||||||
|
config.option.log_cli_level = cli_level
|
||||||
|
# ---------------------------------------
|
||||||
|
# Record our options in global dictionary
|
||||||
|
# ---------------------------------------
|
||||||
|
|
||||||
|
topotest_extra_config["rundir"] = rundir
|
||||||
|
|
||||||
asan_abort = config.getoption("--asan-abort")
|
asan_abort = config.getoption("--asan-abort")
|
||||||
topotest_extra_config["asan_abort"] = asan_abort
|
topotest_extra_config["asan_abort"] = asan_abort
|
||||||
@ -194,8 +277,8 @@ def pytest_configure(config):
|
|||||||
gdb_breakpoints = gdb_breakpoints.split(",") if gdb_breakpoints else []
|
gdb_breakpoints = gdb_breakpoints.split(",") if gdb_breakpoints else []
|
||||||
topotest_extra_config["gdb_breakpoints"] = gdb_breakpoints
|
topotest_extra_config["gdb_breakpoints"] = gdb_breakpoints
|
||||||
|
|
||||||
mincli_on_error = config.getoption("--mininet-on-error")
|
cli_on_error = config.getoption("--cli-on-error")
|
||||||
topotest_extra_config["mininet_on_error"] = mincli_on_error
|
topotest_extra_config["cli_on_error"] = cli_on_error
|
||||||
|
|
||||||
shell = config.getoption("--shell")
|
shell = config.getoption("--shell")
|
||||||
topotest_extra_config["shell"] = shell.split(",") if shell else []
|
topotest_extra_config["shell"] = shell.split(",") if shell else []
|
||||||
@ -203,8 +286,6 @@ def pytest_configure(config):
|
|||||||
strace = config.getoption("--strace-daemons")
|
strace = config.getoption("--strace-daemons")
|
||||||
topotest_extra_config["strace_daemons"] = strace.split(",") if strace else []
|
topotest_extra_config["strace_daemons"] = strace.split(",") if strace else []
|
||||||
|
|
||||||
pause_after = config.getoption("--pause-after")
|
|
||||||
|
|
||||||
shell_on_error = config.getoption("--shell-on-error")
|
shell_on_error = config.getoption("--shell-on-error")
|
||||||
topotest_extra_config["shell_on_error"] = shell_on_error
|
topotest_extra_config["shell_on_error"] = shell_on_error
|
||||||
|
|
||||||
@ -217,17 +298,44 @@ def pytest_configure(config):
|
|||||||
vtysh_on_error = config.getoption("--vtysh-on-error")
|
vtysh_on_error = config.getoption("--vtysh-on-error")
|
||||||
topotest_extra_config["vtysh_on_error"] = vtysh_on_error
|
topotest_extra_config["vtysh_on_error"] = vtysh_on_error
|
||||||
|
|
||||||
topotest_extra_config["pause_after"] = pause_after or shell or vtysh
|
pause_on_error = vtysh or shell or config.getoption("--pause-on-error")
|
||||||
|
if config.getoption("--no-pause-on-error"):
|
||||||
|
pause_on_error = False
|
||||||
|
|
||||||
|
topotest_extra_config["pause_on_error"] = pause_on_error
|
||||||
|
topotest_extra_config["pause"] = config.getoption("--pause")
|
||||||
|
|
||||||
topotest_extra_config["topology_only"] = config.getoption("--topology-only")
|
topotest_extra_config["topology_only"] = config.getoption("--topology-only")
|
||||||
|
|
||||||
|
# Check environment now that we have config
|
||||||
|
if not diagnose_env(rundir):
|
||||||
|
pytest.exit("environment has errors, please read the logs")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, scope="session")
|
||||||
|
def setup_session_auto():
|
||||||
|
if "PYTEST_TOPOTEST_WORKER" not in os.environ:
|
||||||
|
is_worker = False
|
||||||
|
elif not os.environ["PYTEST_TOPOTEST_WORKER"]:
|
||||||
|
is_worker = False
|
||||||
|
else:
|
||||||
|
is_worker = True
|
||||||
|
|
||||||
|
logger.debug("Before the run (is_worker: %s)", is_worker)
|
||||||
|
if not is_worker:
|
||||||
|
cleanup_previous()
|
||||||
|
yield
|
||||||
|
if not is_worker:
|
||||||
|
cleanup_current()
|
||||||
|
logger.debug("After the run (is_worker: %s)", is_worker)
|
||||||
|
|
||||||
|
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
"Log all assert messages to default logger with error level"
|
"Log all assert messages to default logger with error level"
|
||||||
|
|
||||||
# Nothing happened
|
# Nothing happened
|
||||||
if call.when == "call":
|
if call.when == "call":
|
||||||
pause = topotest_extra_config["pause_after"]
|
pause = topotest_extra_config["pause"]
|
||||||
else:
|
else:
|
||||||
pause = False
|
pause = False
|
||||||
|
|
||||||
@ -237,6 +345,8 @@ def pytest_runtest_makereport(item, call):
|
|||||||
except:
|
except:
|
||||||
call.excinfo = ExceptionInfo()
|
call.excinfo = ExceptionInfo()
|
||||||
|
|
||||||
|
title='unset'
|
||||||
|
|
||||||
if call.excinfo is None:
|
if call.excinfo is None:
|
||||||
error = False
|
error = False
|
||||||
else:
|
else:
|
||||||
@ -261,11 +371,15 @@ def pytest_runtest_makereport(item, call):
|
|||||||
modname, item.name, call.excinfo.value
|
modname, item.name, call.excinfo.value
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
title = "{}/{}".format(modname, item.name)
|
||||||
|
|
||||||
# We want to pause, if requested, on any error not just test cases
|
# We want to pause, if requested, on any error not just test cases
|
||||||
# (e.g., call.when == "setup")
|
# (e.g., call.when == "setup")
|
||||||
if not pause:
|
if not pause:
|
||||||
pause = topotest_extra_config["pause_after"]
|
pause = (
|
||||||
|
topotest_extra_config["pause_on_error"]
|
||||||
|
or topotest_extra_config["pause"]
|
||||||
|
)
|
||||||
|
|
||||||
# (topogen) Set topology error to avoid advancing in the test.
|
# (topogen) Set topology error to avoid advancing in the test.
|
||||||
tgen = get_topogen()
|
tgen = get_topogen()
|
||||||
@ -273,23 +387,75 @@ def pytest_runtest_makereport(item, call):
|
|||||||
# This will cause topogen to report error on `routers_have_failure`.
|
# This will cause topogen to report error on `routers_have_failure`.
|
||||||
tgen.set_error("{}/{}".format(modname, item.name))
|
tgen.set_error("{}/{}".format(modname, item.name))
|
||||||
|
|
||||||
if error and topotest_extra_config["shell_on_error"]:
|
commander = Commander("pytest")
|
||||||
for router in tgen.routers():
|
isatty = sys.stdout.isatty()
|
||||||
pause = True
|
error_cmd = None
|
||||||
tgen.net[router].runInWindow(os.getenv("SHELL", "bash"))
|
|
||||||
|
|
||||||
if error and topotest_extra_config["vtysh_on_error"]:
|
if error and topotest_extra_config["vtysh_on_error"]:
|
||||||
for router in tgen.routers():
|
error_cmd = commander.get_exec_path(["vtysh"])
|
||||||
|
elif error and topotest_extra_config["shell_on_error"]:
|
||||||
|
error_cmd = os.getenv("SHELL", commander.get_exec_path(["bash"]))
|
||||||
|
|
||||||
|
if error_cmd:
|
||||||
|
# Really would like something better than using this global here.
|
||||||
|
# Not all tests use topogen though so get_topogen() won't work.
|
||||||
|
win_info = None
|
||||||
|
wait_for_channels = []
|
||||||
|
for node in Mininet.g_mnet_inst.hosts.values():
|
||||||
pause = True
|
pause = True
|
||||||
tgen.net[router].runInWindow("vtysh")
|
|
||||||
|
|
||||||
if error and topotest_extra_config["mininet_on_error"]:
|
channel = "{}-{}".format(os.getpid(), Commander.tmux_wait_gen) if not isatty else None
|
||||||
tgen.mininet_cli()
|
Commander.tmux_wait_gen += 1
|
||||||
|
wait_for_channels.append(channel)
|
||||||
|
|
||||||
if pause:
|
pane_info = node.run_in_window(
|
||||||
|
error_cmd,
|
||||||
|
new_window=win_info is None,
|
||||||
|
background=True,
|
||||||
|
title="{} ({})".format(title, node.name),
|
||||||
|
name=title,
|
||||||
|
tmux_target=win_info,
|
||||||
|
wait_for=channel
|
||||||
|
)
|
||||||
|
if win_info is None:
|
||||||
|
win_info = pane_info
|
||||||
|
|
||||||
|
# Now wait on any channels
|
||||||
|
for channel in wait_for_channels:
|
||||||
|
logger.debug("Waiting on TMUX channel %s", channel)
|
||||||
|
commander.cmd_raises([commander.get_exec_path("tmux"), "wait", channel])
|
||||||
|
|
||||||
|
if error and topotest_extra_config["cli_on_error"]:
|
||||||
|
# Really would like something better than using this global here.
|
||||||
|
# Not all tests use topogen though so get_topogen() won't work.
|
||||||
|
if Mininet.g_mnet_inst:
|
||||||
|
cli(Mininet.g_mnet_inst, title=title, background=False)
|
||||||
|
else:
|
||||||
|
logger.error("Could not launch CLI b/c no mininet exists yet")
|
||||||
|
|
||||||
|
while pause and isatty:
|
||||||
try:
|
try:
|
||||||
user = raw_input('Testing paused, "pdb" to debug, "Enter" to continue: ')
|
user = raw_input(
|
||||||
|
'PAUSED, "cli" for CLI, "pdb" to debug, "Enter" to continue: '
|
||||||
|
)
|
||||||
except NameError:
|
except NameError:
|
||||||
user = input('Testing paused, "pdb" to debug, "Enter" to continue: ')
|
user = input(
|
||||||
if user.strip() == "pdb":
|
'PAUSED, "cli" for CLI, "pdb" to debug, "Enter" to continue: '
|
||||||
|
)
|
||||||
|
user = user.strip()
|
||||||
|
|
||||||
|
if user == "cli":
|
||||||
|
cli(Mininet.g_mnet_inst)
|
||||||
|
elif user == "pdb":
|
||||||
pdb.set_trace()
|
pdb.set_trace()
|
||||||
|
elif user:
|
||||||
|
print('Unrecognized input: "%s"' % user)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add common fixtures available to all tests as parameters
|
||||||
|
#
|
||||||
|
tgen = pytest.fixture(lib.fixtures.tgen)
|
||||||
|
topo = pytest.fixture(lib.fixtures.topo)
|
||||||
|
@ -18,40 +18,39 @@
|
|||||||
# OF THIS SOFTWARE.
|
# OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
from time import sleep
|
|
||||||
import traceback
|
|
||||||
import ipaddr
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from lib import topotest
|
import traceback
|
||||||
from lib.topolog import logger
|
from copy import deepcopy
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
from lib.topogen import TopoRouter, get_topogen
|
import ipaddr
|
||||||
from lib.topotest import frr_unicode
|
|
||||||
|
|
||||||
# Import common_config to use commomnly used APIs
|
# Import common_config to use commomnly used APIs
|
||||||
from lib.common_config import (
|
from lib.common_config import (
|
||||||
create_common_configurations,
|
create_common_configurations,
|
||||||
InvalidCLIError,
|
|
||||||
load_config_to_router,
|
|
||||||
check_address_types,
|
|
||||||
generate_ips,
|
|
||||||
validate_ip_address,
|
|
||||||
find_interface_with_greater_ip,
|
|
||||||
run_frr_cmd,
|
|
||||||
FRRCFG_FILE,
|
FRRCFG_FILE,
|
||||||
retry,
|
InvalidCLIError,
|
||||||
|
check_address_types,
|
||||||
|
create_common_configuration,
|
||||||
|
find_interface_with_greater_ip,
|
||||||
|
generate_ips,
|
||||||
|
get_frr_ipv6_linklocal,
|
||||||
get_ipv6_linklocal_address,
|
get_ipv6_linklocal_address,
|
||||||
get_frr_ipv6_linklocal
|
load_config_to_router,
|
||||||
|
retry,
|
||||||
|
run_frr_cmd,
|
||||||
|
validate_ip_address,
|
||||||
)
|
)
|
||||||
|
from lib.topogen import TopoRouter, get_topogen
|
||||||
|
from lib.topolog import logger
|
||||||
|
from lib.topotest import frr_unicode
|
||||||
|
|
||||||
LOGDIR = "/tmp/topotests/"
|
from lib import topotest
|
||||||
TMPDIR = None
|
|
||||||
|
|
||||||
|
|
||||||
def create_router_bgp(tgen, topo, input_dict=None, build=False, load_config=True):
|
def create_router_bgp(tgen, topo=None, input_dict=None, build=False, load_config=True):
|
||||||
"""
|
"""
|
||||||
API to configure bgp on router
|
API to configure bgp on router
|
||||||
|
|
||||||
@ -139,6 +138,9 @@ def create_router_bgp(tgen, topo, input_dict=None, build=False, load_config=True
|
|||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
# Flag is used when testing ipv6 over ipv4 or vice-versa
|
# Flag is used when testing ipv6 over ipv4 or vice-versa
|
||||||
afi_test = False
|
afi_test = False
|
||||||
|
|
||||||
@ -1096,9 +1098,6 @@ def modify_bgp_config_when_bgpd_down(tgen, topo, input_dict):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
try:
|
try:
|
||||||
|
|
||||||
global LOGDIR
|
|
||||||
|
|
||||||
result = create_router_bgp(
|
result = create_router_bgp(
|
||||||
tgen, topo, input_dict, build=False, load_config=False
|
tgen, topo, input_dict, build=False, load_config=False
|
||||||
)
|
)
|
||||||
@ -1112,13 +1111,10 @@ def modify_bgp_config_when_bgpd_down(tgen, topo, input_dict):
|
|||||||
if router != dut:
|
if router != dut:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
TMPDIR = os.path.join(LOGDIR, tgen.modname)
|
|
||||||
|
|
||||||
logger.info("Delete BGP config when BGPd is down in {}".format(router))
|
logger.info("Delete BGP config when BGPd is down in {}".format(router))
|
||||||
# Reading the config from /tmp/topotests and
|
# Reading the config from "rundir" and copy to /etc/frr/bgpd.conf
|
||||||
# copy to /etc/frr/bgpd.conf
|
|
||||||
cmd = "cat {}/{}/{} >> /etc/frr/bgpd.conf".format(
|
cmd = "cat {}/{}/{} >> /etc/frr/bgpd.conf".format(
|
||||||
TMPDIR, router, FRRCFG_FILE
|
tgen.logdir, router, FRRCFG_FILE
|
||||||
)
|
)
|
||||||
router_list[router].run(cmd)
|
router_list[router].run(cmd)
|
||||||
|
|
||||||
@ -1207,7 +1203,7 @@ def verify_router_id(tgen, topo, input_dict, expected=True):
|
|||||||
|
|
||||||
|
|
||||||
@retry(retry_timeout=150)
|
@retry(retry_timeout=150)
|
||||||
def verify_bgp_convergence(tgen, topo, dut=None, expected=True):
|
def verify_bgp_convergence(tgen, topo=None, dut=None, expected=True):
|
||||||
"""
|
"""
|
||||||
API will verify if BGP is converged with in the given time frame.
|
API will verify if BGP is converged with in the given time frame.
|
||||||
Running "show bgp summary json" command and verify bgp neighbor
|
Running "show bgp summary json" command and verify bgp neighbor
|
||||||
@ -1230,6 +1226,9 @@ def verify_bgp_convergence(tgen, topo, dut=None, expected=True):
|
|||||||
errormsg(str) or True
|
errormsg(str) or True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
result = False
|
result = False
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
tgen = get_topogen()
|
tgen = get_topogen()
|
||||||
|
@ -18,37 +18,36 @@
|
|||||||
# OF THIS SOFTWARE.
|
# OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from time import sleep
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from re import search as re_search
|
from re import search as re_search
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
from time import sleep
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
import socket
|
|
||||||
import subprocess
|
|
||||||
import ipaddress
|
|
||||||
import platform
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Imports from python2
|
# Imports from python2
|
||||||
from StringIO import StringIO
|
|
||||||
import ConfigParser as configparser
|
import ConfigParser as configparser
|
||||||
|
from StringIO import StringIO
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Imports from python3
|
# Imports from python3
|
||||||
from io import StringIO
|
|
||||||
import configparser
|
import configparser
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
from lib.topolog import logger, logger_config
|
from lib.micronet_compat import Mininet
|
||||||
from lib.topogen import TopoRouter, get_topogen
|
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
|
||||||
|
|
||||||
FRRCFG_FILE = "frr_json.conf"
|
FRRCFG_FILE = "frr_json.conf"
|
||||||
FRRCFG_BKUP_FILE = "frr_json_initial.conf"
|
FRRCFG_BKUP_FILE = "frr_json_initial.conf"
|
||||||
@ -60,13 +59,6 @@ ROUTER_LIST = []
|
|||||||
CD = os.path.dirname(os.path.realpath(__file__))
|
CD = os.path.dirname(os.path.realpath(__file__))
|
||||||
PYTESTINI_PATH = os.path.join(CD, "../pytest.ini")
|
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
|
# NOTE: to save execution logs to log file frrtest_log_dir must be configured
|
||||||
# in `pytest.ini`.
|
# in `pytest.ini`.
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
@ -138,6 +130,9 @@ DEBUG_LOGS = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_iperf_client_procs = {}
|
||||||
|
g_iperf_server_procs = {}
|
||||||
|
|
||||||
def is_string(value):
|
def is_string(value):
|
||||||
try:
|
try:
|
||||||
return isinstance(value, basestring)
|
return isinstance(value, basestring)
|
||||||
@ -146,9 +141,9 @@ def is_string(value):
|
|||||||
|
|
||||||
if config.has_option("topogen", "verbosity"):
|
if config.has_option("topogen", "verbosity"):
|
||||||
loglevel = config.get("topogen", "verbosity")
|
loglevel = config.get("topogen", "verbosity")
|
||||||
loglevel = loglevel.upper()
|
loglevel = loglevel.lower()
|
||||||
else:
|
else:
|
||||||
loglevel = "INFO"
|
loglevel = "info"
|
||||||
|
|
||||||
if config.has_option("topogen", "frrtest_log_dir"):
|
if config.has_option("topogen", "frrtest_log_dir"):
|
||||||
frrtest_log_dir = config.get("topogen", "frrtest_log_dir")
|
frrtest_log_dir = config.get("topogen", "frrtest_log_dir")
|
||||||
@ -157,9 +152,7 @@ if config.has_option("topogen", "frrtest_log_dir"):
|
|||||||
frrtest_log_file = frrtest_log_dir + logfile_name + str(time_stamp)
|
frrtest_log_file = frrtest_log_dir + logfile_name + str(time_stamp)
|
||||||
print("frrtest_log_file..", frrtest_log_file)
|
print("frrtest_log_file..", frrtest_log_file)
|
||||||
|
|
||||||
logger = logger_config.get_logger(
|
logger = get_logger("test_execution_logs", log_level=loglevel, target=frrtest_log_file)
|
||||||
name="test_execution_logs", log_level=loglevel, target=frrtest_log_file
|
|
||||||
)
|
|
||||||
print("Logs will be sent to logfile: {}".format(frrtest_log_file))
|
print("Logs will be sent to logfile: {}".format(frrtest_log_file))
|
||||||
|
|
||||||
if config.has_option("topogen", "show_router_config"):
|
if config.has_option("topogen", "show_router_config"):
|
||||||
@ -284,7 +277,7 @@ def apply_raw_config(tgen, input_dict):
|
|||||||
if not isinstance(config_cmd, list):
|
if not isinstance(config_cmd, list):
|
||||||
config_cmd = [config_cmd]
|
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:
|
with open(frr_cfg_file, "w") as cfg:
|
||||||
for cmd in config_cmd:
|
for cmd in config_cmd:
|
||||||
cfg.write("{}\n".format(cmd))
|
cfg.write("{}\n".format(cmd))
|
||||||
@ -314,7 +307,6 @@ def create_common_configurations(
|
|||||||
-------
|
-------
|
||||||
True or False
|
True or False
|
||||||
"""
|
"""
|
||||||
TMPDIR = os.path.join(LOGDIR, tgen.modname)
|
|
||||||
|
|
||||||
config_map = OrderedDict(
|
config_map = OrderedDict(
|
||||||
{
|
{
|
||||||
@ -342,7 +334,7 @@ def create_common_configurations(
|
|||||||
|
|
||||||
routers = config_dict.keys()
|
routers = config_dict.keys()
|
||||||
for router in routers:
|
for router in routers:
|
||||||
fname = "{}/{}/{}".format(TMPDIR, router, FRRCFG_FILE)
|
fname = "{}/{}/{}".format(tgen.logdir, router, FRRCFG_FILE)
|
||||||
try:
|
try:
|
||||||
frr_cfg_fd = open(fname, mode)
|
frr_cfg_fd = open(fname, mode)
|
||||||
if config_type:
|
if config_type:
|
||||||
@ -504,9 +496,9 @@ def reset_config_on_routers(tgen, routerName=None):
|
|||||||
return True
|
return True
|
||||||
router_list = { routerName: router_list[routerName] }
|
router_list = { routerName: router_list[routerName] }
|
||||||
|
|
||||||
delta_fmt = TMPDIR + "/{}/delta.conf"
|
delta_fmt = tgen.logdir + "/{}/delta.conf"
|
||||||
init_cfg_fmt = TMPDIR + "/{}/frr_json_initial.conf"
|
init_cfg_fmt = tgen.logdir + "/{}/frr_json_initial.conf"
|
||||||
run_cfg_fmt = TMPDIR + "/{}/frr.sav"
|
run_cfg_fmt = tgen.logdir + "/{}/frr.sav"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Get all running configs in parallel
|
# Get all running configs in parallel
|
||||||
@ -532,7 +524,7 @@ def reset_config_on_routers(tgen, routerName=None):
|
|||||||
procs = {}
|
procs = {}
|
||||||
for rname in router_list:
|
for rname in router_list:
|
||||||
logger.info("Generating delta for router %s to new configuration", rname)
|
logger.info("Generating delta for router %s to new configuration", rname)
|
||||||
procs[rname] = subprocess.Popen(
|
procs[rname] = tgen.net.popen(
|
||||||
[ "/usr/lib/frr/frr-reload.py",
|
[ "/usr/lib/frr/frr-reload.py",
|
||||||
"--test-reset",
|
"--test-reset",
|
||||||
"--input",
|
"--input",
|
||||||
@ -630,8 +622,8 @@ def load_config_to_routers(tgen, routers, save_bkup=False):
|
|||||||
continue
|
continue
|
||||||
router_list[router] = base_router_list[router]
|
router_list[router] = base_router_list[router]
|
||||||
|
|
||||||
frr_cfg_file_fmt = TMPDIR + "/{}/" + FRRCFG_FILE
|
frr_cfg_file_fmt = tgen.logdir + "/{}/" + FRRCFG_FILE
|
||||||
frr_cfg_bkup_fmt = TMPDIR + "/{}/" + FRRCFG_BKUP_FILE
|
frr_cfg_bkup_fmt = tgen.logdir + "/{}/" + FRRCFG_BKUP_FILE
|
||||||
|
|
||||||
procs = {}
|
procs = {}
|
||||||
for rname in router_list:
|
for rname in router_list:
|
||||||
@ -642,8 +634,8 @@ def load_config_to_routers(tgen, routers, save_bkup=False):
|
|||||||
with open(frr_cfg_file, "r+") as cfg:
|
with open(frr_cfg_file, "r+") as cfg:
|
||||||
data = cfg.read()
|
data = cfg.read()
|
||||||
logger.info(
|
logger.info(
|
||||||
"Applying following configuration on router"
|
"Applying following configuration on router %s (gen: %d):\n%s",
|
||||||
" {}:\n{}".format(rname, data)
|
rname, gen, data
|
||||||
)
|
)
|
||||||
if save_bkup:
|
if save_bkup:
|
||||||
with open(frr_cfg_bkup, "w") as bkup:
|
with open(frr_cfg_bkup, "w") as bkup:
|
||||||
@ -808,26 +800,18 @@ def generate_support_bundle():
|
|||||||
router_list = tgen.routers()
|
router_list = tgen.routers()
|
||||||
test_name = os.environ.get('PYTEST_CURRENT_TEST').split(':')[-1].split(' ')[0]
|
test_name = os.environ.get('PYTEST_CURRENT_TEST').split(':')[-1].split(' ')[0]
|
||||||
|
|
||||||
TMPDIR = os.path.join(LOGDIR, tgen.modname)
|
|
||||||
|
|
||||||
bundle_procs = {}
|
bundle_procs = {}
|
||||||
for rname, rnode in router_list.items():
|
for rname, rnode in router_list.items():
|
||||||
logger.info("Spawn collection of support bundle for %s", rname)
|
logger.info("Spawn collection of support bundle for %s", rname)
|
||||||
rnode.run("mkdir -p /var/log/frr")
|
dst_bundle = "{}/{}/support_bundles/{}".format(tgen.logdir, rname, test_name)
|
||||||
bundle_procs[rname] = tgen.net[rname].popen(
|
rnode.run("mkdir -p " + dst_bundle)
|
||||||
"/usr/lib/frr/generate_support_bundle.py",
|
|
||||||
stdin=None,
|
gen_sup_cmd = ["/usr/lib/frr/generate_support_bundle.py", "--log-dir=" + dst_bundle]
|
||||||
stdout=subprocess.PIPE,
|
bundle_procs[rname] = tgen.net[rname].popen(gen_sup_cmd, stdin=None)
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
|
|
||||||
for rname, rnode in router_list.items():
|
for rname, rnode in router_list.items():
|
||||||
dst_bundle = "{}/{}/support_bundles/{}".format(TMPDIR, rname, test_name)
|
logger.info("Waiting on support bundle for %s", rname)
|
||||||
src_bundle = "/var/log/frr"
|
|
||||||
|
|
||||||
output, error = bundle_procs[rname].communicate()
|
output, error = bundle_procs[rname].communicate()
|
||||||
|
|
||||||
logger.info("Saving support bundle for %s", rname)
|
|
||||||
if output:
|
if output:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Output from collecting support bundle for %s:\n%s", rname, output
|
"Output from collecting support bundle for %s:\n%s", rname, output
|
||||||
@ -836,9 +820,6 @@ def generate_support_bundle():
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
"Error from collecting support bundle for %s:\n%s", rname, error
|
"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
|
return True
|
||||||
|
|
||||||
@ -850,7 +831,7 @@ def start_topology(tgen, daemon=None):
|
|||||||
* `tgen` : topogen object
|
* `tgen` : topogen object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
global TMPDIR, ROUTER_LIST
|
global ROUTER_LIST
|
||||||
# Starting topology
|
# Starting topology
|
||||||
tgen.start_topology()
|
tgen.start_topology()
|
||||||
|
|
||||||
@ -860,7 +841,6 @@ def start_topology(tgen, daemon=None):
|
|||||||
ROUTER_LIST = sorted(
|
ROUTER_LIST = sorted(
|
||||||
router_list.keys(), key=lambda x: int(re_search("[0-9]+", x).group(0))
|
router_list.keys(), key=lambda x: int(re_search("[0-9]+", x).group(0))
|
||||||
)
|
)
|
||||||
TMPDIR = os.path.join(LOGDIR, tgen.modname)
|
|
||||||
|
|
||||||
linux_ver = ""
|
linux_ver = ""
|
||||||
router_list = tgen.routers()
|
router_list = tgen.routers()
|
||||||
@ -874,49 +854,50 @@ def start_topology(tgen, daemon=None):
|
|||||||
logger.info("Logging platform related details: \n %s \n", linux_ver)
|
logger.info("Logging platform related details: \n %s \n", linux_ver)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.chdir(TMPDIR)
|
os.chdir(tgen.logdir)
|
||||||
|
|
||||||
# Creating router named dir and empty zebra.conf bgpd.conf files
|
# # Creating router named dir and empty zebra.conf bgpd.conf files
|
||||||
# inside the current directory
|
# # inside the current directory
|
||||||
if os.path.isdir("{}".format(rname)):
|
# if os.path.isdir("{}".format(rname)):
|
||||||
os.system("rm -rf {}".format(rname))
|
# os.system("rm -rf {}".format(rname))
|
||||||
os.mkdir("{}".format(rname))
|
# os.mkdir("{}".format(rname))
|
||||||
os.system("chmod -R go+rw {}".format(rname))
|
# os.system("chmod -R go+rw {}".format(rname))
|
||||||
os.chdir("{}/{}".format(TMPDIR, rname))
|
# os.chdir("{}/{}".format(tgen.logdir, rname))
|
||||||
os.system("touch zebra.conf bgpd.conf")
|
# os.system("touch zebra.conf bgpd.conf")
|
||||||
else:
|
# else:
|
||||||
os.mkdir("{}".format(rname))
|
# os.mkdir("{}".format(rname))
|
||||||
os.system("chmod -R go+rw {}".format(rname))
|
# os.system("chmod -R go+rw {}".format(rname))
|
||||||
os.chdir("{}/{}".format(TMPDIR, rname))
|
# os.chdir("{}/{}".format(tgen.logdir, rname))
|
||||||
os.system("touch zebra.conf bgpd.conf")
|
# os.system("touch zebra.conf bgpd.conf")
|
||||||
|
|
||||||
except IOError as err:
|
except IOError as err:
|
||||||
logger.error("I/O error({0}): {1}".format(err.errno, err.strerror))
|
logger.error("I/O error({0}): {1}".format(err.errno, err.strerror))
|
||||||
|
|
||||||
|
|
||||||
# Loading empty zebra.conf file to router, to start the zebra daemon
|
# Loading empty zebra.conf file to router, to start the zebra daemon
|
||||||
router.load_config(
|
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
|
# 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:
|
if daemon and "ospfd" in daemon:
|
||||||
# Loading empty ospf.conf file to router, to start the bgp daemon
|
# Loading empty ospf.conf file to router, to start the bgp daemon
|
||||||
router.load_config(
|
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:
|
if daemon and "ospf6d" in daemon:
|
||||||
# Loading empty ospf.conf file to router, to start the bgp daemon
|
# Loading empty ospf.conf file to router, to start the bgp daemon
|
||||||
router.load_config(
|
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:
|
if daemon and "pimd" in daemon:
|
||||||
# Loading empty pimd.conf file to router, to start the pim deamon
|
# Loading empty pimd.conf file to router, to start the pim deamon
|
||||||
router.load_config(
|
router.load_config(
|
||||||
TopoRouter.RD_PIM, "{}/{}/pimd.conf".format(TMPDIR, rname)
|
TopoRouter.RD_PIM, "{}/{}/pimd.conf".format(tgen.logdir, rname)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Starting routers
|
# Starting routers
|
||||||
@ -991,12 +972,15 @@ def number_to_column(routerName):
|
|||||||
return ord(routerName[0]) - 97
|
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.
|
Returns daemon list required for the suite based on topojson.
|
||||||
"""
|
"""
|
||||||
daemon_list = []
|
daemon_list = []
|
||||||
|
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
router_list = tgen.routers()
|
router_list = tgen.routers()
|
||||||
ROUTER_LIST = sorted(
|
ROUTER_LIST = sorted(
|
||||||
router_list.keys(), key=lambda x: int(re_search("[0-9]+", x).group(0))
|
router_list.keys(), key=lambda x: int(re_search("[0-9]+", x).group(0))
|
||||||
@ -1047,7 +1031,7 @@ def add_interfaces_to_vlan(tgen, input_dict):
|
|||||||
|
|
||||||
router_list = tgen.routers()
|
router_list = tgen.routers()
|
||||||
for dut in input_dict.keys():
|
for dut in input_dict.keys():
|
||||||
rnode = tgen.routers()[dut]
|
rnode = router_list[dut]
|
||||||
|
|
||||||
if "vlan" in input_dict[dut]:
|
if "vlan" in input_dict[dut]:
|
||||||
for vlan, interfaces in input_dict[dut]["vlan"].items():
|
for vlan, interfaces in input_dict[dut]["vlan"].items():
|
||||||
@ -1123,7 +1107,7 @@ def tcpdump_capture_start(
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
rnode = tgen.routers()[router]
|
rnode = tgen.gears[router]
|
||||||
|
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
cmd = "timeout {}".format(timeout)
|
cmd = "timeout {}".format(timeout)
|
||||||
@ -1140,7 +1124,7 @@ def tcpdump_capture_start(
|
|||||||
cmdargs += " -s 0 {}".format(str(options))
|
cmdargs += " -s 0 {}".format(str(options))
|
||||||
|
|
||||||
if cap_file:
|
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))
|
cmdargs += " -w {}".format(str(file_name))
|
||||||
# Remove existing capture file
|
# Remove existing capture file
|
||||||
rnode.run("rm -rf {}".format(file_name))
|
rnode.run("rm -rf {}".format(file_name))
|
||||||
@ -1152,7 +1136,9 @@ def tcpdump_capture_start(
|
|||||||
if not background:
|
if not background:
|
||||||
rnode.run(cmdargs)
|
rnode.run(cmdargs)
|
||||||
else:
|
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
|
# Check if tcpdump process is running
|
||||||
if background:
|
if background:
|
||||||
@ -1199,7 +1185,7 @@ def tcpdump_capture_stop(tgen, router):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
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
|
# Check if tcpdump process is running
|
||||||
result = rnode.run("ps -ef | grep tcpdump")
|
result = rnode.run("ps -ef | grep tcpdump")
|
||||||
@ -1209,6 +1195,7 @@ def tcpdump_capture_stop(tgen, router):
|
|||||||
errormsg = "tcpdump is not running {}".format("tcpdump")
|
errormsg = "tcpdump is not running {}".format("tcpdump")
|
||||||
return errormsg
|
return errormsg
|
||||||
else:
|
else:
|
||||||
|
# XXX this doesn't work with micronet
|
||||||
ppid = tgen.net.nameToNode[rnode.name].pid
|
ppid = tgen.net.nameToNode[rnode.name].pid
|
||||||
rnode.run("set +m; pkill -P %s tcpdump &> /dev/null" % ppid)
|
rnode.run("set +m; pkill -P %s tcpdump &> /dev/null" % ppid)
|
||||||
logger.info("Stopped tcpdump capture")
|
logger.info("Stopped tcpdump capture")
|
||||||
@ -1268,7 +1255,7 @@ def create_debug_log_config(tgen, input_dict, build=False):
|
|||||||
log_file = debug_dict.setdefault("log_file", None)
|
log_file = debug_dict.setdefault("log_file", None)
|
||||||
|
|
||||||
if log_file:
|
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))
|
debug_config.append("log file {} \n".format(_log_file))
|
||||||
|
|
||||||
if type(enable_logs) is list:
|
if type(enable_logs) is list:
|
||||||
@ -1374,9 +1361,8 @@ def create_vrf_cfg(tgen, topo, input_dict=None, build=False):
|
|||||||
config_data_dict = {}
|
config_data_dict = {}
|
||||||
|
|
||||||
for c_router, c_data in input_dict.items():
|
for c_router, c_data in input_dict.items():
|
||||||
rnode = tgen.routers()[c_router]
|
rnode = tgen.gears[c_router]
|
||||||
config_data = []
|
config_data = []
|
||||||
|
|
||||||
if "vrfs" in c_data:
|
if "vrfs" in c_data:
|
||||||
for vrf in c_data["vrfs"]:
|
for vrf in c_data["vrfs"]:
|
||||||
del_action = vrf.setdefault("delete", False)
|
del_action = vrf.setdefault("delete", False)
|
||||||
@ -1490,7 +1476,7 @@ def create_interface_in_kernel(
|
|||||||
to create
|
to create
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rnode = tgen.routers()[dut]
|
rnode = tgen.gears[dut]
|
||||||
|
|
||||||
if create:
|
if create:
|
||||||
cmd = "ip link show {0} >/dev/null || ip link add {0} type dummy".format(name)
|
cmd = "ip link show {0} >/dev/null || ip link add {0} type dummy".format(name)
|
||||||
@ -1528,7 +1514,7 @@ def shutdown_bringup_interface_in_kernel(tgen, dut, intf_name, ifaceaction=False
|
|||||||
ineterface
|
ineterface
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rnode = tgen.routers()[dut]
|
rnode = tgen.gears[dut]
|
||||||
|
|
||||||
cmd = "ip link set dev"
|
cmd = "ip link set dev"
|
||||||
if ifaceaction:
|
if ifaceaction:
|
||||||
@ -1737,7 +1723,7 @@ def interface_status(tgen, topo, input_dict):
|
|||||||
interface_list = input_dict[router]["interface_list"]
|
interface_list = input_dict[router]["interface_list"]
|
||||||
status = input_dict[router].setdefault("status", "up")
|
status = input_dict[router].setdefault("status", "up")
|
||||||
for intf in interface_list:
|
for intf in interface_list:
|
||||||
rnode = tgen.routers()[router]
|
rnode = tgen.gears[router]
|
||||||
interface_set_status(rnode, intf, status)
|
interface_set_status(rnode, intf, status)
|
||||||
|
|
||||||
rlist.append(router)
|
rlist.append(router)
|
||||||
@ -2797,7 +2783,7 @@ def addKernelRoute(
|
|||||||
|
|
||||||
logger.debug("Entering lib API: addKernelRoute()")
|
logger.debug("Entering lib API: addKernelRoute()")
|
||||||
|
|
||||||
rnode = tgen.routers()[router]
|
rnode = tgen.gears[router]
|
||||||
|
|
||||||
if type(group_addr_range) is not list:
|
if type(group_addr_range) is not list:
|
||||||
group_addr_range = [group_addr_range]
|
group_addr_range = [group_addr_range]
|
||||||
@ -2879,7 +2865,7 @@ def configure_vxlan(tgen, input_dict):
|
|||||||
|
|
||||||
router_list = tgen.routers()
|
router_list = tgen.routers()
|
||||||
for dut in input_dict.keys():
|
for dut in input_dict.keys():
|
||||||
rnode = tgen.routers()[dut]
|
rnode = router_list[dut]
|
||||||
|
|
||||||
if "vxlan" in input_dict[dut]:
|
if "vxlan" in input_dict[dut]:
|
||||||
for vxlan_dict in input_dict[dut]["vxlan"]:
|
for vxlan_dict in input_dict[dut]["vxlan"]:
|
||||||
@ -2978,7 +2964,7 @@ def configure_brctl(tgen, topo, input_dict):
|
|||||||
|
|
||||||
router_list = tgen.routers()
|
router_list = tgen.routers()
|
||||||
for dut in input_dict.keys():
|
for dut in input_dict.keys():
|
||||||
rnode = tgen.routers()[dut]
|
rnode = router_list[dut]
|
||||||
|
|
||||||
if "brctl" in input_dict[dut]:
|
if "brctl" in input_dict[dut]:
|
||||||
for brctl_dict in input_dict[dut]["brctl"]:
|
for brctl_dict in input_dict[dut]["brctl"]:
|
||||||
@ -3064,7 +3050,7 @@ def configure_interface_mac(tgen, input_dict):
|
|||||||
|
|
||||||
router_list = tgen.routers()
|
router_list = tgen.routers()
|
||||||
for dut in input_dict.keys():
|
for dut in input_dict.keys():
|
||||||
rnode = tgen.routers()[dut]
|
rnode = router_list[dut]
|
||||||
|
|
||||||
for intf, mac in input_dict[dut].items():
|
for intf, mac in input_dict[dut].items():
|
||||||
cmd = "ip link set {} address {}".format(intf, mac)
|
cmd = "ip link set {} address {}".format(intf, mac)
|
||||||
@ -3535,7 +3521,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))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
router_list = tgen.routers()
|
router_list = tgen.routers()
|
||||||
|
if dut not in router_list:
|
||||||
|
return
|
||||||
|
|
||||||
for routerInput in input_dict.keys():
|
for routerInput in input_dict.keys():
|
||||||
|
# XXX replace with router = dut; rnode = router_list[dut]
|
||||||
for router, rnode in router_list.items():
|
for router, rnode in router_list.items():
|
||||||
if router != dut:
|
if router != dut:
|
||||||
continue
|
continue
|
||||||
@ -3780,11 +3770,11 @@ def verify_admin_distance_for_static_routes(tgen, input_dict):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
|
router_list = tgen.routers()
|
||||||
for router in input_dict.keys():
|
for router in input_dict.keys():
|
||||||
if router not in tgen.routers():
|
if router not in router_list:
|
||||||
continue
|
continue
|
||||||
|
rnode = router_list[router]
|
||||||
rnode = tgen.routers()[router]
|
|
||||||
|
|
||||||
for static_route in input_dict[router]["static_routes"]:
|
for static_route in input_dict[router]["static_routes"]:
|
||||||
addr_type = validate_ip_address(static_route["network"])
|
addr_type = validate_ip_address(static_route["network"])
|
||||||
@ -3862,11 +3852,12 @@ def verify_prefix_lists(tgen, input_dict):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
|
router_list = tgen.routers()
|
||||||
for router in input_dict.keys():
|
for router in input_dict.keys():
|
||||||
if router not in tgen.routers():
|
if router not in router_list:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rnode = tgen.routers()[router]
|
rnode = router_list[router]
|
||||||
|
|
||||||
# Show ip prefix list
|
# Show ip prefix list
|
||||||
show_prefix_list = run_frr_cmd(rnode, "show ip prefix-list")
|
show_prefix_list = run_frr_cmd(rnode, "show ip prefix-list")
|
||||||
@ -3925,11 +3916,12 @@ def verify_route_maps(tgen, input_dict):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
|
router_list = tgen.routers()
|
||||||
for router in input_dict.keys():
|
for router in input_dict.keys():
|
||||||
if router not in tgen.routers():
|
if router not in router_list:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rnode = tgen.routers()[router]
|
rnode = router_list[router]
|
||||||
# Show ip route-map
|
# Show ip route-map
|
||||||
show_route_maps = rnode.vtysh_cmd("show route-map")
|
show_route_maps = rnode.vtysh_cmd("show route-map")
|
||||||
|
|
||||||
@ -3978,10 +3970,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))
|
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
|
return False
|
||||||
|
|
||||||
rnode = tgen.routers()[router]
|
rnode = router_list[router]
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Verifying BGP community attributes on dut %s: for %s " "network %s",
|
"Verifying BGP community attributes on dut %s: for %s " "network %s",
|
||||||
@ -4108,11 +4101,12 @@ def verify_create_community_list(tgen, input_dict):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
|
router_list = tgen.routers()
|
||||||
for router in input_dict.keys():
|
for router in input_dict.keys():
|
||||||
if router not in tgen.routers():
|
if router not in router_list:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rnode = tgen.routers()[router]
|
rnode = router_list[router]
|
||||||
|
|
||||||
logger.info("Verifying large-community is created for dut %s:", router)
|
logger.info("Verifying large-community is created for dut %s:", router)
|
||||||
|
|
||||||
@ -4163,7 +4157,7 @@ def verify_cli_json(tgen, input_dict):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
for dut in input_dict.keys():
|
for dut in input_dict.keys():
|
||||||
rnode = tgen.routers()[dut]
|
rnode = tgen.gears[dut]
|
||||||
|
|
||||||
for cli in input_dict[dut]["cli"]:
|
for cli in input_dict[dut]["cli"]:
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -4225,7 +4219,7 @@ def verify_evpn_vni(tgen, input_dict):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
for dut in input_dict.keys():
|
for dut in input_dict.keys():
|
||||||
rnode = tgen.routers()[dut]
|
rnode = tgen.gears[dut]
|
||||||
|
|
||||||
logger.info("[DUT: %s]: Verifying evpn vni details :", dut)
|
logger.info("[DUT: %s]: Verifying evpn vni details :", dut)
|
||||||
|
|
||||||
@ -4343,7 +4337,7 @@ def verify_vrf_vni(tgen, input_dict):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
for dut in input_dict.keys():
|
for dut in input_dict.keys():
|
||||||
rnode = tgen.routers()[dut]
|
rnode = tgen.gears[dut]
|
||||||
|
|
||||||
logger.info("[DUT: %s]: Verifying vrf vni details :", dut)
|
logger.info("[DUT: %s]: Verifying vrf vni details :", dut)
|
||||||
|
|
||||||
@ -4447,9 +4441,7 @@ def required_linux_kernel_version(required_version):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def iperfSendIGMPJoin(
|
def iperfSendIGMPJoin(tgen, server, bindToAddress, l4Type="UDP", join_interval=1):
|
||||||
tgen, server, bindToAddress, l4Type="UDP", join_interval=1, inc_step=0, repeat=0
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Run iperf to send IGMP join and traffic
|
Run iperf to send IGMP join and traffic
|
||||||
|
|
||||||
@ -4461,8 +4453,6 @@ def iperfSendIGMPJoin(
|
|||||||
* `bindToAddress`: bind to <host>, an interface or multicast
|
* `bindToAddress`: bind to <host>, an interface or multicast
|
||||||
address
|
address
|
||||||
* `join_interval`: seconds between periodic bandwidth reports
|
* `join_interval`: seconds between periodic bandwidth reports
|
||||||
* `inc_step`: increamental steps, by default 0
|
|
||||||
* `repeat`: Repetition of group, by default 0
|
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
--------
|
--------
|
||||||
@ -4471,54 +4461,44 @@ def iperfSendIGMPJoin(
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
rnode = tgen.routers()[server]
|
rnode = tgen.gears[server]
|
||||||
|
|
||||||
iperfArgs = "iperf -s "
|
iperf_path = tgen.net.get_exec_path("iperf")
|
||||||
|
|
||||||
|
if bindToAddress and not isinstance(bindToAddress, list):
|
||||||
|
bindToAddress = [ipaddress.IPv4Address(frr_unicode(bindToAddress))]
|
||||||
|
|
||||||
|
for bindTo in bindToAddress:
|
||||||
|
iperf_args = [iperf_path, "-s"]
|
||||||
|
|
||||||
# UDP/TCP
|
# UDP/TCP
|
||||||
if l4Type == "UDP":
|
if l4Type == "UDP":
|
||||||
iperfArgs += "-u "
|
iperf_args.append("-u")
|
||||||
|
|
||||||
iperfCmd = iperfArgs
|
iperf_args.append("-B")
|
||||||
# Group address range to cover
|
iperf_args.append(str(bindTo))
|
||||||
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
|
# Join interval
|
||||||
if join_interval:
|
if join_interval:
|
||||||
iperfArgs += "-i %d " % join_interval
|
iperf_args.append("-i")
|
||||||
|
iperf_args.append(str(join_interval))
|
||||||
|
|
||||||
iperfArgs += " &>/dev/null &"
|
|
||||||
# Run iperf command to send IGMP join
|
# Run iperf command to send IGMP join
|
||||||
logger.debug("[DUT: {}]: Running command: [{}]".format(server, iperfArgs))
|
logger.debug("[DUT: %s]: Running command: %s",server, iperf_args)
|
||||||
output = rnode.run("set +m; {} sleep 0.5".format(iperfArgs))
|
|
||||||
|
|
||||||
# Check if iperf process is running
|
p = rnode.popen(iperf_args, stderr=subprocess.STDOUT)
|
||||||
if output:
|
rc = p.poll()
|
||||||
pid = output.split()[1]
|
if rc is not None:
|
||||||
rnode.run("touch /var/run/frr/iperf_server.pid")
|
output, _ = p.communicate()
|
||||||
rnode.run("echo %s >> /var/run/frr/iperf_server.pid" % pid)
|
if rc:
|
||||||
else:
|
|
||||||
errormsg = "IGMP join is not sent for {}. Error: {}".format(bindTo, output)
|
errormsg = "IGMP join is not sent for {}. Error: {}".format(bindTo, output)
|
||||||
logger.error(output)
|
logger.error("%s", output)
|
||||||
return errormsg
|
return errormsg
|
||||||
|
|
||||||
|
if server not in g_iperf_server_procs:
|
||||||
|
g_iperf_server_procs[server] = []
|
||||||
|
g_iperf_server_procs[server].append(p)
|
||||||
|
|
||||||
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -4526,13 +4506,11 @@ def iperfSendIGMPJoin(
|
|||||||
def iperfSendTraffic(
|
def iperfSendTraffic(
|
||||||
tgen,
|
tgen,
|
||||||
client,
|
client,
|
||||||
bindToAddress,
|
sentToAddress,
|
||||||
ttl,
|
ttl,
|
||||||
time=0,
|
time=0,
|
||||||
l4Type="UDP",
|
l4Type="UDP",
|
||||||
inc_step=0,
|
bindToIntf=None,
|
||||||
repeat=0,
|
|
||||||
mappedAddress=None,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Run iperf to send IGMP join and traffic
|
Run iperf to send IGMP join and traffic
|
||||||
@ -4542,13 +4520,11 @@ def iperfSendTraffic(
|
|||||||
* `tgen` : Topogen object
|
* `tgen` : Topogen object
|
||||||
* `l4Type`: string, one of [ TCP, UDP ]
|
* `l4Type`: string, one of [ TCP, UDP ]
|
||||||
* `client`: iperf client, from where iperf traffic would be sent
|
* `client`: iperf client, from where iperf traffic would be sent
|
||||||
* `bindToAddress`: bind to <host>, an interface or multicast
|
* `sentToAddress`: bind to <host>, an interface or multicast
|
||||||
address
|
address
|
||||||
* `ttl`: time to live
|
* `ttl`: time to live
|
||||||
* `time`: time in seconds to transmit for
|
* `time`: time in seconds to transmit for
|
||||||
* `inc_step`: increamental steps, by default 0
|
* `bindToIntf`: Source interface ip address
|
||||||
* `repeat`: Repetition of group, by default 0
|
|
||||||
* `mappedAddress`: Mapped Interface ip address
|
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
--------
|
--------
|
||||||
@ -4557,65 +4533,57 @@ def iperfSendTraffic(
|
|||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
rnode = tgen.routers()[client]
|
rnode = tgen.gears[client]
|
||||||
|
|
||||||
iperfArgs = "iperf -c "
|
iperf_path = tgen.net.get_exec_path("iperf")
|
||||||
|
|
||||||
iperfCmd = iperfArgs
|
if sentToAddress and not isinstance(sentToAddress, list):
|
||||||
# Group address range to cover
|
sentToAddress = [ipaddress.IPv4Address(frr_unicode(sentToAddress))]
|
||||||
if bindToAddress:
|
|
||||||
if type(bindToAddress) is not list:
|
|
||||||
Address = []
|
|
||||||
start = ipaddress.IPv4Address(frr_unicode(bindToAddress))
|
|
||||||
|
|
||||||
Address = [start]
|
for sendTo in sentToAddress:
|
||||||
next_ip = start
|
iperf_args = [iperf_path, "-c", sendTo]
|
||||||
|
|
||||||
count = 1
|
# Bind to Interface IP
|
||||||
while count < repeat:
|
if bindToIntf:
|
||||||
next_ip += inc_step
|
ifaddr = frr_unicode(tgen.json_topo["routers"][client]["links"][bindToIntf]["ipv4"])
|
||||||
Address.append(next_ip)
|
ipaddr = ipaddress.IPv4Interface(ifaddr).ip
|
||||||
count += 1
|
iperf_args.append("-B")
|
||||||
bindToAddress = Address
|
iperf_args.append(str(ipaddr))
|
||||||
|
|
||||||
for bindTo in bindToAddress:
|
|
||||||
iperfArgs = iperfCmd
|
|
||||||
iperfArgs += "%s " % bindTo
|
|
||||||
|
|
||||||
# Mapped Interface IP
|
|
||||||
if mappedAddress:
|
|
||||||
iperfArgs += "-B %s " % mappedAddress
|
|
||||||
|
|
||||||
# UDP/TCP
|
# UDP/TCP
|
||||||
if l4Type == "UDP":
|
if l4Type == "UDP":
|
||||||
iperfArgs += "-u -b 0.012m "
|
iperf_args.append("-u")
|
||||||
|
iperf_args.append("-b")
|
||||||
|
iperf_args.append("0.012m")
|
||||||
|
|
||||||
# TTL
|
# TTL
|
||||||
if ttl:
|
if ttl:
|
||||||
iperfArgs += "-T %d " % ttl
|
iperf_args.append("-T")
|
||||||
|
iperf_args.append(str(ttl))
|
||||||
|
|
||||||
# Time
|
# Time
|
||||||
if time:
|
if time:
|
||||||
iperfArgs += "-t %d " % time
|
iperf_args.append("-t")
|
||||||
|
iperf_args.append(str(time))
|
||||||
iperfArgs += " &>/dev/null &"
|
|
||||||
|
|
||||||
# Run iperf command to send multicast traffic
|
# Run iperf command to send multicast traffic
|
||||||
logger.debug("[DUT: {}]: Running command: [{}]".format(client, iperfArgs))
|
logger.debug("[DUT: {}]: Running command: {}".format(client, iperf_args))
|
||||||
output = rnode.run("set +m; {} sleep 0.5".format(iperfArgs))
|
|
||||||
|
|
||||||
# Check if iperf process is running
|
p = rnode.popen(iperf_args, stderr=subprocess.STDOUT)
|
||||||
if output:
|
rc = p.poll()
|
||||||
pid = output.split()[1]
|
if rc is not None:
|
||||||
rnode.run("touch /var/run/frr/iperf_client.pid")
|
output, _ = p.communicate()
|
||||||
rnode.run("echo %s >> /var/run/frr/iperf_client.pid" % pid)
|
if rc:
|
||||||
else:
|
|
||||||
errormsg = "Multicast traffic is not sent for {}. Error {}".format(
|
errormsg = "Multicast traffic is not sent for {}. Error {}".format(
|
||||||
bindTo, output
|
sendTo, output
|
||||||
)
|
)
|
||||||
logger.error(output)
|
logger.error(output)
|
||||||
return errormsg
|
return errormsg
|
||||||
|
|
||||||
|
if client not in g_iperf_client_procs:
|
||||||
|
g_iperf_client_procs[client] = []
|
||||||
|
g_iperf_client_procs[client].append(p)
|
||||||
|
|
||||||
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -4637,24 +4605,36 @@ def kill_iperf(tgen, dut=None, action=None):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
logger.debug("Running iperfs: clients: %s servers: %s", g_iperf_client_procs, g_iperf_server_procs)
|
||||||
|
|
||||||
router_list = tgen.routers()
|
if dut is not None:
|
||||||
for router, rnode in router_list.items():
|
nodes = [dut]
|
||||||
# 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:
|
else:
|
||||||
pids = "\n".join([pid_client, pid_server])
|
nodes = sorted(tgen.gears.keys())
|
||||||
for pid in pids.split("\n"):
|
|
||||||
pid = pid.strip()
|
for name in nodes:
|
||||||
if pid.isdigit():
|
logger.debug("Checking for iperfs on %s", name)
|
||||||
cmd = "set +m; kill -9 %s &> /dev/null" % pid
|
if action == "remove_join":
|
||||||
logger.debug("[DUT: {}]: Running command: [{}]".format(router, cmd))
|
procs = g_iperf_server_procs[name] if name in g_iperf_server_procs else []
|
||||||
rnode.run(cmd)
|
g_iperf_server_procs[name] = []
|
||||||
|
elif action == "remove_traffic":
|
||||||
|
procs = g_iperf_client_procs[name] if name in g_iperf_client_procs else []
|
||||||
|
g_iperf_client_procs[name] = []
|
||||||
|
else:
|
||||||
|
procs = []
|
||||||
|
if name in g_iperf_server_procs:
|
||||||
|
procs.extend(g_iperf_server_procs[name])
|
||||||
|
g_iperf_server_procs[name] = []
|
||||||
|
if name in g_iperf_client_procs:
|
||||||
|
procs.extend(g_iperf_client_procs[name])
|
||||||
|
g_iperf_client_procs[name] = []
|
||||||
|
for p in procs:
|
||||||
|
logger.info("[DUT: {}]: Terminating iperf: [{}]".format(name, p.pid))
|
||||||
|
# p.send_signal(signal.SIGHUP)
|
||||||
|
p.terminate()
|
||||||
|
for p in procs:
|
||||||
|
logger.info("[DUT: {}]: Waiting for iperf to terminate: [{}]".format(name, p.pid))
|
||||||
|
p.wait()
|
||||||
|
|
||||||
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
@ -4689,14 +4669,15 @@ def verify_ip_nht(tgen, input_dict):
|
|||||||
|
|
||||||
logger.debug("Entering lib API: verify_ip_nht()")
|
logger.debug("Entering lib API: verify_ip_nht()")
|
||||||
|
|
||||||
|
router_list = tgen.routers()
|
||||||
for router in input_dict.keys():
|
for router in input_dict.keys():
|
||||||
if router not in tgen.routers():
|
if router not in router_list:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rnode = tgen.routers()[router]
|
rnode = router_list[router]
|
||||||
nh_list = input_dict[router]
|
nh_list = input_dict[router]
|
||||||
|
|
||||||
if validate_ip_address(nh_list.keys()[0]) is "ipv6":
|
if validate_ip_address(next(iter(nh_list))) is "ipv6":
|
||||||
show_ip_nht = run_frr_cmd(rnode, "show ipv6 nht")
|
show_ip_nht = run_frr_cmd(rnode, "show ipv6 nht")
|
||||||
else:
|
else:
|
||||||
show_ip_nht = run_frr_cmd(rnode, "show ip nht")
|
show_ip_nht = run_frr_cmd(rnode, "show ip nht")
|
||||||
@ -4714,7 +4695,7 @@ def verify_ip_nht(tgen, input_dict):
|
|||||||
|
|
||||||
|
|
||||||
def scapy_send_raw_packet(
|
def scapy_send_raw_packet(
|
||||||
tgen, topo, senderRouter, intf, packet=None, interval=1, count=1
|
tgen, topo, senderRouter, intf, packet=None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Using scapy Raw() method to send BSR raw packet from one FRR
|
Using scapy Raw() method to send BSR raw packet from one FRR
|
||||||
@ -4726,8 +4707,6 @@ def scapy_send_raw_packet(
|
|||||||
* `topo` : json file data
|
* `topo` : json file data
|
||||||
* `senderRouter` : Sender router
|
* `senderRouter` : Sender router
|
||||||
* `packet` : packet in raw format
|
* `packet` : packet in raw format
|
||||||
* `interval` : Interval between the packets
|
|
||||||
* `count` : Number of packets to be sent
|
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
--------
|
--------
|
||||||
@ -4749,18 +4728,11 @@ def scapy_send_raw_packet(
|
|||||||
"data"
|
"data"
|
||||||
]
|
]
|
||||||
|
|
||||||
if interval > 1 or count > 1:
|
python3_path = tgen.net.get_exec_path(["python3", "python"])
|
||||||
|
script_path = os.path.join(CD, "send_bsr_packet.py")
|
||||||
cmd = (
|
cmd = (
|
||||||
"nohup /usr/bin/python {}/send_bsr_packet.py '{}' '{}' "
|
"{} {} '{}' '{}' --interval=1 --count=1".format(
|
||||||
"--interval={} --count={} &".format(
|
python3_path, script_path, packet, sender_interface
|
||||||
CD, packet, sender_interface, interval, count
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
cmd = (
|
|
||||||
"/usr/bin/python {}/send_bsr_packet.py '{}' '{}' "
|
|
||||||
"--interval={} --count={}".format(
|
|
||||||
CD, packet, sender_interface, interval, count
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
46
tests/topotests/lib/fixtures.py
Normal file
46
tests/topotests/lib/fixtures.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# -*- coding: utf-8 eval: (yapf-mode 1) -*-
|
||||||
|
#
|
||||||
|
# August 27 2021, Christian Hopps <chopps@labn.net>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2021, LabN Consulting, L.L.C. ("LabN")
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
import lib.topojson as topojson
|
||||||
|
import lib.topogen as topogen
|
||||||
|
from lib.topolog import logger
|
||||||
|
|
||||||
|
def tgen_json(request):
|
||||||
|
logger.info("Creating/starting topogen topology for %s", request.module.__name__)
|
||||||
|
|
||||||
|
tgen = topojson.setup_module_from_json(request.module.__file__)
|
||||||
|
yield tgen
|
||||||
|
|
||||||
|
logger.info("Stopping topogen topology for %s", request.module.__name__)
|
||||||
|
tgen.stop_topology()
|
||||||
|
|
||||||
|
|
||||||
|
def topo(tgen):
|
||||||
|
"""Make tgen json object available as test argument."""
|
||||||
|
return tgen.json_topo
|
||||||
|
|
||||||
|
|
||||||
|
def tgen():
|
||||||
|
"""Make global topogen object available as test argument."""
|
||||||
|
return topogen.get_topogen()
|
@ -28,8 +28,8 @@ ltemplate.py: LabN template for FRR tests.
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import imp
|
|
||||||
|
|
||||||
# pylint: disable=C0413
|
# pylint: disable=C0413
|
||||||
# Import topogen and topotest helpers
|
# Import topogen and topotest helpers
|
||||||
@ -43,7 +43,6 @@ from mininet.topo import Topo
|
|||||||
|
|
||||||
customize = None
|
customize = None
|
||||||
|
|
||||||
|
|
||||||
class LTemplate:
|
class LTemplate:
|
||||||
test = None
|
test = None
|
||||||
testdir = None
|
testdir = None
|
||||||
@ -54,12 +53,20 @@ class LTemplate:
|
|||||||
iproute2Ver = None
|
iproute2Ver = None
|
||||||
|
|
||||||
def __init__(self, test, testdir):
|
def __init__(self, test, testdir):
|
||||||
|
pathname = os.path.join(testdir, "customize.py")
|
||||||
global customize
|
global customize
|
||||||
customize = imp.load_source("customize", os.path.join(testdir, "customize.py"))
|
if sys.version_info >= (3, 5):
|
||||||
|
import importlib.util
|
||||||
|
spec = importlib.util.spec_from_file_location("customize", pathname)
|
||||||
|
customize = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(customize)
|
||||||
|
else:
|
||||||
|
import imp
|
||||||
|
customize = imp.load_source("customize", pathname)
|
||||||
self.test = test
|
self.test = test
|
||||||
self.testdir = testdir
|
self.testdir = testdir
|
||||||
self.scriptdir = testdir
|
self.scriptdir = testdir
|
||||||
self.logdir = "/tmp/topotests/{0}.test_{0}".format(test)
|
self.logdir = ""
|
||||||
logger.info("LTemplate: " + test)
|
logger.info("LTemplate: " + test)
|
||||||
|
|
||||||
def setup_module(self, mod):
|
def setup_module(self, mod):
|
||||||
@ -69,6 +76,8 @@ class LTemplate:
|
|||||||
# ... and here it calls Mininet initialization functions.
|
# ... and here it calls Mininet initialization functions.
|
||||||
tgen.start_topology()
|
tgen.start_topology()
|
||||||
|
|
||||||
|
self.logdir = tgen.logdir
|
||||||
|
|
||||||
logger.info("Topology started")
|
logger.info("Topology started")
|
||||||
try:
|
try:
|
||||||
self.prestarthooksuccess = customize.ltemplatePreRouterStartHook()
|
self.prestarthooksuccess = customize.ltemplatePreRouterStartHook()
|
||||||
|
@ -21,14 +21,15 @@ for the multicast group we subscribed to.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
|
||||||
import struct
|
import struct
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Functions
|
# Functions
|
||||||
#
|
#
|
||||||
@ -64,7 +65,7 @@ parser.add_argument('group', help='Multicast IP')
|
|||||||
parser.add_argument('interface', help='Interface name')
|
parser.add_argument('interface', help='Interface name')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--send',
|
'--send',
|
||||||
help='Transmit instead of join with interval (defaults to 0.7 sec)',
|
help='Transmit instead of join with interval',
|
||||||
type=float, default=0)
|
type=float, default=0)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
@ -18,37 +18,35 @@
|
|||||||
# OF THIS SOFTWARE.
|
# OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
import ipaddr
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from time import sleep
|
|
||||||
from lib.topolog import logger
|
|
||||||
from lib.topotest import frr_unicode
|
|
||||||
from ipaddress import IPv6Address
|
from ipaddress import IPv6Address
|
||||||
import sys
|
from time import sleep
|
||||||
|
|
||||||
|
import ipaddr
|
||||||
|
|
||||||
# Import common_config to use commomnly used APIs
|
# Import common_config to use commomnly used APIs
|
||||||
from lib.common_config import (
|
from lib.common_config import (
|
||||||
create_common_configurations,
|
create_common_configurations,
|
||||||
InvalidCLIError,
|
InvalidCLIError,
|
||||||
retry,
|
|
||||||
generate_ips,
|
|
||||||
check_address_types,
|
check_address_types,
|
||||||
validate_ip_address,
|
create_common_configuration,
|
||||||
|
generate_ips,
|
||||||
|
retry,
|
||||||
run_frr_cmd,
|
run_frr_cmd,
|
||||||
|
validate_ip_address,
|
||||||
)
|
)
|
||||||
|
from lib.topolog import logger
|
||||||
LOGDIR = "/tmp/topotests/"
|
from lib.topotest import frr_unicode
|
||||||
TMPDIR = None
|
|
||||||
|
|
||||||
################################
|
################################
|
||||||
# Configure procs
|
# Configure procs
|
||||||
################################
|
################################
|
||||||
|
|
||||||
|
|
||||||
def create_router_ospf(tgen, topo, input_dict=None, build=False, load_config=True):
|
def create_router_ospf(tgen, topo=None, input_dict=None, build=False, load_config=True):
|
||||||
"""
|
"""
|
||||||
API to configure ospf on router.
|
API to configure ospf on router.
|
||||||
|
|
||||||
@ -79,6 +77,9 @@ def create_router_ospf(tgen, topo, input_dict=None, build=False, load_config=Tru
|
|||||||
logger.debug("Entering lib API: create_router_ospf()")
|
logger.debug("Entering lib API: create_router_ospf()")
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
if not input_dict:
|
if not input_dict:
|
||||||
input_dict = deepcopy(topo)
|
input_dict = deepcopy(topo)
|
||||||
else:
|
else:
|
||||||
@ -373,7 +374,7 @@ def __create_ospf_global(
|
|||||||
return config_data
|
return config_data
|
||||||
|
|
||||||
|
|
||||||
def create_router_ospf6(tgen, topo, input_dict=None, build=False, load_config=True):
|
def create_router_ospf6(tgen, topo=None, input_dict=None, build=False, load_config=True):
|
||||||
"""
|
"""
|
||||||
API to configure ospf on router
|
API to configure ospf on router
|
||||||
|
|
||||||
@ -400,6 +401,9 @@ def create_router_ospf6(tgen, topo, input_dict=None, build=False, load_config=Tr
|
|||||||
logger.debug("Entering lib API: create_router_ospf6()")
|
logger.debug("Entering lib API: create_router_ospf6()")
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
if not input_dict:
|
if not input_dict:
|
||||||
input_dict = deepcopy(topo)
|
input_dict = deepcopy(topo)
|
||||||
else:
|
else:
|
||||||
@ -431,7 +435,7 @@ def create_router_ospf6(tgen, topo, input_dict=None, build=False, load_config=Tr
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def config_ospf_interface(tgen, topo, input_dict=None, build=False, load_config=True):
|
def config_ospf_interface(tgen, topo=None, input_dict=None, build=False, load_config=True):
|
||||||
"""
|
"""
|
||||||
API to configure ospf on router.
|
API to configure ospf on router.
|
||||||
|
|
||||||
@ -466,6 +470,10 @@ def config_ospf_interface(tgen, topo, input_dict=None, build=False, load_config=
|
|||||||
"""
|
"""
|
||||||
logger.debug("Enter lib config_ospf_interface")
|
logger.debug("Enter lib config_ospf_interface")
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
if not input_dict:
|
if not input_dict:
|
||||||
input_dict = deepcopy(topo)
|
input_dict = deepcopy(topo)
|
||||||
else:
|
else:
|
||||||
@ -632,7 +640,7 @@ def redistribute_ospf(tgen, topo, dut, route_type, **kwargs):
|
|||||||
# Verification procs
|
# Verification procs
|
||||||
################################
|
################################
|
||||||
@retry(retry_timeout=80)
|
@retry(retry_timeout=80)
|
||||||
def verify_ospf_neighbor(tgen, topo, dut=None, input_dict=None, lan=False, expected=True):
|
def verify_ospf_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False, expected=True):
|
||||||
"""
|
"""
|
||||||
This API is to verify ospf neighborship by running
|
This API is to verify ospf neighborship by running
|
||||||
show ip ospf neighbour command,
|
show ip ospf neighbour command,
|
||||||
@ -680,6 +688,9 @@ def verify_ospf_neighbor(tgen, topo, dut=None, input_dict=None, lan=False, expec
|
|||||||
"""
|
"""
|
||||||
logger.debug("Entering lib API: verify_ospf_neighbor()")
|
logger.debug("Entering lib API: verify_ospf_neighbor()")
|
||||||
result = False
|
result = False
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
if input_dict:
|
if input_dict:
|
||||||
for router, rnode in tgen.routers().items():
|
for router, rnode in tgen.routers().items():
|
||||||
if "ospf" not in topo["routers"][router]:
|
if "ospf" not in topo["routers"][router]:
|
||||||
@ -827,7 +838,7 @@ def verify_ospf_neighbor(tgen, topo, dut=None, input_dict=None, lan=False, expec
|
|||||||
# Verification procs
|
# Verification procs
|
||||||
################################
|
################################
|
||||||
@retry(retry_timeout=50)
|
@retry(retry_timeout=50)
|
||||||
def verify_ospf6_neighbor(tgen, topo, dut=None, input_dict=None, lan=False):
|
def verify_ospf6_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False):
|
||||||
"""
|
"""
|
||||||
This API is to verify ospf neighborship by running
|
This API is to verify ospf neighborship by running
|
||||||
show ipv6 ospf neighbour command,
|
show ipv6 ospf neighbour command,
|
||||||
@ -875,6 +886,9 @@ def verify_ospf6_neighbor(tgen, topo, dut=None, input_dict=None, lan=False):
|
|||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
if input_dict:
|
if input_dict:
|
||||||
for router, rnode in tgen.routers().items():
|
for router, rnode in tgen.routers().items():
|
||||||
if "ospf6" not in topo["routers"][router]:
|
if "ospf6" not in topo["routers"][router]:
|
||||||
@ -1318,7 +1332,7 @@ def verify_ospf_rib(
|
|||||||
|
|
||||||
|
|
||||||
@retry(retry_timeout=20)
|
@retry(retry_timeout=20)
|
||||||
def verify_ospf_interface(tgen, topo, dut=None, lan=False, input_dict=None, expected=True):
|
def verify_ospf_interface(tgen, topo=None, dut=None, lan=False, input_dict=None, expected=True):
|
||||||
"""
|
"""
|
||||||
This API is to verify ospf routes by running
|
This API is to verify ospf routes by running
|
||||||
show ip ospf interface command.
|
show ip ospf interface command.
|
||||||
@ -1360,6 +1374,9 @@ def verify_ospf_interface(tgen, topo, dut=None, lan=False, input_dict=None, expe
|
|||||||
|
|
||||||
logger.debug("Entering lib API: verify_ospf_interface()")
|
logger.debug("Entering lib API: verify_ospf_interface()")
|
||||||
result = False
|
result = False
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
for router, rnode in tgen.routers().items():
|
for router, rnode in tgen.routers().items():
|
||||||
if "ospf" not in topo["routers"][router]:
|
if "ospf" not in topo["routers"][router]:
|
||||||
continue
|
continue
|
||||||
@ -1936,7 +1953,7 @@ def verify_ospf6_rib(tgen, dut, input_dict, next_hop=None,
|
|||||||
|
|
||||||
|
|
||||||
@retry(retry_timeout=6)
|
@retry(retry_timeout=6)
|
||||||
def verify_ospf6_interface(tgen, topo, dut=None,lan=False, input_dict=None):
|
def verify_ospf6_interface(tgen, topo=None, dut=None,lan=False, input_dict=None):
|
||||||
"""
|
"""
|
||||||
This API is to verify ospf routes by running
|
This API is to verify ospf routes by running
|
||||||
show ip ospf interface command.
|
show ip ospf interface command.
|
||||||
@ -1978,8 +1995,11 @@ def verify_ospf6_interface(tgen, topo, dut=None,lan=False, input_dict=None):
|
|||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
for router, rnode in tgen.routers().iteritems():
|
if topo is None:
|
||||||
if "ospf6" not in topo["routers"][router]:
|
topo = tgen.json_topo
|
||||||
|
|
||||||
|
for router, rnode in tgen.routers().items():
|
||||||
|
if 'ospf6' not in topo['routers'][router]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if dut is not None and dut != router:
|
if dut is not None and dut != router:
|
||||||
@ -2315,7 +2335,7 @@ def verify_ospf6_database(tgen, topo, dut, input_dict):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def config_ospf6_interface(tgen, topo, input_dict=None, build=False, load_config=True):
|
def config_ospf6_interface(tgen, topo=None, input_dict=None, build=False, load_config=True):
|
||||||
"""
|
"""
|
||||||
API to configure ospf on router.
|
API to configure ospf on router.
|
||||||
|
|
||||||
@ -2350,6 +2370,9 @@ def config_ospf6_interface(tgen, topo, input_dict=None, build=False, load_config
|
|||||||
"""
|
"""
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
result = False
|
result = False
|
||||||
|
if topo is None:
|
||||||
|
topo = tgen.json_topo
|
||||||
|
|
||||||
if not input_dict:
|
if not input_dict:
|
||||||
input_dict = deepcopy(topo)
|
input_dict = deepcopy(topo)
|
||||||
else:
|
else:
|
||||||
|
@ -16,24 +16,25 @@
|
|||||||
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
||||||
# OF THIS SOFTWARE.
|
# OF THIS SOFTWARE.
|
||||||
|
|
||||||
import sys
|
import datetime
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import datetime
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import pytest
|
|
||||||
from time import sleep
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from lib.topolog import logger
|
from time import sleep
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
# Import common_config to use commomnly used APIs
|
# Import common_config to use commomnly used APIs
|
||||||
from lib.common_config import (
|
from lib.common_config import (
|
||||||
create_common_configuration,
|
|
||||||
create_common_configurations,
|
create_common_configurations,
|
||||||
|
create_common_configuration,
|
||||||
InvalidCLIError,
|
InvalidCLIError,
|
||||||
retry,
|
retry,
|
||||||
run_frr_cmd,
|
run_frr_cmd,
|
||||||
)
|
)
|
||||||
|
from lib.topolog import logger
|
||||||
|
|
||||||
####
|
####
|
||||||
CWD = os.path.dirname(os.path.realpath(__file__))
|
CWD = os.path.dirname(os.path.realpath(__file__))
|
||||||
@ -922,7 +923,8 @@ def verify_join_state_and_timer(tgen, dut, iif, src_address, group_addresses, ex
|
|||||||
error = (
|
error = (
|
||||||
"[DUT %s]: Verifying join timer for"
|
"[DUT %s]: Verifying join timer for"
|
||||||
" (%s,%s) [FAILED]!! "
|
" (%s,%s) [FAILED]!! "
|
||||||
" Expected: %s, Found: %s",
|
" Expected: %s, Found: %s"
|
||||||
|
) % (
|
||||||
dut,
|
dut,
|
||||||
src_address,
|
src_address,
|
||||||
grp_addr,
|
grp_addr,
|
||||||
@ -2028,9 +2030,7 @@ def add_rp_interfaces_and_pim_config(tgen, topo, interface, rp, rp_mapping):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def scapy_send_bsr_raw_packet(
|
def scapy_send_bsr_raw_packet(tgen, topo, senderRouter, receiverRouter, packet=None):
|
||||||
tgen, topo, senderRouter, receiverRouter, packet=None, interval=1, count=1
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Using scapy Raw() method to send BSR raw packet from one FRR
|
Using scapy Raw() method to send BSR raw packet from one FRR
|
||||||
to other
|
to other
|
||||||
@ -2042,8 +2042,6 @@ def scapy_send_bsr_raw_packet(
|
|||||||
* `senderRouter` : Sender router
|
* `senderRouter` : Sender router
|
||||||
* `receiverRouter` : Receiver router
|
* `receiverRouter` : Receiver router
|
||||||
* `packet` : BSR packet in raw format
|
* `packet` : BSR packet in raw format
|
||||||
* `interval` : Interval between the packets
|
|
||||||
* `count` : Number of packets to be sent
|
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
--------
|
--------
|
||||||
@ -2054,7 +2052,9 @@ def scapy_send_bsr_raw_packet(
|
|||||||
result = ""
|
result = ""
|
||||||
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
|
||||||
|
|
||||||
rnode = tgen.routers()[senderRouter]
|
python3_path = tgen.net.get_exec_path(["python3", "python"])
|
||||||
|
script_path = os.path.join(CWD, "send_bsr_packet.py")
|
||||||
|
node = tgen.net[senderRouter]
|
||||||
|
|
||||||
for destLink, data in topo["routers"][senderRouter]["links"].items():
|
for destLink, data in topo["routers"][senderRouter]["links"].items():
|
||||||
if "type" in data and data["type"] == "loopback":
|
if "type" in data and data["type"] == "loopback":
|
||||||
@ -2065,26 +2065,16 @@ def scapy_send_bsr_raw_packet(
|
|||||||
|
|
||||||
packet = topo["routers"][senderRouter]["bsm"]["bsr_packets"][packet]["data"]
|
packet = topo["routers"][senderRouter]["bsm"]["bsr_packets"][packet]["data"]
|
||||||
|
|
||||||
if interval > 1 or count > 1:
|
cmd = [
|
||||||
cmd = (
|
python3_path,
|
||||||
"nohup /usr/bin/python {}/send_bsr_packet.py '{}' '{}' "
|
script_path,
|
||||||
"--interval={} --count={} &".format(
|
packet,
|
||||||
CWD, packet, sender_interface, interval, count
|
sender_interface,
|
||||||
)
|
"--interval=1",
|
||||||
)
|
"--count=1",
|
||||||
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)
|
logger.info("Scapy cmd: \n %s", cmd)
|
||||||
result = rnode.run(cmd)
|
node.cmd_raises(cmd)
|
||||||
|
|
||||||
if result == "":
|
|
||||||
return result
|
|
||||||
|
|
||||||
logger.debug("Exiting lib API: scapy_send_bsr_raw_packet")
|
logger.debug("Exiting lib API: scapy_send_bsr_raw_packet")
|
||||||
return True
|
return True
|
||||||
|
@ -30,7 +30,7 @@ Basic usage instructions:
|
|||||||
* see tests/topotest/simple-snmp-test/test_simple_snmp.py for example
|
* see tests/topotest/simple-snmp-test/test_simple_snmp.py for example
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from topolog import logger
|
from lib.topolog import logger
|
||||||
|
|
||||||
|
|
||||||
class SnmpTester(object):
|
class SnmpTester(object):
|
||||||
@ -93,7 +93,7 @@ class SnmpTester(object):
|
|||||||
return tokens[0].split(".", 1)[1]
|
return tokens[0].split(".", 1)[1]
|
||||||
|
|
||||||
def _parse_multiline(self, snmp_output):
|
def _parse_multiline(self, snmp_output):
|
||||||
results = snmp_output.strip().split("\r\n")
|
results = snmp_output.strip().split("\n")
|
||||||
|
|
||||||
out_dict = {}
|
out_dict = {}
|
||||||
out_list = []
|
out_list = []
|
||||||
|
@ -38,31 +38,30 @@ Basic usage instructions:
|
|||||||
* After running stop Mininet with: tgen.stop_topology()
|
* After running stop Mininet with: tgen.stop_topology()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import grp
|
||||||
import sys
|
import inspect
|
||||||
import io
|
|
||||||
import logging
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import pwd
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
if sys.version_info[0] > 2:
|
if sys.version_info[0] > 2:
|
||||||
import configparser
|
import configparser
|
||||||
else:
|
else:
|
||||||
import ConfigParser as configparser
|
import ConfigParser as configparser
|
||||||
|
|
||||||
import glob
|
import lib.topolog as topolog
|
||||||
import grp
|
from lib.micronet import Commander
|
||||||
import platform
|
from lib.micronet_compat import Mininet
|
||||||
import pwd
|
from lib.topolog import logger
|
||||||
import subprocess
|
from lib.topotest import g_extra_config
|
||||||
import pytest
|
|
||||||
|
|
||||||
from mininet.net import Mininet
|
|
||||||
from mininet.log import setLogLevel
|
|
||||||
from mininet.cli import CLI
|
|
||||||
|
|
||||||
from lib import topotest
|
from lib import topotest
|
||||||
from lib.topolog import logger, logger_config
|
|
||||||
from lib.topotest import set_sysctl
|
|
||||||
|
|
||||||
CWD = os.path.dirname(os.path.realpath(__file__))
|
CWD = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
@ -89,6 +88,51 @@ def set_topogen(tgen):
|
|||||||
global_tgen = tgen
|
global_tgen = tgen
|
||||||
|
|
||||||
|
|
||||||
|
def is_string(value):
|
||||||
|
"""Return True if value is a string."""
|
||||||
|
try:
|
||||||
|
return isinstance(value, basestring) # type: ignore
|
||||||
|
except NameError:
|
||||||
|
return isinstance(value, str)
|
||||||
|
|
||||||
|
|
||||||
|
def get_exabgp_cmd(commander=None):
|
||||||
|
"""Return the command to use for ExaBGP version < 4."""
|
||||||
|
|
||||||
|
if commander is None:
|
||||||
|
commander = Commander("topogen")
|
||||||
|
|
||||||
|
def exacmd_version_ok(exacmd):
|
||||||
|
logger.debug("checking %s for exabgp < version 4", exacmd)
|
||||||
|
_, stdout, _ = commander.cmd_status(exacmd + " -v", warn=False)
|
||||||
|
m = re.search(r"ExaBGP\s*:\s*((\d+)\.(\d+)(?:\.(\d+))?)", stdout)
|
||||||
|
if not m:
|
||||||
|
return False
|
||||||
|
version = m.group(1)
|
||||||
|
if topotest.version_cmp(version, "4") >= 0:
|
||||||
|
logging.debug(
|
||||||
|
"found exabgp version >= 4 in %s will keep looking", exacmd
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
logger.info("Using ExaBGP version %s in %s", version, exacmd)
|
||||||
|
return True
|
||||||
|
|
||||||
|
exacmd = commander.get_exec_path("exabgp")
|
||||||
|
if exacmd and exacmd_version_ok(exacmd):
|
||||||
|
return exacmd
|
||||||
|
py2_path = commander.get_exec_path("python2")
|
||||||
|
if py2_path:
|
||||||
|
exacmd = py2_path + " -m exabgp"
|
||||||
|
if exacmd_version_ok(exacmd):
|
||||||
|
return exacmd
|
||||||
|
py2_path = commander.get_exec_path("python")
|
||||||
|
if py2_path:
|
||||||
|
exacmd = py2_path + " -m exabgp"
|
||||||
|
if exacmd_version_ok(exacmd):
|
||||||
|
return exacmd
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Main class: topology builder
|
# Main class: topology builder
|
||||||
#
|
#
|
||||||
@ -107,10 +151,12 @@ class Topogen(object):
|
|||||||
|
|
||||||
CONFIG_SECTION = "topogen"
|
CONFIG_SECTION = "topogen"
|
||||||
|
|
||||||
def __init__(self, cls, modname="unnamed"):
|
def __init__(self, topodef, modname="unnamed"):
|
||||||
"""
|
"""
|
||||||
Topogen initialization function, takes the following arguments:
|
Topogen initialization function, takes the following arguments:
|
||||||
* `cls`: the topology class that is child of mininet.topo
|
* `cls`: OLD:uthe topology class that is child of mininet.topo or a build function.
|
||||||
|
* `topodef`: A dictionary defining the topology, a filename of a json file, or a
|
||||||
|
function that will do the same
|
||||||
* `modname`: module name must be a unique name to identify logs later.
|
* `modname`: module name must be a unique name to identify logs later.
|
||||||
"""
|
"""
|
||||||
self.config = None
|
self.config = None
|
||||||
@ -123,16 +169,22 @@ class Topogen(object):
|
|||||||
self.errorsd = {}
|
self.errorsd = {}
|
||||||
self.errors = ""
|
self.errors = ""
|
||||||
self.peern = 1
|
self.peern = 1
|
||||||
self._init_topo(cls)
|
self.cfg_gen = 0
|
||||||
|
self.exabgp_cmd = None
|
||||||
|
self._init_topo(topodef)
|
||||||
|
|
||||||
logger.info("loading topology: {}".format(self.modname))
|
logger.info("loading topology: {}".format(self.modname))
|
||||||
|
|
||||||
@staticmethod
|
# @staticmethod
|
||||||
def _mininet_reset():
|
# def _mininet_reset():
|
||||||
"Reset the mininet environment"
|
# "Reset the mininet environment"
|
||||||
# Clean up the mininet environment
|
# # Clean up the mininet environment
|
||||||
os.system("mn -c > /dev/null 2>&1")
|
# os.system("mn -c > /dev/null 2>&1")
|
||||||
|
|
||||||
def _init_topo(self, cls):
|
def __str__(self):
|
||||||
|
return "Topogen()"
|
||||||
|
|
||||||
|
def _init_topo(self, topodef):
|
||||||
"""
|
"""
|
||||||
Initialize the topogily provided by the user. The user topology class
|
Initialize the topogily provided by the user. The user topology class
|
||||||
must call get_topogen() during build() to get the topogen object.
|
must call get_topogen() during build() to get the topogen object.
|
||||||
@ -140,6 +192,9 @@ class Topogen(object):
|
|||||||
# Set the global variable so the test cases can access it anywhere
|
# Set the global variable so the test cases can access it anywhere
|
||||||
set_topogen(self)
|
set_topogen(self)
|
||||||
|
|
||||||
|
# Increase host based limits
|
||||||
|
topotest.fix_host_limits()
|
||||||
|
|
||||||
# Test for MPLS Kernel modules available
|
# Test for MPLS Kernel modules available
|
||||||
self.hasmpls = False
|
self.hasmpls = False
|
||||||
if not topotest.module_present("mpls-router"):
|
if not topotest.module_present("mpls-router"):
|
||||||
@ -148,15 +203,90 @@ class Topogen(object):
|
|||||||
logger.info("MPLS tests will not run (missing mpls-iptunnel kernel module)")
|
logger.info("MPLS tests will not run (missing mpls-iptunnel kernel module)")
|
||||||
else:
|
else:
|
||||||
self.hasmpls = True
|
self.hasmpls = True
|
||||||
|
|
||||||
# Load the default topology configurations
|
# Load the default topology configurations
|
||||||
self._load_config()
|
self._load_config()
|
||||||
|
|
||||||
# Initialize the API
|
# Create new log directory
|
||||||
self._mininet_reset()
|
self.logdir = topotest.get_logs_path(g_extra_config["rundir"])
|
||||||
cls()
|
subprocess.check_call("mkdir -p {0} && chmod 1777 {0}".format(self.logdir), shell=True)
|
||||||
|
try:
|
||||||
|
routertype = self.config.get(self.CONFIG_SECTION, "routertype")
|
||||||
|
# Only allow group, if it exist.
|
||||||
|
gid = grp.getgrnam(routertype)[2]
|
||||||
|
os.chown(self.logdir, 0, gid)
|
||||||
|
os.chmod(self.logdir, 0o775)
|
||||||
|
except KeyError:
|
||||||
|
# Allow anyone, but set the sticky bit to avoid file deletions
|
||||||
|
os.chmod(self.logdir, 0o1777)
|
||||||
|
|
||||||
|
# Old twisty way of creating sub-classed topology object which has it's build
|
||||||
|
# method invoked which calls Topogen methods which then call Topo methods to
|
||||||
|
# create a topology within the Topo object, which is then used by
|
||||||
|
# Mininet(Micronet) to build the actual topology.
|
||||||
|
if inspect.isclass(topodef):
|
||||||
|
self.topo = topodef()
|
||||||
|
|
||||||
self.net = Mininet(controller=None, topo=self.topo)
|
self.net = Mininet(controller=None, topo=self.topo)
|
||||||
for gear in self.gears.values():
|
|
||||||
gear.net = self.net
|
# New direct way: Either a dictionary defines the topology or a build function
|
||||||
|
# is supplied, or a json filename all of which build the topology by calling
|
||||||
|
# Topogen methods which call Mininet(Micronet) methods to create the actual
|
||||||
|
# topology.
|
||||||
|
if not inspect.isclass(topodef):
|
||||||
|
if callable(topodef):
|
||||||
|
topodef(self)
|
||||||
|
self.net.configure_hosts()
|
||||||
|
elif is_string(topodef):
|
||||||
|
# topojson imports topogen in one function too,
|
||||||
|
# switch away from this use here to the topojson
|
||||||
|
# fixutre and remove this case
|
||||||
|
from lib.topojson import build_topo_from_json
|
||||||
|
with open(topodef, "r") as topof:
|
||||||
|
self.json_topo = json.load(topof)
|
||||||
|
build_topo_from_json(self, self.json_topo)
|
||||||
|
self.net.configure_hosts()
|
||||||
|
elif topodef:
|
||||||
|
self.add_topology_from_dict(topodef)
|
||||||
|
|
||||||
|
def add_topology_from_dict(self, topodef):
|
||||||
|
|
||||||
|
keylist = topodef.keys() if isinstance(topodef, OrderedDict) else sorted(topodef.keys())
|
||||||
|
# ---------------------------
|
||||||
|
# Create all referenced hosts
|
||||||
|
# ---------------------------
|
||||||
|
for oname in keylist:
|
||||||
|
tup = (topodef[oname],) if is_string(topodef[oname]) else topodef[oname]
|
||||||
|
for e in tup:
|
||||||
|
desc = e.split(":")
|
||||||
|
name = desc[0]
|
||||||
|
if name not in self.gears:
|
||||||
|
logging.debug("Adding router: %s", name)
|
||||||
|
self.add_router(name)
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Create all referenced switches
|
||||||
|
# ------------------------------
|
||||||
|
for oname in keylist:
|
||||||
|
if oname is not None and oname not in self.gears:
|
||||||
|
logging.debug("Adding switch: %s", oname)
|
||||||
|
self.add_switch(oname)
|
||||||
|
|
||||||
|
# ----------------
|
||||||
|
# Create all links
|
||||||
|
# ----------------
|
||||||
|
for oname in keylist:
|
||||||
|
if oname is None:
|
||||||
|
continue
|
||||||
|
tup = (topodef[oname],) if is_string(topodef[oname]) else topodef[oname]
|
||||||
|
for e in tup:
|
||||||
|
desc = e.split(":")
|
||||||
|
name = desc[0]
|
||||||
|
ifname = desc[1] if len(desc) > 1 else None
|
||||||
|
sifname = desc[2] if len(desc) > 2 else None
|
||||||
|
self.add_link(self.gears[oname], self.gears[name], sifname, ifname)
|
||||||
|
|
||||||
|
self.net.configure_hosts()
|
||||||
|
|
||||||
def _load_config(self):
|
def _load_config(self):
|
||||||
"""
|
"""
|
||||||
@ -167,7 +297,7 @@ class Topogen(object):
|
|||||||
pytestini_path = os.path.join(CWD, "../pytest.ini")
|
pytestini_path = os.path.join(CWD, "../pytest.ini")
|
||||||
self.config.read(pytestini_path)
|
self.config.read(pytestini_path)
|
||||||
|
|
||||||
def add_router(self, name=None, cls=topotest.Router, **params):
|
def add_router(self, name=None, cls=None, **params):
|
||||||
"""
|
"""
|
||||||
Adds a new router to the topology. This function has the following
|
Adds a new router to the topology. This function has the following
|
||||||
options:
|
options:
|
||||||
@ -176,6 +306,8 @@ class Topogen(object):
|
|||||||
* `routertype`: (optional) `frr`
|
* `routertype`: (optional) `frr`
|
||||||
Returns a TopoRouter.
|
Returns a TopoRouter.
|
||||||
"""
|
"""
|
||||||
|
if cls is None:
|
||||||
|
cls = topotest.Router
|
||||||
if name is None:
|
if name is None:
|
||||||
name = "r{}".format(self.routern)
|
name = "r{}".format(self.routern)
|
||||||
if name in self.gears:
|
if name in self.gears:
|
||||||
@ -190,7 +322,7 @@ class Topogen(object):
|
|||||||
self.routern += 1
|
self.routern += 1
|
||||||
return self.gears[name]
|
return self.gears[name]
|
||||||
|
|
||||||
def add_switch(self, name=None, cls=topotest.LegacySwitch):
|
def add_switch(self, name=None):
|
||||||
"""
|
"""
|
||||||
Adds a new switch to the topology. This function has the following
|
Adds a new switch to the topology. This function has the following
|
||||||
options:
|
options:
|
||||||
@ -202,7 +334,7 @@ class Topogen(object):
|
|||||||
if name in self.gears:
|
if name in self.gears:
|
||||||
raise KeyError("switch already exists")
|
raise KeyError("switch already exists")
|
||||||
|
|
||||||
self.gears[name] = TopoSwitch(self, cls, name)
|
self.gears[name] = TopoSwitch(self, name)
|
||||||
self.switchn += 1
|
self.switchn += 1
|
||||||
return self.gears[name]
|
return self.gears[name]
|
||||||
|
|
||||||
@ -258,6 +390,9 @@ class Topogen(object):
|
|||||||
|
|
||||||
node1.register_link(ifname1, node2, ifname2)
|
node1.register_link(ifname1, node2, ifname2)
|
||||||
node2.register_link(ifname2, node1, ifname1)
|
node2.register_link(ifname2, node1, ifname1)
|
||||||
|
if self.net:
|
||||||
|
self.net.add_link(node1.name, node2.name, ifname1, ifname2)
|
||||||
|
else:
|
||||||
self.topo.addLink(node1.name, node2.name, intfName1=ifname1, intfName2=ifname2)
|
self.topo.addLink(node1.name, node2.name, intfName1=ifname1, intfName2=ifname2)
|
||||||
|
|
||||||
def get_gears(self, geartype):
|
def get_gears(self, geartype):
|
||||||
@ -300,27 +435,8 @@ class Topogen(object):
|
|||||||
"""
|
"""
|
||||||
return self.get_gears(TopoExaBGP)
|
return self.get_gears(TopoExaBGP)
|
||||||
|
|
||||||
def start_topology(self, log_level=None):
|
def start_topology(self):
|
||||||
"""
|
"""Starts the topology class."""
|
||||||
Starts the topology class. Possible `log_level`s are:
|
|
||||||
'debug': all information possible
|
|
||||||
'info': informational messages
|
|
||||||
'output': default logging level defined by Mininet
|
|
||||||
'warning': only warning, error and critical messages
|
|
||||||
'error': only error and critical messages
|
|
||||||
'critical': only critical messages
|
|
||||||
"""
|
|
||||||
# If log_level is not specified use the configuration.
|
|
||||||
if log_level is None:
|
|
||||||
log_level = self.config.get(self.CONFIG_SECTION, "verbosity")
|
|
||||||
|
|
||||||
# Set python logger level
|
|
||||||
logger_config.set_log_level(log_level)
|
|
||||||
|
|
||||||
# Run mininet
|
|
||||||
if log_level == "debug":
|
|
||||||
setLogLevel(log_level)
|
|
||||||
|
|
||||||
logger.info("starting topology: {}".format(self.modname))
|
logger.info("starting topology: {}".format(self.modname))
|
||||||
self.net.start()
|
self.net.start()
|
||||||
|
|
||||||
@ -331,6 +447,7 @@ class Topogen(object):
|
|||||||
"""
|
"""
|
||||||
if router is None:
|
if router is None:
|
||||||
# pylint: disable=r1704
|
# pylint: disable=r1704
|
||||||
|
# XXX should be hosts?
|
||||||
for _, router in self.routers().items():
|
for _, router in self.routers().items():
|
||||||
router.start()
|
router.start()
|
||||||
else:
|
else:
|
||||||
@ -358,17 +475,19 @@ class Topogen(object):
|
|||||||
|
|
||||||
self.net.stop()
|
self.net.stop()
|
||||||
|
|
||||||
def mininet_cli(self):
|
def get_exabgp_cmd(self):
|
||||||
|
if not self.exabgp_cmd:
|
||||||
|
self.exabgp_cmd = get_exabgp_cmd(self.net)
|
||||||
|
return self.exabgp_cmd
|
||||||
|
|
||||||
|
def cli(self):
|
||||||
"""
|
"""
|
||||||
Interrupt the test and call the command line interface for manual
|
Interrupt the test and call the command line interface for manual
|
||||||
inspection. Should be only used on non production code.
|
inspection. Should be only used on non production code.
|
||||||
"""
|
"""
|
||||||
if not sys.stdin.isatty():
|
self.net.cli()
|
||||||
raise EnvironmentError(
|
|
||||||
"you must run pytest with '-s' in order to use mininet CLI"
|
|
||||||
)
|
|
||||||
|
|
||||||
CLI(self.net)
|
mininet_cli = cli
|
||||||
|
|
||||||
def is_memleak_enabled(self):
|
def is_memleak_enabled(self):
|
||||||
"Returns `True` if memory leak report is enable, otherwise `False`."
|
"Returns `True` if memory leak report is enable, otherwise `False`."
|
||||||
@ -438,13 +557,18 @@ class Topogen(object):
|
|||||||
class TopoGear(object):
|
class TopoGear(object):
|
||||||
"Abstract class for type checking"
|
"Abstract class for type checking"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, tgen, name, **params):
|
||||||
self.tgen = None
|
self.tgen = tgen
|
||||||
self.name = None
|
self.name = name
|
||||||
self.cls = None
|
self.params = params
|
||||||
self.links = {}
|
self.links = {}
|
||||||
self.linkn = 0
|
self.linkn = 0
|
||||||
|
|
||||||
|
# Would be nice for this to point at the gears log directory rather than the
|
||||||
|
# test's.
|
||||||
|
self.logdir = tgen.logdir
|
||||||
|
self.gearlogdir = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
links = ""
|
links = ""
|
||||||
for myif, dest in self.links.items():
|
for myif, dest in self.links.items():
|
||||||
@ -455,27 +579,42 @@ class TopoGear(object):
|
|||||||
|
|
||||||
return 'TopoGear<name="{}",links=[{}]>'.format(self.name, links)
|
return 'TopoGear<name="{}",links=[{}]>'.format(self.name, links)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def net(self):
|
||||||
|
return self.tgen.net[self.name]
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"Basic start function that just reports equipment start"
|
"Basic start function that just reports equipment start"
|
||||||
logger.info('starting "{}"'.format(self.name))
|
logger.info('starting "{}"'.format(self.name))
|
||||||
|
|
||||||
def stop(self, wait=True, assertOnError=True):
|
def stop(self, wait=True, assertOnError=True):
|
||||||
"Basic start function that just reports equipment stop"
|
"Basic stop function that just reports equipment stop"
|
||||||
logger.info('stopping "{}"'.format(self.name))
|
logger.info('"{}" base stop called'.format(self.name))
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def run(self, command):
|
def cmd(self, command, **kwargs):
|
||||||
"""
|
"""
|
||||||
Runs the provided command string in the router and returns a string
|
Runs the provided command string in the router and returns a string
|
||||||
with the response.
|
with the response.
|
||||||
"""
|
"""
|
||||||
return self.tgen.net[self.name].cmd(command)
|
return self.net.cmd_legacy(command, **kwargs)
|
||||||
|
|
||||||
|
def cmd_raises(self, command, **kwargs):
|
||||||
|
"""
|
||||||
|
Runs the provided command string in the router and returns a string
|
||||||
|
with the response. Raise an exception on any error.
|
||||||
|
"""
|
||||||
|
return self.net.cmd_raises(command, **kwargs)
|
||||||
|
|
||||||
|
run = cmd
|
||||||
|
|
||||||
def popen(self, *params, **kwargs):
|
def popen(self, *params, **kwargs):
|
||||||
"""
|
"""
|
||||||
Popen on the router.
|
Creates a pipe with the given command. Same args as python Popen.
|
||||||
|
If `command` is a string then will be invoked with shell, otherwise
|
||||||
|
`command` is a list and will be invoked w/o shell. Returns a popen object.
|
||||||
"""
|
"""
|
||||||
return self.tgen.net[self.name].popen(*params, **kwargs)
|
return self.net.popen(*params, **kwargs)
|
||||||
|
|
||||||
def add_link(self, node, myif=None, nodeif=None):
|
def add_link(self, node, myif=None, nodeif=None):
|
||||||
"""
|
"""
|
||||||
@ -508,6 +647,7 @@ class TopoGear(object):
|
|||||||
extract = ""
|
extract = ""
|
||||||
if netns is not None:
|
if netns is not None:
|
||||||
extract = "ip netns exec {} ".format(netns)
|
extract = "ip netns exec {} ".format(netns)
|
||||||
|
|
||||||
return self.run("{}ip link set dev {} {}".format(extract, myif, operation))
|
return self.run("{}ip link set dev {} {}".format(extract, myif, operation))
|
||||||
|
|
||||||
def peer_link_enable(self, myif, enabled=True, netns=None):
|
def peer_link_enable(self, myif, enabled=True, netns=None):
|
||||||
@ -546,6 +686,11 @@ class TopoGear(object):
|
|||||||
|
|
||||||
self.links[myif] = (node, nodeif)
|
self.links[myif] = (node, nodeif)
|
||||||
|
|
||||||
|
def _setup_tmpdir(self):
|
||||||
|
topotest.setup_node_tmpdir(self.logdir, self.name)
|
||||||
|
self.gearlogdir = "{}/{}".format(self.logdir, self.name)
|
||||||
|
return "{}/{}.log".format(self.logdir, self.name)
|
||||||
|
|
||||||
|
|
||||||
class TopoRouter(TopoGear):
|
class TopoRouter(TopoGear):
|
||||||
"""
|
"""
|
||||||
@ -555,6 +700,7 @@ class TopoRouter(TopoGear):
|
|||||||
# The default required directories by FRR
|
# The default required directories by FRR
|
||||||
PRIVATE_DIRS = [
|
PRIVATE_DIRS = [
|
||||||
"/etc/frr",
|
"/etc/frr",
|
||||||
|
"/etc/snmp",
|
||||||
"/var/run/frr",
|
"/var/run/frr",
|
||||||
"/var/log",
|
"/var/log",
|
||||||
]
|
]
|
||||||
@ -608,66 +754,28 @@ class TopoRouter(TopoGear):
|
|||||||
* daemondir: daemon binary directory
|
* daemondir: daemon binary directory
|
||||||
* routertype: 'frr'
|
* routertype: 'frr'
|
||||||
"""
|
"""
|
||||||
super(TopoRouter, self).__init__()
|
super(TopoRouter, self).__init__(tgen, name, **params)
|
||||||
self.tgen = tgen
|
|
||||||
self.net = None
|
|
||||||
self.name = name
|
|
||||||
self.cls = cls
|
|
||||||
self.options = {}
|
|
||||||
self.routertype = params.get("routertype", "frr")
|
self.routertype = params.get("routertype", "frr")
|
||||||
if "privateDirs" not in params:
|
if "privateDirs" not in params:
|
||||||
params["privateDirs"] = self.PRIVATE_DIRS
|
params["privateDirs"] = self.PRIVATE_DIRS
|
||||||
|
|
||||||
self.options["memleak_path"] = params.get("memleak_path", None)
|
|
||||||
|
|
||||||
# Create new log directory
|
|
||||||
self.logdir = "/tmp/topotests/{}".format(self.tgen.modname)
|
|
||||||
# Clean up before starting new log files: avoids removing just created
|
|
||||||
# log files.
|
|
||||||
self._prepare_tmpfiles()
|
|
||||||
# Propagate the router log directory
|
# Propagate the router log directory
|
||||||
|
logfile = self._setup_tmpdir()
|
||||||
params["logdir"] = self.logdir
|
params["logdir"] = self.logdir
|
||||||
|
|
||||||
# setup the per node directory
|
self.logger = topolog.get_logger(name, log_level="debug", target=logfile)
|
||||||
dir = "{}/{}".format(self.logdir, self.name)
|
params["logger"] = self.logger
|
||||||
os.system("mkdir -p " + dir)
|
tgen.net.add_host(self.name, cls=cls, **params)
|
||||||
os.system("chmod -R go+rw /tmp/topotests")
|
topotest.fix_netns_limits(tgen.net[name])
|
||||||
|
|
||||||
# Open router log file
|
# Mount gear log directory on a common path
|
||||||
logfile = "{0}/{1}.log".format(self.logdir, name)
|
self.net.bind_mount(self.gearlogdir, "/tmp/gearlogdir")
|
||||||
self.logger = logger_config.get_logger(name=name, target=logfile)
|
|
||||||
|
|
||||||
self.tgen.topo.addNode(self.name, cls=self.cls, **params)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
gear = super(TopoRouter, self).__str__()
|
gear = super(TopoRouter, self).__str__()
|
||||||
gear += " TopoRouter<>"
|
gear += " TopoRouter<>"
|
||||||
return gear
|
return gear
|
||||||
|
|
||||||
def _prepare_tmpfiles(self):
|
|
||||||
# Create directories if they don't exist
|
|
||||||
try:
|
|
||||||
os.makedirs(self.logdir, 0o755)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Allow unprivileged daemon user (frr) to create log files
|
|
||||||
try:
|
|
||||||
# Only allow group, if it exist.
|
|
||||||
gid = grp.getgrnam(self.routertype)[2]
|
|
||||||
os.chown(self.logdir, 0, gid)
|
|
||||||
os.chmod(self.logdir, 0o775)
|
|
||||||
except KeyError:
|
|
||||||
# Allow anyone, but set the sticky bit to avoid file deletions
|
|
||||||
os.chmod(self.logdir, 0o1777)
|
|
||||||
|
|
||||||
# Try to find relevant old logfiles in /tmp and delete them
|
|
||||||
map(os.remove, glob.glob("{}/{}/*.log".format(self.logdir, self.name)))
|
|
||||||
# Remove old valgrind files
|
|
||||||
map(os.remove, glob.glob("{}/{}.valgrind.*".format(self.logdir, self.name)))
|
|
||||||
# Remove old core files
|
|
||||||
map(os.remove, glob.glob("{}/{}/*.dmp".format(self.logdir, self.name)))
|
|
||||||
|
|
||||||
def check_capability(self, daemon, param):
|
def check_capability(self, daemon, param):
|
||||||
"""
|
"""
|
||||||
Checks a capability daemon against an argument option
|
Checks a capability daemon against an argument option
|
||||||
@ -675,7 +783,7 @@ class TopoRouter(TopoGear):
|
|||||||
"""
|
"""
|
||||||
daemonstr = self.RD.get(daemon)
|
daemonstr = self.RD.get(daemon)
|
||||||
self.logger.info('check capability {} for "{}"'.format(param, daemonstr))
|
self.logger.info('check capability {} for "{}"'.format(param, daemonstr))
|
||||||
return self.tgen.net[self.name].checkCapability(daemonstr, param)
|
return self.net.checkCapability(daemonstr, param)
|
||||||
|
|
||||||
def load_config(self, daemon, source=None, param=None):
|
def load_config(self, daemon, source=None, param=None):
|
||||||
"""
|
"""
|
||||||
@ -684,17 +792,20 @@ class TopoRouter(TopoGear):
|
|||||||
TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
|
TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
|
||||||
TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
|
TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
|
||||||
TopoRouter.RD_PIM, TopoRouter.RD_PBR, TopoRouter.RD_SNMP.
|
TopoRouter.RD_PIM, TopoRouter.RD_PBR, TopoRouter.RD_SNMP.
|
||||||
|
|
||||||
|
This API unfortunately allows for source to not exist for any and
|
||||||
|
all routers.
|
||||||
"""
|
"""
|
||||||
daemonstr = self.RD.get(daemon)
|
daemonstr = self.RD.get(daemon)
|
||||||
self.logger.info('loading "{}" configuration: {}'.format(daemonstr, source))
|
self.logger.info('loading "{}" configuration: {}'.format(daemonstr, source))
|
||||||
self.tgen.net[self.name].loadConf(daemonstr, source, param)
|
self.net.loadConf(daemonstr, source, param)
|
||||||
|
|
||||||
def check_router_running(self):
|
def check_router_running(self):
|
||||||
"""
|
"""
|
||||||
Run a series of checks and returns a status string.
|
Run a series of checks and returns a status string.
|
||||||
"""
|
"""
|
||||||
self.logger.info("checking if daemons are running")
|
self.logger.info("checking if daemons are running")
|
||||||
return self.tgen.net[self.name].checkRouterRunning()
|
return self.net.checkRouterRunning()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""
|
"""
|
||||||
@ -705,46 +816,41 @@ class TopoRouter(TopoGear):
|
|||||||
* Start daemons (e.g. FRR)
|
* Start daemons (e.g. FRR)
|
||||||
* Configure daemon logging files
|
* Configure daemon logging files
|
||||||
"""
|
"""
|
||||||
self.logger.debug("starting")
|
|
||||||
nrouter = self.tgen.net[self.name]
|
nrouter = self.net
|
||||||
result = nrouter.startRouter(self.tgen)
|
result = nrouter.startRouter(self.tgen)
|
||||||
|
|
||||||
|
# Enable command logging
|
||||||
|
|
||||||
# Enable all daemon command logging, logging files
|
# Enable all daemon command logging, logging files
|
||||||
# and set them to the start dir.
|
# and set them to the start dir.
|
||||||
for daemon, enabled in nrouter.daemons.items():
|
for daemon, enabled in nrouter.daemons.items():
|
||||||
if enabled == 0:
|
if enabled and daemon != "snmpd":
|
||||||
continue
|
|
||||||
self.vtysh_cmd(
|
self.vtysh_cmd(
|
||||||
"configure terminal\nlog commands\nlog file {}.log".format(daemon),
|
"\n".join(["clear log cmdline-targets",
|
||||||
|
"conf t",
|
||||||
|
"log file {}.log debug".format(daemon),
|
||||||
|
"log commands",
|
||||||
|
"log timestamp precision 3"]),
|
||||||
daemon=daemon,
|
daemon=daemon,
|
||||||
)
|
)
|
||||||
|
|
||||||
if result != "":
|
if result != "":
|
||||||
self.tgen.set_error(result)
|
self.tgen.set_error(result)
|
||||||
else:
|
elif nrouter.daemons["ldpd"] == 1 or nrouter.daemons["pathd"] == 1:
|
||||||
# Enable MPLS processing on all interfaces.
|
# Enable MPLS processing on all interfaces.
|
||||||
for interface in self.links.keys():
|
for interface in self.links:
|
||||||
set_sysctl(nrouter, "net.mpls.conf.{}.input".format(interface), 1)
|
topotest.sysctl_assure(nrouter, "net.mpls.conf.{}.input".format(interface), 1)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __stop_internal(self, wait=True, assertOnError=True):
|
|
||||||
"""
|
|
||||||
Stop router, private internal version
|
|
||||||
* Kill daemons
|
|
||||||
"""
|
|
||||||
self.logger.debug("stopping: wait {}, assert {}".format(wait, assertOnError))
|
|
||||||
return self.tgen.net[self.name].stopRouter(wait, assertOnError)
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""
|
"""
|
||||||
Stop router cleanly:
|
Stop router cleanly:
|
||||||
* Signal daemons twice, once without waiting, and then a second time
|
* Signal daemons twice, once with SIGTERM, then with SIGKILL.
|
||||||
with a wait to ensure the daemons exit cleanly
|
|
||||||
"""
|
"""
|
||||||
self.logger.debug("stopping")
|
self.logger.debug("stopping (no assert)")
|
||||||
self.__stop_internal(False, False)
|
return self.net.stopRouter(False)
|
||||||
return self.__stop_internal(True, False)
|
|
||||||
|
|
||||||
def startDaemons(self, daemons):
|
def startDaemons(self, daemons):
|
||||||
"""
|
"""
|
||||||
@ -753,17 +859,23 @@ class TopoRouter(TopoGear):
|
|||||||
* Configure daemon logging files
|
* Configure daemon logging files
|
||||||
"""
|
"""
|
||||||
self.logger.debug("starting")
|
self.logger.debug("starting")
|
||||||
nrouter = self.tgen.net[self.name]
|
nrouter = self.net
|
||||||
result = nrouter.startRouterDaemons(daemons)
|
result = nrouter.startRouterDaemons(daemons)
|
||||||
|
|
||||||
|
if daemons is None:
|
||||||
|
daemons = nrouter.daemons.keys()
|
||||||
|
|
||||||
# Enable all daemon command logging, logging files
|
# Enable all daemon command logging, logging files
|
||||||
# and set them to the start dir.
|
# and set them to the start dir.
|
||||||
for daemon, enabled in nrouter.daemons.items():
|
for daemon in daemons:
|
||||||
for d in daemons:
|
enabled = nrouter.daemons[daemon]
|
||||||
if enabled == 0:
|
if enabled and daemon != "snmpd":
|
||||||
continue
|
|
||||||
self.vtysh_cmd(
|
self.vtysh_cmd(
|
||||||
"configure terminal\nlog commands\nlog file {}.log".format(daemon),
|
"\n".join(["clear log cmdline-targets",
|
||||||
|
"conf t",
|
||||||
|
"log file {}.log debug".format(daemon),
|
||||||
|
"log commands",
|
||||||
|
"log timestamp precision 3"]),
|
||||||
daemon=daemon,
|
daemon=daemon,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -778,7 +890,7 @@ class TopoRouter(TopoGear):
|
|||||||
forcefully using SIGKILL
|
forcefully using SIGKILL
|
||||||
"""
|
"""
|
||||||
self.logger.debug("Killing daemons using SIGKILL..")
|
self.logger.debug("Killing daemons using SIGKILL..")
|
||||||
return self.tgen.net[self.name].killRouterDaemons(daemons, wait, assertOnError)
|
return self.net.killRouterDaemons(daemons, wait, assertOnError)
|
||||||
|
|
||||||
def vtysh_cmd(self, command, isjson=False, daemon=None):
|
def vtysh_cmd(self, command, isjson=False, daemon=None):
|
||||||
"""
|
"""
|
||||||
@ -798,10 +910,17 @@ class TopoRouter(TopoGear):
|
|||||||
|
|
||||||
vtysh_command = 'vtysh {} -c "{}" 2>/dev/null'.format(dparam, command)
|
vtysh_command = 'vtysh {} -c "{}" 2>/dev/null'.format(dparam, command)
|
||||||
|
|
||||||
|
self.logger.info('vtysh command => "{}"'.format(command))
|
||||||
output = self.run(vtysh_command)
|
output = self.run(vtysh_command)
|
||||||
self.logger.info(
|
|
||||||
"\nvtysh command => {}\nvtysh output <= {}".format(command, output)
|
dbgout = output.strip()
|
||||||
)
|
if dbgout:
|
||||||
|
if "\n" in dbgout:
|
||||||
|
dbgout = dbgout.replace("\n", "\n\t")
|
||||||
|
self.logger.info('vtysh result:\n\t{}'.format(dbgout))
|
||||||
|
else:
|
||||||
|
self.logger.info('vtysh result: "{}"'.format(dbgout))
|
||||||
|
|
||||||
if isjson is False:
|
if isjson is False:
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@ -833,13 +952,20 @@ class TopoRouter(TopoGear):
|
|||||||
else:
|
else:
|
||||||
vtysh_command = "vtysh {} -f {}".format(dparam, fname)
|
vtysh_command = "vtysh {} -f {}".format(dparam, fname)
|
||||||
|
|
||||||
|
dbgcmds = commands if is_string(commands) else "\n".join(commands)
|
||||||
|
dbgcmds = "\t" + dbgcmds.replace("\n", "\n\t")
|
||||||
|
self.logger.info('vtysh command => FILE:\n{}'.format(dbgcmds))
|
||||||
|
|
||||||
res = self.run(vtysh_command)
|
res = self.run(vtysh_command)
|
||||||
os.unlink(fname)
|
os.unlink(fname)
|
||||||
|
|
||||||
self.logger.info(
|
dbgres = res.strip()
|
||||||
'\nvtysh command => "{}"\nvtysh output <= "{}"'.format(vtysh_command, res)
|
if dbgres:
|
||||||
)
|
if "\n" in dbgres:
|
||||||
|
dbgres = dbgres.replace("\n", "\n\t")
|
||||||
|
self.logger.info('vtysh result:\n\t{}'.format(dbgres))
|
||||||
|
else:
|
||||||
|
self.logger.info('vtysh result: "{}"'.format(dbgres))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def report_memory_leaks(self, testname):
|
def report_memory_leaks(self, testname):
|
||||||
@ -851,7 +977,7 @@ class TopoRouter(TopoGear):
|
|||||||
TOPOTESTS_CHECK_MEMLEAK set or memleak_path configured in `pytest.ini`.
|
TOPOTESTS_CHECK_MEMLEAK set or memleak_path configured in `pytest.ini`.
|
||||||
"""
|
"""
|
||||||
memleak_file = (
|
memleak_file = (
|
||||||
os.environ.get("TOPOTESTS_CHECK_MEMLEAK") or self.options["memleak_path"]
|
os.environ.get("TOPOTESTS_CHECK_MEMLEAK") or self.params["memleak_path"]
|
||||||
)
|
)
|
||||||
if memleak_file == "" or memleak_file == None:
|
if memleak_file == "" or memleak_file == None:
|
||||||
return
|
return
|
||||||
@ -859,7 +985,7 @@ class TopoRouter(TopoGear):
|
|||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
self.logger.info("running memory leak report")
|
self.logger.info("running memory leak report")
|
||||||
self.tgen.net[self.name].report_memory_leaks(memleak_file, testname)
|
self.net.report_memory_leaks(memleak_file, testname)
|
||||||
|
|
||||||
def version_info(self):
|
def version_info(self):
|
||||||
"Get equipment information from 'show version'."
|
"Get equipment information from 'show version'."
|
||||||
@ -888,7 +1014,7 @@ class TopoRouter(TopoGear):
|
|||||||
|
|
||||||
Usage example: router.has_version('>', '1.0')
|
Usage example: router.has_version('>', '1.0')
|
||||||
"""
|
"""
|
||||||
return self.tgen.net[self.name].checkRouterVersion(cmpop, version)
|
return self.net.checkRouterVersion(cmpop, version)
|
||||||
|
|
||||||
def has_type(self, rtype):
|
def has_type(self, rtype):
|
||||||
"""
|
"""
|
||||||
@ -899,8 +1025,7 @@ class TopoRouter(TopoGear):
|
|||||||
return rtype == curtype
|
return rtype == curtype
|
||||||
|
|
||||||
def has_mpls(self):
|
def has_mpls(self):
|
||||||
nrouter = self.tgen.net[self.name]
|
return self.net.hasmpls
|
||||||
return nrouter.hasmpls
|
|
||||||
|
|
||||||
|
|
||||||
class TopoSwitch(TopoGear):
|
class TopoSwitch(TopoGear):
|
||||||
@ -912,13 +1037,9 @@ class TopoSwitch(TopoGear):
|
|||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
def __init__(self, tgen, cls, name):
|
def __init__(self, tgen, name, **params):
|
||||||
super(TopoSwitch, self).__init__()
|
super(TopoSwitch, self).__init__(tgen, name, **params)
|
||||||
self.tgen = tgen
|
tgen.net.add_switch(name)
|
||||||
self.net = None
|
|
||||||
self.name = name
|
|
||||||
self.cls = cls
|
|
||||||
self.tgen.topo.addSwitch(name, cls=self.cls)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
gear = super(TopoSwitch, self).__str__()
|
gear = super(TopoSwitch, self).__str__()
|
||||||
@ -939,19 +1060,27 @@ class TopoHost(TopoGear):
|
|||||||
* `privateDirs`: directories that will be mounted on a different domain
|
* `privateDirs`: directories that will be mounted on a different domain
|
||||||
(e.g. '/etc/important_dir').
|
(e.g. '/etc/important_dir').
|
||||||
"""
|
"""
|
||||||
super(TopoHost, self).__init__()
|
super(TopoHost, self).__init__(tgen, name, **params)
|
||||||
self.tgen = tgen
|
|
||||||
self.net = None
|
# Propagate the router log directory
|
||||||
self.name = name
|
logfile = self._setup_tmpdir()
|
||||||
self.options = params
|
params["logdir"] = self.logdir
|
||||||
self.tgen.topo.addHost(name, **params)
|
|
||||||
|
# Odd to have 2 logfiles for each host
|
||||||
|
self.logger = topolog.get_logger(name, log_level="debug", target=logfile)
|
||||||
|
params["logger"] = self.logger
|
||||||
|
tgen.net.add_host(name, **params)
|
||||||
|
topotest.fix_netns_limits(tgen.net[name])
|
||||||
|
|
||||||
|
# Mount gear log directory on a common path
|
||||||
|
self.net.bind_mount(self.gearlogdir, "/tmp/gearlogdir")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
gear = super(TopoHost, self).__str__()
|
gear = super(TopoHost, self).__str__()
|
||||||
gear += ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format(
|
gear += ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format(
|
||||||
self.options["ip"],
|
self.params["ip"],
|
||||||
self.options["defaultRoute"],
|
self.params["defaultRoute"],
|
||||||
str(self.options["privateDirs"]),
|
str(self.params["privateDirs"]),
|
||||||
)
|
)
|
||||||
return gear
|
return gear
|
||||||
|
|
||||||
@ -979,7 +1108,6 @@ class TopoExaBGP(TopoHost):
|
|||||||
"""
|
"""
|
||||||
params["privateDirs"] = self.PRIVATE_DIRS
|
params["privateDirs"] = self.PRIVATE_DIRS
|
||||||
super(TopoExaBGP, self).__init__(tgen, name, **params)
|
super(TopoExaBGP, self).__init__(tgen, name, **params)
|
||||||
self.tgen.topo.addHost(name, **params)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
gear = super(TopoExaBGP, self).__str__()
|
gear = super(TopoExaBGP, self).__str__()
|
||||||
@ -994,7 +1122,10 @@ class TopoExaBGP(TopoHost):
|
|||||||
* Make all python files runnable
|
* Make all python files runnable
|
||||||
* Run ExaBGP with env file `env_file` and configuration peer*/exabgp.cfg
|
* Run ExaBGP with env file `env_file` and configuration peer*/exabgp.cfg
|
||||||
"""
|
"""
|
||||||
self.run("mkdir /etc/exabgp")
|
exacmd = self.tgen.get_exabgp_cmd()
|
||||||
|
assert exacmd, "Can't find a usabel ExaBGP (must be < version 4)"
|
||||||
|
|
||||||
|
self.run("mkdir -p /etc/exabgp")
|
||||||
self.run("chmod 755 /etc/exabgp")
|
self.run("chmod 755 /etc/exabgp")
|
||||||
self.run("cp {}/* /etc/exabgp/".format(peer_dir))
|
self.run("cp {}/* /etc/exabgp/".format(peer_dir))
|
||||||
if env_file is not None:
|
if env_file is not None:
|
||||||
@ -1002,9 +1133,11 @@ class TopoExaBGP(TopoHost):
|
|||||||
self.run("chmod 644 /etc/exabgp/*")
|
self.run("chmod 644 /etc/exabgp/*")
|
||||||
self.run("chmod a+x /etc/exabgp/*.py")
|
self.run("chmod a+x /etc/exabgp/*.py")
|
||||||
self.run("chown -R exabgp:exabgp /etc/exabgp")
|
self.run("chown -R exabgp:exabgp /etc/exabgp")
|
||||||
output = self.run("exabgp -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg")
|
|
||||||
|
output = self.run(exacmd + " -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg")
|
||||||
if output == None or len(output) == 0:
|
if output == None or len(output) == 0:
|
||||||
output = "<none>"
|
output = "<none>"
|
||||||
|
|
||||||
logger.info("{} exabgp started, output={}".format(self.name, output))
|
logger.info("{} exabgp started, output={}".format(self.name, output))
|
||||||
|
|
||||||
def stop(self, wait=True, assertOnError=True):
|
def stop(self, wait=True, assertOnError=True):
|
||||||
@ -1019,42 +1152,38 @@ class TopoExaBGP(TopoHost):
|
|||||||
|
|
||||||
# Disable linter branch warning. It is expected to have these here.
|
# Disable linter branch warning. It is expected to have these here.
|
||||||
# pylint: disable=R0912
|
# pylint: disable=R0912
|
||||||
def diagnose_env_linux():
|
def diagnose_env_linux(rundir):
|
||||||
"""
|
"""
|
||||||
Run diagnostics in the running environment. Returns `True` when everything
|
Run diagnostics in the running environment. Returns `True` when everything
|
||||||
is ok, otherwise `False`.
|
is ok, otherwise `False`.
|
||||||
"""
|
"""
|
||||||
ret = True
|
ret = True
|
||||||
|
|
||||||
# Test log path exists before installing handler.
|
|
||||||
if not os.path.isdir("/tmp"):
|
|
||||||
logger.warning("could not find /tmp for logs")
|
|
||||||
else:
|
|
||||||
os.system("mkdir -p /tmp/topotests")
|
|
||||||
# Log diagnostics to file so it can be examined later.
|
|
||||||
fhandler = logging.FileHandler(filename="/tmp/topotests/diagnostics.txt")
|
|
||||||
fhandler.setLevel(logging.DEBUG)
|
|
||||||
fhandler.setFormatter(
|
|
||||||
logging.Formatter(fmt="%(asctime)s %(levelname)s: %(message)s")
|
|
||||||
)
|
|
||||||
logger.addHandler(fhandler)
|
|
||||||
|
|
||||||
logger.info("Running environment diagnostics")
|
|
||||||
|
|
||||||
# Load configuration
|
# Load configuration
|
||||||
config = configparser.ConfigParser(defaults=tgen_defaults)
|
config = configparser.ConfigParser(defaults=tgen_defaults)
|
||||||
pytestini_path = os.path.join(CWD, "../pytest.ini")
|
pytestini_path = os.path.join(CWD, "../pytest.ini")
|
||||||
config.read(pytestini_path)
|
config.read(pytestini_path)
|
||||||
|
|
||||||
|
# Test log path exists before installing handler.
|
||||||
|
os.system("mkdir -p " + rundir)
|
||||||
|
# Log diagnostics to file so it can be examined later.
|
||||||
|
fhandler = logging.FileHandler(filename="{}/diagnostics.txt".format(rundir))
|
||||||
|
fhandler.setLevel(logging.DEBUG)
|
||||||
|
fhandler.setFormatter(logging.Formatter(fmt=topolog.FORMAT))
|
||||||
|
logger.addHandler(fhandler)
|
||||||
|
|
||||||
|
logger.info("Running environment diagnostics")
|
||||||
|
|
||||||
|
|
||||||
# Assert that we are running as root
|
# Assert that we are running as root
|
||||||
if os.getuid() != 0:
|
if os.getuid() != 0:
|
||||||
logger.error("you must run topotest as root")
|
logger.error("you must run topotest as root")
|
||||||
ret = False
|
ret = False
|
||||||
|
|
||||||
# Assert that we have mininet
|
# Assert that we have mininet
|
||||||
if os.system("which mn >/dev/null 2>/dev/null") != 0:
|
# if os.system("which mn >/dev/null 2>/dev/null") != 0:
|
||||||
logger.error("could not find mininet binary (mininet is not installed)")
|
# logger.error("could not find mininet binary (mininet is not installed)")
|
||||||
ret = False
|
# ret = False
|
||||||
|
|
||||||
# Assert that we have iproute installed
|
# Assert that we have iproute installed
|
||||||
if os.system("which ip >/dev/null 2>/dev/null") != 0:
|
if os.system("which ip >/dev/null 2>/dev/null") != 0:
|
||||||
@ -1118,7 +1247,7 @@ def diagnose_env_linux():
|
|||||||
if fname != "zebra":
|
if fname != "zebra":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
os.system("{} -v 2>&1 >/tmp/topotests/frr_zebra.txt".format(path))
|
os.system("{} -v 2>&1 >{}/frr_zebra.txt".format(path, rundir))
|
||||||
|
|
||||||
# Test MPLS availability
|
# Test MPLS availability
|
||||||
krel = platform.release()
|
krel = platform.release()
|
||||||
@ -1135,23 +1264,9 @@ def diagnose_env_linux():
|
|||||||
if not topotest.module_present("mpls-iptunnel", load=False) != 0:
|
if not topotest.module_present("mpls-iptunnel", load=False) != 0:
|
||||||
logger.info("LDPd tests will not run (missing mpls-iptunnel kernel module)")
|
logger.info("LDPd tests will not run (missing mpls-iptunnel kernel module)")
|
||||||
|
|
||||||
# TODO remove me when we start supporting exabgp >= 4
|
if not get_exabgp_cmd():
|
||||||
try:
|
logger.warning("Failed to find exabgp < 4")
|
||||||
p = os.popen("exabgp -v")
|
|
||||||
line = p.readlines()
|
|
||||||
version = line[0].split()
|
|
||||||
if topotest.version_cmp(version[2], "4") >= 0:
|
|
||||||
logger.warning(
|
|
||||||
"BGP topologies are still using exabgp version 3, expect failures"
|
|
||||||
)
|
|
||||||
p.close()
|
|
||||||
|
|
||||||
# We want to catch all exceptions
|
|
||||||
# pylint: disable=W0702
|
|
||||||
except:
|
|
||||||
logger.warning("failed to find exabgp or returned error")
|
|
||||||
|
|
||||||
# After we logged the output to file, remove the handler.
|
|
||||||
logger.removeHandler(fhandler)
|
logger.removeHandler(fhandler)
|
||||||
fhandler.close()
|
fhandler.close()
|
||||||
|
|
||||||
@ -1162,9 +1277,9 @@ def diagnose_env_freebsd():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def diagnose_env():
|
def diagnose_env(rundir):
|
||||||
if sys.platform.startswith("linux"):
|
if sys.platform.startswith("linux"):
|
||||||
return diagnose_env_linux()
|
return diagnose_env_linux(rundir)
|
||||||
elif sys.platform.startswith("freebsd"):
|
elif sys.platform.startswith("freebsd"):
|
||||||
return diagnose_env_freebsd()
|
return diagnose_env_freebsd()
|
||||||
|
|
||||||
|
@ -18,34 +18,27 @@
|
|||||||
# OF THIS SOFTWARE.
|
# OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
from collections import OrderedDict
|
import json
|
||||||
from json import dumps as json_dumps
|
|
||||||
from re import search as re_search
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import pytest
|
import os
|
||||||
import ipaddr
|
from collections import OrderedDict
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from re import search as re_search
|
||||||
|
|
||||||
|
import ipaddr
|
||||||
|
import pytest
|
||||||
|
|
||||||
# Import topogen and topotest helpers
|
|
||||||
from lib.topolog import logger
|
|
||||||
|
|
||||||
# Required to instantiate the topology builder class.
|
|
||||||
from lib.common_config import (
|
|
||||||
number_to_row,
|
|
||||||
number_to_column,
|
|
||||||
load_config_to_routers,
|
|
||||||
create_interfaces_cfg,
|
|
||||||
create_static_routes,
|
|
||||||
create_prefix_lists,
|
|
||||||
create_route_maps,
|
|
||||||
create_bgp_community_lists,
|
|
||||||
create_vrf_cfg,
|
|
||||||
)
|
|
||||||
|
|
||||||
from lib.pim import create_pim_config, create_igmp_config
|
|
||||||
from lib.bgp import create_router_bgp
|
from lib.bgp import create_router_bgp
|
||||||
|
from lib.common_config import (create_bgp_community_lists,
|
||||||
|
create_interfaces_cfg, create_prefix_lists,
|
||||||
|
create_route_maps, create_static_routes,
|
||||||
|
create_vrf_cfg, load_config_to_routers,
|
||||||
|
start_topology,
|
||||||
|
topo_daemons,
|
||||||
|
number_to_column)
|
||||||
from lib.ospf import create_router_ospf, create_router_ospf6
|
from lib.ospf import create_router_ospf, create_router_ospf6
|
||||||
|
from lib.pim import create_igmp_config, create_pim_config
|
||||||
|
from lib.topolog import logger
|
||||||
|
|
||||||
ROUTER_LIST = []
|
ROUTER_LIST = []
|
||||||
|
|
||||||
@ -60,13 +53,13 @@ def build_topo_from_json(tgen, topo):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
ROUTER_LIST = sorted(
|
ROUTER_LIST = sorted(
|
||||||
topo["routers"].keys(), key=lambda x: int(re_search("\d+", x).group(0))
|
topo["routers"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0))
|
||||||
)
|
)
|
||||||
|
|
||||||
SWITCH_LIST = []
|
SWITCH_LIST = []
|
||||||
if "switches" in topo:
|
if "switches" in topo:
|
||||||
SWITCH_LIST = sorted(
|
SWITCH_LIST = sorted(
|
||||||
topo["switches"].keys(), key=lambda x: int(re_search("\d+", x).group(0))
|
topo["switches"].keys(), key=lambda x: int(re_search(r"\d+", x).group(0))
|
||||||
)
|
)
|
||||||
|
|
||||||
listRouters = sorted(ROUTER_LIST[:])
|
listRouters = sorted(ROUTER_LIST[:])
|
||||||
@ -204,7 +197,7 @@ def build_topo_from_json(tgen, topo):
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
"Generated link data for router: %s\n%s",
|
"Generated link data for router: %s\n%s",
|
||||||
curRouter,
|
curRouter,
|
||||||
json_dumps(
|
json.dumps(
|
||||||
topo["routers"][curRouter]["links"], indent=4, sort_keys=True
|
topo["routers"][curRouter]["links"], indent=4, sort_keys=True
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -287,7 +280,7 @@ def build_topo_from_json(tgen, topo):
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
"Generated link data for router: %s\n%s",
|
"Generated link data for router: %s\n%s",
|
||||||
curRouter,
|
curRouter,
|
||||||
json_dumps(
|
json.dumps(
|
||||||
topo["routers"][curRouter]["links"], indent=4, sort_keys=True
|
topo["routers"][curRouter]["links"], indent=4, sort_keys=True
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -297,7 +290,7 @@ def linux_intf_config_from_json(tgen, topo):
|
|||||||
"""Configure interfaces from linux based on topo."""
|
"""Configure interfaces from linux based on topo."""
|
||||||
routers = topo["routers"]
|
routers = topo["routers"]
|
||||||
for rname in routers:
|
for rname in routers:
|
||||||
router = tgen.gears[rname]
|
router = tgen.net[rname]
|
||||||
links = routers[rname]["links"]
|
links = routers[rname]["links"]
|
||||||
for rrname in links:
|
for rrname in links:
|
||||||
link = links[rrname]
|
link = links[rrname]
|
||||||
@ -306,9 +299,9 @@ def linux_intf_config_from_json(tgen, topo):
|
|||||||
else:
|
else:
|
||||||
lname = link["interface"]
|
lname = link["interface"]
|
||||||
if "ipv4" in link:
|
if "ipv4" in link:
|
||||||
router.run("ip addr add {} dev {}".format(link["ipv4"], lname))
|
router.cmd_raises("ip addr add {} dev {}".format(link["ipv4"], lname))
|
||||||
if "ipv6" in link:
|
if "ipv6" in link:
|
||||||
router.run("ip -6 addr add {} dev {}".format(link["ipv6"], lname))
|
router.cmd_raises("ip -6 addr add {} dev {}".format(link["ipv6"], lname))
|
||||||
|
|
||||||
|
|
||||||
def build_config_from_json(tgen, topo, save_bkup=True):
|
def build_config_from_json(tgen, topo, save_bkup=True):
|
||||||
@ -347,3 +340,50 @@ def build_config_from_json(tgen, topo, save_bkup=True):
|
|||||||
if not result:
|
if not result:
|
||||||
logger.info("build_config_from_json: failed to configure topology")
|
logger.info("build_config_from_json: failed to configure topology")
|
||||||
pytest.exit(1)
|
pytest.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def create_tgen_from_json(testfile, json_file=None):
|
||||||
|
"""Create a topogen object given a testfile.
|
||||||
|
|
||||||
|
- `testfile` : The path to the testfile.
|
||||||
|
- `json_file` : The path to the json config file. If None the pathname is derived
|
||||||
|
from the `testfile` first by trying to replace `.py` by `.json` and if that isn't
|
||||||
|
present then by removing `test_` prefix as well.
|
||||||
|
"""
|
||||||
|
from lib.topogen import Topogen # Topogen imports this module too
|
||||||
|
thisdir = os.path.dirname(os.path.realpath(testfile))
|
||||||
|
basename = os.path.basename(testfile)
|
||||||
|
logger.debug("starting standard JSON based module setup for %s", basename)
|
||||||
|
|
||||||
|
assert basename.startswith("test_")
|
||||||
|
assert basename.endswith(".py")
|
||||||
|
json_file = os.path.join(thisdir, basename[:-3] + ".json")
|
||||||
|
if not os.path.exists(json_file):
|
||||||
|
json_file = os.path.join(thisdir, basename[5:-3] + ".json")
|
||||||
|
assert os.path.exists(json_file)
|
||||||
|
with open(json_file, "r") as topof:
|
||||||
|
topo = json.load(topof)
|
||||||
|
|
||||||
|
# Create topology
|
||||||
|
tgen = Topogen(lambda tgen: build_topo_from_json(tgen, topo), basename[:-3])
|
||||||
|
tgen.json_topo = topo
|
||||||
|
return tgen
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module_from_json(testfile, json_file=None):
|
||||||
|
"""Do the standard module setup for JSON based test.
|
||||||
|
|
||||||
|
* `testfile` : The path to the testfile. The name is used to derive the json config
|
||||||
|
file name as well (removing `test_` prefix and replacing `.py` suffix with `.json`
|
||||||
|
"""
|
||||||
|
# Create topology object
|
||||||
|
tgen = create_tgen_from_json(testfile, json_file)
|
||||||
|
|
||||||
|
# Start routers (and their daemons)
|
||||||
|
start_topology(tgen, topo_daemons(tgen))
|
||||||
|
|
||||||
|
# Configure routers
|
||||||
|
build_config_from_json(tgen)
|
||||||
|
assert not tgen.routers_have_failure()
|
||||||
|
|
||||||
|
return tgen
|
||||||
|
@ -26,8 +26,23 @@ Logging utilities for topology tests.
|
|||||||
This file defines our logging abstraction.
|
This file defines our logging abstraction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version_info[0] > 2:
|
||||||
|
import configparser
|
||||||
|
else:
|
||||||
|
import ConfigParser as configparser
|
||||||
|
|
||||||
|
try:
|
||||||
|
from xdist import is_xdist_controller
|
||||||
|
except ImportError:
|
||||||
|
def is_xdist_controller():
|
||||||
|
return False
|
||||||
|
|
||||||
|
BASENAME = "topolog"
|
||||||
|
|
||||||
# Helper dictionary to convert Topogen logging levels to Python's logging.
|
# Helper dictionary to convert Topogen logging levels to Python's logging.
|
||||||
DEBUG_TOPO2LOGGING = {
|
DEBUG_TOPO2LOGGING = {
|
||||||
@ -38,81 +53,121 @@ DEBUG_TOPO2LOGGING = {
|
|||||||
"error": logging.ERROR,
|
"error": logging.ERROR,
|
||||||
"critical": logging.CRITICAL,
|
"critical": logging.CRITICAL,
|
||||||
}
|
}
|
||||||
|
FORMAT = "%(asctime)s.%(msecs)03d %(levelname)s: %(name)s: %(message)s"
|
||||||
|
|
||||||
|
handlers = {}
|
||||||
|
logger = logging.getLogger("topolog")
|
||||||
|
|
||||||
|
|
||||||
class InfoFilter(logging.Filter):
|
def set_handler(l, target=None):
|
||||||
def filter(self, rec):
|
if target is None:
|
||||||
return rec.levelno in (logging.DEBUG, logging.INFO)
|
h = logging.NullHandler()
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Logger class definition
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class Logger(object):
|
|
||||||
"""
|
|
||||||
Logger class that encapsulates logging functions, internaly it uses Python
|
|
||||||
logging module with a separated instance instead of global.
|
|
||||||
|
|
||||||
Default logging level is 'info'.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
# Create default global logger
|
|
||||||
self.log_level = logging.INFO
|
|
||||||
self.logger = logging.Logger("topolog", level=self.log_level)
|
|
||||||
|
|
||||||
handler_stdout = logging.StreamHandler(sys.stdout)
|
|
||||||
handler_stdout.setLevel(logging.DEBUG)
|
|
||||||
handler_stdout.addFilter(InfoFilter())
|
|
||||||
handler_stdout.setFormatter(
|
|
||||||
logging.Formatter(fmt="%(asctime)s %(levelname)s: %(message)s")
|
|
||||||
)
|
|
||||||
handler_stderr = logging.StreamHandler()
|
|
||||||
handler_stderr.setLevel(logging.WARNING)
|
|
||||||
handler_stderr.setFormatter(
|
|
||||||
logging.Formatter(fmt="%(asctime)s %(levelname)s: %(message)s")
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.addHandler(handler_stdout)
|
|
||||||
self.logger.addHandler(handler_stderr)
|
|
||||||
|
|
||||||
# Handle more loggers
|
|
||||||
self.loggers = {"topolog": self.logger}
|
|
||||||
|
|
||||||
def set_log_level(self, level):
|
|
||||||
"Set the logging level"
|
|
||||||
self.log_level = DEBUG_TOPO2LOGGING.get(level)
|
|
||||||
self.logger.setLevel(self.log_level)
|
|
||||||
|
|
||||||
def get_logger(self, name="topolog", log_level=None, target=sys.stdout):
|
|
||||||
"""
|
|
||||||
Get a new logger entry. Allows creating different loggers for formating,
|
|
||||||
filtering or handling (file, stream or stdout/stderr).
|
|
||||||
"""
|
|
||||||
if log_level is None:
|
|
||||||
log_level = self.log_level
|
|
||||||
if name in self.loggers:
|
|
||||||
return self.loggers[name]
|
|
||||||
|
|
||||||
nlogger = logging.Logger(name, level=log_level)
|
|
||||||
if isinstance(target, str):
|
|
||||||
handler = logging.FileHandler(filename=target)
|
|
||||||
else:
|
else:
|
||||||
handler = logging.StreamHandler(stream=target)
|
if isinstance(target, str):
|
||||||
|
h = logging.FileHandler(filename=target, mode="w")
|
||||||
handler.setFormatter(
|
else:
|
||||||
logging.Formatter(fmt="%(asctime)s %(levelname)s: %(message)s")
|
h = logging.StreamHandler(stream=target)
|
||||||
)
|
h.setFormatter(logging.Formatter(fmt=FORMAT))
|
||||||
nlogger.addHandler(handler)
|
# Don't filter anything at the handler level
|
||||||
self.loggers[name] = nlogger
|
h.setLevel(logging.DEBUG)
|
||||||
return nlogger
|
l.addHandler(h)
|
||||||
|
return h
|
||||||
|
|
||||||
|
|
||||||
#
|
def set_log_level(l, level):
|
||||||
# Global variables
|
"Set the logging level."
|
||||||
#
|
# Messages sent to this logger only are created if this level or above.
|
||||||
|
log_level = DEBUG_TOPO2LOGGING.get(level, level)
|
||||||
|
l.setLevel(log_level)
|
||||||
|
|
||||||
logger_config = Logger()
|
|
||||||
logger = logger_config.logger
|
def get_logger(name, log_level=None, target=None):
|
||||||
|
l = logging.getLogger("{}.{}".format(BASENAME, name))
|
||||||
|
|
||||||
|
if log_level is not None:
|
||||||
|
set_log_level(l, log_level)
|
||||||
|
|
||||||
|
if target is not None:
|
||||||
|
set_handler(l, target)
|
||||||
|
|
||||||
|
return l
|
||||||
|
|
||||||
|
|
||||||
|
# nodeid: all_protocol_startup/test_all_protocol_startup.py::test_router_running
|
||||||
|
|
||||||
|
def get_test_logdir(nodeid=None):
|
||||||
|
"""Get log directory relative pathname."""
|
||||||
|
xdist_worker = os.getenv("PYTEST_XDIST_WORKER", "")
|
||||||
|
mode = os.getenv("PYTEST_XDIST_MODE", "no")
|
||||||
|
|
||||||
|
if not nodeid:
|
||||||
|
nodeid = os.environ["PYTEST_CURRENT_TEST"].split(" ")[0]
|
||||||
|
|
||||||
|
cur_test = nodeid.replace("[", "_").replace("]", "_")
|
||||||
|
path, testname = cur_test.split("::")
|
||||||
|
path = path[:-3].replace("/", ".")
|
||||||
|
|
||||||
|
# We use different logdir paths based on how xdist is running.
|
||||||
|
if mode == "each":
|
||||||
|
return os.path.join(path, testname, xdist_worker)
|
||||||
|
elif mode == "load":
|
||||||
|
return os.path.join(path, testname)
|
||||||
|
else:
|
||||||
|
assert (
|
||||||
|
mode == "no" or
|
||||||
|
mode == "loadfile" or
|
||||||
|
mode == "loadscope"
|
||||||
|
), "Unknown dist mode {}".format(mode)
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def logstart(nodeid, location, rundir):
|
||||||
|
"""Called from pytest before module setup."""
|
||||||
|
|
||||||
|
mode = os.getenv("PYTEST_XDIST_MODE", "no")
|
||||||
|
worker = os.getenv("PYTEST_TOPOTEST_WORKER", "")
|
||||||
|
|
||||||
|
# We only per-test log in the workers (or non-dist)
|
||||||
|
if not worker and mode != "no":
|
||||||
|
return
|
||||||
|
|
||||||
|
handler_id = nodeid + worker
|
||||||
|
assert handler_id not in handlers
|
||||||
|
|
||||||
|
rel_log_dir = get_test_logdir(nodeid)
|
||||||
|
exec_log_dir = os.path.join(rundir, rel_log_dir)
|
||||||
|
subprocess.check_call("mkdir -p {0} && chmod 1777 {0}".format(exec_log_dir), shell=True)
|
||||||
|
exec_log_path = os.path.join(exec_log_dir, "exec.log")
|
||||||
|
|
||||||
|
# Add test based exec log handler
|
||||||
|
h = set_handler(logger, exec_log_path)
|
||||||
|
handlers[handler_id] = h
|
||||||
|
|
||||||
|
if worker:
|
||||||
|
logger.info("Logging on worker %s for %s into %s", worker, handler_id, exec_log_path)
|
||||||
|
else:
|
||||||
|
logger.info("Logging for %s into %s", handler_id, exec_log_path)
|
||||||
|
|
||||||
|
|
||||||
|
def logfinish(nodeid, location):
|
||||||
|
"""Called from pytest after module teardown."""
|
||||||
|
# This function may not be called if pytest is interrupted.
|
||||||
|
|
||||||
|
worker = os.getenv("PYTEST_TOPOTEST_WORKER", "")
|
||||||
|
handler_id = nodeid + worker
|
||||||
|
|
||||||
|
if handler_id in handlers:
|
||||||
|
# Remove test based exec log handler
|
||||||
|
if worker:
|
||||||
|
logger.info("Closing logs for %s", handler_id)
|
||||||
|
|
||||||
|
h = handlers[handler_id]
|
||||||
|
logger.removeHandler(handlers[handler_id])
|
||||||
|
h.flush()
|
||||||
|
h.close()
|
||||||
|
del handlers[handler_id]
|
||||||
|
|
||||||
|
|
||||||
|
console_handler = set_handler(logger, None)
|
||||||
|
set_log_level(logger, "debug")
|
||||||
|
@ -22,38 +22,40 @@
|
|||||||
# OF THIS SOFTWARE.
|
# OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
import json
|
import difflib
|
||||||
import os
|
|
||||||
import errno
|
import errno
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import functools
|
import functools
|
||||||
import glob
|
import glob
|
||||||
import subprocess
|
import json
|
||||||
import tempfile
|
import os
|
||||||
|
import pdb
|
||||||
import platform
|
import platform
|
||||||
import difflib
|
import re
|
||||||
import time
|
import resource
|
||||||
import signal
|
import signal
|
||||||
|
import subprocess
|
||||||
from lib.topolog import logger
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
import lib.topolog as topolog
|
||||||
|
from lib.topolog import logger
|
||||||
|
|
||||||
if sys.version_info[0] > 2:
|
if sys.version_info[0] > 2:
|
||||||
import configparser
|
import configparser
|
||||||
else:
|
else:
|
||||||
import ConfigParser as configparser
|
import ConfigParser as configparser
|
||||||
|
|
||||||
from mininet.topo import Topo
|
from lib import micronet
|
||||||
from mininet.net import Mininet
|
from lib.micronet_compat import Node
|
||||||
from mininet.node import Node, OVSSwitch, Host
|
|
||||||
from mininet.log import setLogLevel, info
|
|
||||||
from mininet.cli import CLI
|
|
||||||
from mininet.link import Intf
|
|
||||||
from mininet.term import makeTerm
|
|
||||||
|
|
||||||
g_extra_config = {}
|
g_extra_config = {}
|
||||||
|
|
||||||
|
def get_logs_path(rundir):
|
||||||
|
logspath = topolog.get_test_logdir()
|
||||||
|
return os.path.join(rundir, logspath)
|
||||||
|
|
||||||
|
|
||||||
def gdb_core(obj, daemon, corefiles):
|
def gdb_core(obj, daemon, corefiles):
|
||||||
gdbcmds = """
|
gdbcmds = """
|
||||||
@ -283,7 +285,7 @@ def json_cmp(d1, d2, exact=False):
|
|||||||
* `d2`: parsed JSON data structure
|
* `d2`: parsed JSON data structure
|
||||||
|
|
||||||
Returns 'None' when all JSON Object keys and all Array elements of d2 have a match
|
Returns 'None' when all JSON Object keys and all Array elements of d2 have a match
|
||||||
in d1, e.g. when d2 is a "subset" of d1 without honoring any order. Otherwise an
|
in d1, i.e., when d2 is a "subset" of d1 without honoring any order. Otherwise an
|
||||||
error report is generated and wrapped in a 'json_cmp_result()'. There are special
|
error report is generated and wrapped in a 'json_cmp_result()'. There are special
|
||||||
parameters and notations explained below which can be used to cover rather unusual
|
parameters and notations explained below which can be used to cover rather unusual
|
||||||
cases:
|
cases:
|
||||||
@ -497,6 +499,8 @@ def get_file(content):
|
|||||||
"""
|
"""
|
||||||
Generates a temporary file in '/tmp' with `content` and returns the file name.
|
Generates a temporary file in '/tmp' with `content` and returns the file name.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(content, list) or isinstance(content, tuple):
|
||||||
|
content = "\n".join(content)
|
||||||
fde = tempfile.NamedTemporaryFile(mode="w", delete=False)
|
fde = tempfile.NamedTemporaryFile(mode="w", delete=False)
|
||||||
fname = fde.name
|
fname = fde.name
|
||||||
fde.write(content)
|
fde.write(content)
|
||||||
@ -991,7 +995,6 @@ def checkAddressSanitizerError(output, router, component, logdir=""):
|
|||||||
and (callingProc != "checkAddressSanitizerError")
|
and (callingProc != "checkAddressSanitizerError")
|
||||||
and (callingProc != "checkRouterCores")
|
and (callingProc != "checkRouterCores")
|
||||||
and (callingProc != "stopRouter")
|
and (callingProc != "stopRouter")
|
||||||
and (callingProc != "__stop_internal")
|
|
||||||
and (callingProc != "stop")
|
and (callingProc != "stop")
|
||||||
and (callingProc != "stop_topology")
|
and (callingProc != "stop_topology")
|
||||||
and (callingProc != "checkRouterRunning")
|
and (callingProc != "checkRouterRunning")
|
||||||
@ -1026,7 +1029,7 @@ def checkAddressSanitizerError(output, router, component, logdir=""):
|
|||||||
return
|
return
|
||||||
|
|
||||||
addressSanitizerError = re.search(
|
addressSanitizerError = re.search(
|
||||||
"(==[0-9]+==)ERROR: AddressSanitizer: ([^\s]*) ", output
|
r"(==[0-9]+==)ERROR: AddressSanitizer: ([^\s]*) ", output
|
||||||
)
|
)
|
||||||
if addressSanitizerError:
|
if addressSanitizerError:
|
||||||
processAddressSanitizerError(addressSanitizerError, output, router, component)
|
processAddressSanitizerError(addressSanitizerError, output, router, component)
|
||||||
@ -1042,7 +1045,7 @@ def checkAddressSanitizerError(output, router, component, logdir=""):
|
|||||||
with open(file, "r") as asanErrorFile:
|
with open(file, "r") as asanErrorFile:
|
||||||
asanError = asanErrorFile.read()
|
asanError = asanErrorFile.read()
|
||||||
addressSanitizerError = re.search(
|
addressSanitizerError = re.search(
|
||||||
"(==[0-9]+==)ERROR: AddressSanitizer: ([^\s]*) ", asanError
|
r"(==[0-9]+==)ERROR: AddressSanitizer: ([^\s]*) ", asanError
|
||||||
)
|
)
|
||||||
if addressSanitizerError:
|
if addressSanitizerError:
|
||||||
processAddressSanitizerError(
|
processAddressSanitizerError(
|
||||||
@ -1052,48 +1055,218 @@ def checkAddressSanitizerError(output, router, component, logdir=""):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def addRouter(topo, name):
|
def _sysctl_atleast(commander, variable, min_value):
|
||||||
"Adding a FRRouter to Topology"
|
if isinstance(min_value, tuple):
|
||||||
|
min_value = list(min_value)
|
||||||
|
is_list = isinstance(min_value, list)
|
||||||
|
|
||||||
MyPrivateDirs = [
|
sval = commander.cmd_raises("sysctl -n " + variable).strip()
|
||||||
"/etc/frr",
|
if is_list:
|
||||||
"/var/run/frr",
|
cur_val = [int(x) for x in sval.split()]
|
||||||
"/var/log",
|
else:
|
||||||
]
|
cur_val = int(sval)
|
||||||
if sys.platform.startswith("linux"):
|
|
||||||
return topo.addNode(name, cls=LinuxRouter, privateDirs=MyPrivateDirs)
|
set_value = False
|
||||||
elif sys.platform.startswith("freebsd"):
|
if is_list:
|
||||||
return topo.addNode(name, cls=FreeBSDRouter, privateDirs=MyPrivateDirs)
|
for i, v in enumerate(cur_val):
|
||||||
|
if v < min_value[i]:
|
||||||
|
set_value = True
|
||||||
|
else:
|
||||||
|
min_value[i] = v
|
||||||
|
else:
|
||||||
|
if cur_val < min_value:
|
||||||
|
set_value = True
|
||||||
|
if set_value:
|
||||||
|
if is_list:
|
||||||
|
valstr = " ".join([str(x) for x in min_value])
|
||||||
|
else:
|
||||||
|
valstr = str(min_value)
|
||||||
|
logger.info("Increasing sysctl %s from %s to %s", variable, cur_val, valstr)
|
||||||
|
commander.cmd_raises("sysctl -w {}=\"{}\"\n".format(variable, valstr))
|
||||||
|
|
||||||
|
|
||||||
def set_sysctl(node, sysctl, value):
|
def _sysctl_assure(commander, variable, value):
|
||||||
"Set a sysctl value and return None on success or an error string"
|
if isinstance(value, tuple):
|
||||||
valuestr = "{}".format(value)
|
value = list(value)
|
||||||
command = "sysctl {0}={1}".format(sysctl, valuestr)
|
is_list = isinstance(value, list)
|
||||||
cmdret = node.cmd(command)
|
|
||||||
|
|
||||||
matches = re.search(r"([^ ]+) = ([^\s]+)", cmdret)
|
sval = commander.cmd_raises("sysctl -n " + variable).strip()
|
||||||
if matches is None:
|
if is_list:
|
||||||
return cmdret
|
cur_val = [int(x) for x in sval.split()]
|
||||||
if matches.group(1) != sysctl:
|
else:
|
||||||
return cmdret
|
cur_val = sval
|
||||||
if matches.group(2) != valuestr:
|
|
||||||
return cmdret
|
|
||||||
|
|
||||||
return None
|
set_value = False
|
||||||
|
if is_list:
|
||||||
|
for i, v in enumerate(cur_val):
|
||||||
|
if v != value[i]:
|
||||||
|
set_value = True
|
||||||
|
else:
|
||||||
|
value[i] = v
|
||||||
|
else:
|
||||||
|
if cur_val != str(value):
|
||||||
|
set_value = True
|
||||||
|
|
||||||
|
if set_value:
|
||||||
|
if is_list:
|
||||||
|
valstr = " ".join([str(x) for x in value])
|
||||||
|
else:
|
||||||
|
valstr = str(value)
|
||||||
|
logger.info("Changing sysctl %s from %s to %s", variable, cur_val, valstr)
|
||||||
|
commander.cmd_raises("sysctl -w {}=\"{}\"\n".format(variable, valstr))
|
||||||
|
|
||||||
|
|
||||||
def assert_sysctl(node, sysctl, value):
|
def sysctl_atleast(commander, variable, min_value, raises=False):
|
||||||
"Set and assert that the sysctl is set with the specified value."
|
try:
|
||||||
assert set_sysctl(node, sysctl, value) is None
|
if commander is None:
|
||||||
|
commander = micronet.Commander("topotest")
|
||||||
|
return _sysctl_atleast(commander, variable, min_value)
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
logger.warning(
|
||||||
|
"%s: Failed to assure sysctl min value %s = %s",
|
||||||
|
commander, variable, min_value
|
||||||
|
)
|
||||||
|
if raises:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def sysctl_assure(commander, variable, value, raises=False):
|
||||||
|
try:
|
||||||
|
if commander is None:
|
||||||
|
commander = micronet.Commander("topotest")
|
||||||
|
return _sysctl_assure(commander, variable, value)
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
logger.warning(
|
||||||
|
"%s: Failed to assure sysctl value %s = %s",
|
||||||
|
commander, variable, value, exc_info=True
|
||||||
|
)
|
||||||
|
if raises:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def rlimit_atleast(rname, min_value, raises=False):
|
||||||
|
try:
|
||||||
|
cval = resource.getrlimit(rname)
|
||||||
|
soft, hard = cval
|
||||||
|
if soft < min_value:
|
||||||
|
nval = (min_value, hard if min_value < hard else min_value)
|
||||||
|
logger.info("Increasing rlimit %s from %s to %s", rname, cval, nval)
|
||||||
|
resource.setrlimit(rname, nval)
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
logger.warning(
|
||||||
|
"Failed to assure rlimit [%s] = %s",
|
||||||
|
rname, min_value, exc_info=True
|
||||||
|
)
|
||||||
|
if raises:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def fix_netns_limits(ns):
|
||||||
|
|
||||||
|
# Maximum read and write socket buffer sizes
|
||||||
|
sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10*1024, 87380, 16*2**20])
|
||||||
|
sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10*1024, 87380, 16*2**20])
|
||||||
|
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.all.rp_filter", 0)
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.default.rp_filter", 0)
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.lo.rp_filter", 0)
|
||||||
|
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.all.forwarding", 1)
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.default.forwarding", 1)
|
||||||
|
|
||||||
|
# XXX if things fail look here as this wasn't done previously
|
||||||
|
sysctl_assure(ns, "net.ipv6.conf.all.forwarding", 1)
|
||||||
|
sysctl_assure(ns, "net.ipv6.conf.default.forwarding", 1)
|
||||||
|
|
||||||
|
# ARP
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.default.arp_announce", 2)
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.default.arp_notify", 1)
|
||||||
|
# Setting this to 1 breaks topotests that rely on lo addresses being proxy arp'd for
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.default.arp_ignore", 0)
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.all.arp_announce", 2)
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.all.arp_notify", 1)
|
||||||
|
# Setting this to 1 breaks topotests that rely on lo addresses being proxy arp'd for
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.all.arp_ignore", 0)
|
||||||
|
|
||||||
|
sysctl_assure(ns, "net.ipv4.icmp_errors_use_inbound_ifaddr", 1)
|
||||||
|
|
||||||
|
# Keep ipv6 permanent addresses on an admin down
|
||||||
|
sysctl_assure(ns, "net.ipv6.conf.all.keep_addr_on_down", 1)
|
||||||
|
if version_cmp(platform.release(), "4.20") >= 0:
|
||||||
|
sysctl_assure(ns, "net.ipv6.route.skip_notify_on_dev_down", 1)
|
||||||
|
|
||||||
|
sysctl_assure(ns, "net.ipv4.conf.all.ignore_routes_with_linkdown", 1)
|
||||||
|
sysctl_assure(ns, "net.ipv6.conf.all.ignore_routes_with_linkdown", 1)
|
||||||
|
|
||||||
|
# igmp
|
||||||
|
sysctl_atleast(ns, "net.ipv4.igmp_max_memberships", 1000)
|
||||||
|
|
||||||
|
# Use neigh information on selection of nexthop for multipath hops
|
||||||
|
sysctl_assure(ns, "net.ipv4.fib_multipath_use_neigh", 1)
|
||||||
|
|
||||||
|
|
||||||
|
def fix_host_limits():
|
||||||
|
"""Increase system limits."""
|
||||||
|
|
||||||
|
rlimit_atleast(resource.RLIMIT_NPROC, 8*1024)
|
||||||
|
rlimit_atleast(resource.RLIMIT_NOFILE, 16*1024)
|
||||||
|
sysctl_atleast(None, "fs.file-max", 16*1024)
|
||||||
|
sysctl_atleast(None, "kernel.pty.max", 16*1024)
|
||||||
|
|
||||||
|
# Enable coredumps
|
||||||
|
# Original on ubuntu 17.x, but apport won't save as in namespace
|
||||||
|
# |/usr/share/apport/apport %p %s %c %d %P
|
||||||
|
sysctl_assure(None, "kernel.core_pattern", "%e_core-sig_%s-pid_%p.dmp")
|
||||||
|
sysctl_assure(None, "kernel.core_uses_pid", 1)
|
||||||
|
sysctl_assure(None, "fs.suid_dumpable", 1)
|
||||||
|
|
||||||
|
# Maximum connection backlog
|
||||||
|
sysctl_atleast(None, "net.core.netdev_max_backlog", 4*1024)
|
||||||
|
|
||||||
|
# Maximum read and write socket buffer sizes
|
||||||
|
sysctl_atleast(None, "net.core.rmem_max", 16 * 2**20)
|
||||||
|
sysctl_atleast(None, "net.core.wmem_max", 16 * 2**20)
|
||||||
|
|
||||||
|
# Garbage Collection Settings for ARP and Neighbors
|
||||||
|
sysctl_atleast(None, "net.ipv4.neigh.default.gc_thresh2", 4*1024)
|
||||||
|
sysctl_atleast(None, "net.ipv4.neigh.default.gc_thresh3", 8*1024)
|
||||||
|
sysctl_atleast(None, "net.ipv6.neigh.default.gc_thresh2", 4*1024)
|
||||||
|
sysctl_atleast(None, "net.ipv6.neigh.default.gc_thresh3", 8*1024)
|
||||||
|
# Hold entries for 10 minutes
|
||||||
|
sysctl_assure(None, "net.ipv4.neigh.default.base_reachable_time_ms", 10 * 60 * 1000)
|
||||||
|
sysctl_assure(None, "net.ipv6.neigh.default.base_reachable_time_ms", 10 * 60 * 1000)
|
||||||
|
|
||||||
|
# igmp
|
||||||
|
sysctl_assure(None, "net.ipv4.neigh.default.mcast_solicit", 10)
|
||||||
|
|
||||||
|
# MLD
|
||||||
|
sysctl_atleast(None, "net.ipv6.mld_max_msf", 512)
|
||||||
|
|
||||||
|
# Increase routing table size to 128K
|
||||||
|
sysctl_atleast(None, "net.ipv4.route.max_size", 128*1024)
|
||||||
|
sysctl_atleast(None, "net.ipv6.route.max_size", 128*1024)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_node_tmpdir(logdir, name):
|
||||||
|
# Cleanup old log, valgrind, and core files.
|
||||||
|
subprocess.check_call(
|
||||||
|
"rm -rf {0}/{1}.valgrind.* {1}.*.asan {0}/{1}/".format(
|
||||||
|
logdir, name
|
||||||
|
),
|
||||||
|
shell=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setup the per node directory.
|
||||||
|
nodelogdir = "{}/{}".format(logdir, name)
|
||||||
|
subprocess.check_call("mkdir -p {0} && chmod 1777 {0}".format(nodelogdir), shell=True)
|
||||||
|
logfile = "{0}/{1}.log".format(logdir, name)
|
||||||
|
return logfile
|
||||||
|
|
||||||
|
|
||||||
class Router(Node):
|
class Router(Node):
|
||||||
"A Node with IPv4/IPv6 forwarding enabled"
|
"A Node with IPv4/IPv6 forwarding enabled"
|
||||||
|
|
||||||
def __init__(self, name, **params):
|
def __init__(self, name, **params):
|
||||||
super(Router, self).__init__(name, **params)
|
|
||||||
self.logdir = params.get("logdir")
|
|
||||||
|
|
||||||
# Backward compatibility:
|
# Backward compatibility:
|
||||||
# Load configuration defaults like topogen.
|
# Load configuration defaults like topogen.
|
||||||
@ -1105,25 +1278,24 @@ class Router(Node):
|
|||||||
"memleak_path": "",
|
"memleak_path": "",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.config_defaults.read(
|
self.config_defaults.read(
|
||||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "../pytest.ini")
|
os.path.join(os.path.dirname(os.path.realpath(__file__)), "../pytest.ini")
|
||||||
)
|
)
|
||||||
|
|
||||||
# If this topology is using old API and doesn't have logdir
|
# If this topology is using old API and doesn't have logdir
|
||||||
# specified, then attempt to generate an unique logdir.
|
# specified, then attempt to generate an unique logdir.
|
||||||
|
self.logdir = params.get("logdir")
|
||||||
if self.logdir is None:
|
if self.logdir is None:
|
||||||
cur_test = os.environ["PYTEST_CURRENT_TEST"]
|
self.logdir = get_logs_path(g_extra_config["rundir"])
|
||||||
self.logdir = "/tmp/topotests/" + cur_test[
|
|
||||||
cur_test.find("/") + 1 : cur_test.find(".py")
|
|
||||||
].replace("/", ".")
|
|
||||||
|
|
||||||
# If the logdir is not created, then create it and set the
|
if not params.get("logger"):
|
||||||
# appropriated permissions.
|
# If logger is present topogen has already set this up
|
||||||
if not os.path.isdir(self.logdir):
|
logfile = setup_node_tmpdir(self.logdir, name)
|
||||||
os.system("mkdir -p " + self.logdir + "/" + name)
|
l = topolog.get_logger(name, log_level="debug", target=logfile)
|
||||||
os.system("chmod -R go+rw /tmp/topotests")
|
params["logger"] = l
|
||||||
# Erase logs of previous run
|
|
||||||
os.system("rm -rf " + self.logdir + "/" + name)
|
super(Router, self).__init__(name, **params)
|
||||||
|
|
||||||
self.daemondir = None
|
self.daemondir = None
|
||||||
self.hasmpls = False
|
self.hasmpls = False
|
||||||
@ -1152,7 +1324,7 @@ class Router(Node):
|
|||||||
self.reportCores = True
|
self.reportCores = True
|
||||||
self.version = None
|
self.version = None
|
||||||
|
|
||||||
self.ns_cmd = "sudo nsenter -m -n -t {} ".format(self.pid)
|
self.ns_cmd = "sudo nsenter -a -t {} ".format(self.pid)
|
||||||
try:
|
try:
|
||||||
# Allow escaping from running inside docker
|
# Allow escaping from running inside docker
|
||||||
cgroup = open("/proc/1/cgroup").read()
|
cgroup = open("/proc/1/cgroup").read()
|
||||||
@ -1202,118 +1374,87 @@ class Router(Node):
|
|||||||
def terminate(self):
|
def terminate(self):
|
||||||
# Stop running FRR daemons
|
# Stop running FRR daemons
|
||||||
self.stopRouter()
|
self.stopRouter()
|
||||||
|
|
||||||
# Disable forwarding
|
|
||||||
set_sysctl(self, "net.ipv4.ip_forward", 0)
|
|
||||||
set_sysctl(self, "net.ipv6.conf.all.forwarding", 0)
|
|
||||||
super(Router, self).terminate()
|
super(Router, self).terminate()
|
||||||
os.system("chmod -R go+rw /tmp/topotests")
|
os.system("chmod -R go+rw " + self.logdir)
|
||||||
|
|
||||||
# Return count of running daemons
|
# Return count of running daemons
|
||||||
def listDaemons(self):
|
def listDaemons(self):
|
||||||
ret = []
|
ret = []
|
||||||
rundaemons = self.cmd("ls -1 /var/run/%s/*.pid" % self.routertype)
|
rc, stdout, _ = self.cmd_status("ls -1 /var/run/%s/*.pid" % self.routertype, warn=False)
|
||||||
errors = ""
|
if rc:
|
||||||
if re.search(r"No such file or directory", rundaemons):
|
return ret
|
||||||
return 0
|
for d in stdout.strip().split("\n"):
|
||||||
if rundaemons is not None:
|
pidfile = d.strip()
|
||||||
bet = rundaemons.split("\n")
|
try:
|
||||||
for d in bet[:-1]:
|
pid = int(self.cmd_raises("cat %s" % pidfile, warn=False).strip())
|
||||||
daemonpid = self.cmd("cat %s" % d.rstrip()).rstrip()
|
name = os.path.basename(pidfile[:-4])
|
||||||
if daemonpid.isdigit() and pid_exists(int(daemonpid)):
|
|
||||||
ret.append(os.path.basename(d.rstrip().rsplit(".", 1)[0]))
|
|
||||||
|
|
||||||
|
# probably not compatible with bsd.
|
||||||
|
rc, _, _ = self.cmd_status("test -d /proc/{}".format(pid), warn=False)
|
||||||
|
if rc:
|
||||||
|
logger.warning("%s: %s exited leaving pidfile %s (%s)", self.name, name, pidfile, pid)
|
||||||
|
self.cmd("rm -- " + pidfile)
|
||||||
|
else:
|
||||||
|
ret.append((name, pid))
|
||||||
|
except (subprocess.CalledProcessError, ValueError):
|
||||||
|
pass
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def stopRouter(self, wait=True, assertOnError=True, minErrorVersion="5.1"):
|
def stopRouter(self, assertOnError=True, minErrorVersion="5.1"):
|
||||||
# Stop Running FRR Daemons
|
# Stop Running FRR Daemons
|
||||||
rundaemons = self.cmd("ls -1 /var/run/%s/*.pid" % self.routertype)
|
running = self.listDaemons()
|
||||||
errors = ""
|
if not running:
|
||||||
if re.search(r"No such file or directory", rundaemons):
|
return ""
|
||||||
return errors
|
|
||||||
if rundaemons is not None:
|
logger.info("%s: stopping %s", self.name, ", ".join([x[0] for x in running]))
|
||||||
dmns = rundaemons.split("\n")
|
for name, pid in running:
|
||||||
# Exclude empty string at end of list
|
logger.info("{}: sending SIGTERM to {}".format(self.name, name))
|
||||||
for d in dmns[:-1]:
|
|
||||||
# Only check if daemonfilepath starts with /
|
|
||||||
# Avoids hang on "-> Connection closed" in above self.cmd()
|
|
||||||
if d[0] == '/':
|
|
||||||
daemonpid = self.cmd("cat %s" % d.rstrip()).rstrip()
|
|
||||||
if daemonpid.isdigit() and pid_exists(int(daemonpid)):
|
|
||||||
daemonname = os.path.basename(d.rstrip().rsplit(".", 1)[0])
|
|
||||||
logger.info("{}: stopping {}".format(self.name, daemonname))
|
|
||||||
try:
|
try:
|
||||||
os.kill(int(daemonpid), signal.SIGTERM)
|
os.kill(pid, signal.SIGTERM)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
if err.errno == errno.ESRCH:
|
logger.info("%s: could not kill %s (%s): %s", self.name, name, pid, str(err))
|
||||||
logger.error(
|
|
||||||
"{}: {} left a dead pidfile (pid={})".format(
|
|
||||||
self.name, daemonname, daemonpid
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.info(
|
|
||||||
"{}: {} could not kill pid {}: {}".format(
|
|
||||||
self.name, daemonname, daemonpid, str(err)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not wait:
|
|
||||||
return errors
|
|
||||||
|
|
||||||
running = self.listDaemons()
|
running = self.listDaemons()
|
||||||
|
|
||||||
if running:
|
if running:
|
||||||
sleep(
|
for _ in range(0, 5):
|
||||||
0.1,
|
|
||||||
"{}: waiting for daemons stopping: {}".format(
|
|
||||||
self.name, ", ".join(running)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
running = self.listDaemons()
|
|
||||||
|
|
||||||
counter = 20
|
|
||||||
while counter > 0 and running:
|
|
||||||
sleep(
|
sleep(
|
||||||
0.5,
|
0.5,
|
||||||
"{}: waiting for daemons stopping: {}".format(
|
"{}: waiting for daemons stopping: {}".format(
|
||||||
self.name, ", ".join(running)
|
self.name, ", ".join([x[0] for x in running])
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
running = self.listDaemons()
|
running = self.listDaemons()
|
||||||
counter -= 1
|
if not running:
|
||||||
|
break
|
||||||
|
|
||||||
if running:
|
if not running:
|
||||||
# 2nd round of kill if daemons didn't exit
|
return ""
|
||||||
dmns = rundaemons.split("\n")
|
|
||||||
# Exclude empty string at end of list
|
|
||||||
for d in dmns[:-1]:
|
|
||||||
daemonpid = self.cmd("cat %s" % d.rstrip()).rstrip()
|
|
||||||
if daemonpid.isdigit() and pid_exists(int(daemonpid)):
|
|
||||||
logger.info(
|
|
||||||
"{}: killing {}".format(
|
|
||||||
self.name,
|
|
||||||
os.path.basename(d.rstrip().rsplit(".", 1)[0]),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.cmd("kill -7 %s" % daemonpid)
|
|
||||||
self.waitOutput()
|
|
||||||
self.cmd("rm -- {}".format(d.rstrip()))
|
|
||||||
|
|
||||||
if not wait:
|
logger.warning("%s: sending SIGBUS to: %s", self.name, ", ".join([x[0] for x in running]))
|
||||||
return errors
|
for name, pid in running:
|
||||||
|
pidfile = "/var/run/{}/{}.pid".format(self.routertype, name)
|
||||||
|
logger.info("%s: killing %s", self.name, name)
|
||||||
|
self.cmd("kill -SIGBUS %d" % pid)
|
||||||
|
self.cmd("rm -- " + pidfile)
|
||||||
|
|
||||||
|
sleep(0.5, "%s: waiting for daemons to exit/core after initial SIGBUS" % self.name)
|
||||||
|
|
||||||
errors = self.checkRouterCores(reportOnce=True)
|
errors = self.checkRouterCores(reportOnce=True)
|
||||||
if self.checkRouterVersion("<", minErrorVersion):
|
if self.checkRouterVersion("<", minErrorVersion):
|
||||||
# ignore errors in old versions
|
# ignore errors in old versions
|
||||||
errors = ""
|
errors = ""
|
||||||
if assertOnError and errors is not None and len(errors) > 0:
|
if assertOnError and (errors is not None) and len(errors) > 0:
|
||||||
assert "Errors found - details follow:" == 0, errors
|
assert "Errors found - details follow:" == 0, errors
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def removeIPs(self):
|
def removeIPs(self):
|
||||||
for interface in self.intfNames():
|
for interface in self.intfNames():
|
||||||
self.cmd("ip address flush", interface)
|
try:
|
||||||
|
self.intf_ip_cmd(interface, "ip address flush " + interface)
|
||||||
|
except Exception as ex:
|
||||||
|
logger.error("%s can't remove IPs %s", self, str(ex))
|
||||||
|
# pdb.set_trace()
|
||||||
|
# assert False, "can't remove IPs %s" % str(ex)
|
||||||
|
|
||||||
def checkCapability(self, daemon, param):
|
def checkCapability(self, daemon, param):
|
||||||
if param is not None:
|
if param is not None:
|
||||||
@ -1327,29 +1468,32 @@ class Router(Node):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def loadConf(self, daemon, source=None, param=None):
|
def loadConf(self, daemon, source=None, param=None):
|
||||||
|
# Unfortunately this API allowsfor source to not exist for any and all routers.
|
||||||
|
|
||||||
# print "Daemons before:", self.daemons
|
# print "Daemons before:", self.daemons
|
||||||
if daemon in self.daemons.keys():
|
if daemon in self.daemons.keys():
|
||||||
self.daemons[daemon] = 1
|
self.daemons[daemon] = 1
|
||||||
if param is not None:
|
if param is not None:
|
||||||
self.daemons_options[daemon] = param
|
self.daemons_options[daemon] = param
|
||||||
if source is None:
|
conf_file = "/etc/{}/{}.conf".format(self.routertype, daemon)
|
||||||
self.cmd("touch /etc/%s/%s.conf" % (self.routertype, daemon))
|
if source is None or not os.path.exists(source):
|
||||||
self.waitOutput()
|
self.cmd_raises("touch " + conf_file)
|
||||||
else:
|
else:
|
||||||
self.cmd("cp %s /etc/%s/%s.conf" % (source, self.routertype, daemon))
|
self.cmd_raises("cp {} {}".format(source, conf_file))
|
||||||
self.waitOutput()
|
self.cmd_raises("chown {0}:{0} {1}".format(self.routertype, conf_file))
|
||||||
self.cmd("chmod 640 /etc/%s/%s.conf" % (self.routertype, daemon))
|
self.cmd_raises("chmod 664 {}".format(conf_file))
|
||||||
self.waitOutput()
|
|
||||||
self.cmd(
|
|
||||||
"chown %s:%s /etc/%s/%s.conf"
|
|
||||||
% (self.routertype, self.routertype, self.routertype, daemon)
|
|
||||||
)
|
|
||||||
self.waitOutput()
|
|
||||||
if (daemon == "snmpd") and (self.routertype == "frr"):
|
if (daemon == "snmpd") and (self.routertype == "frr"):
|
||||||
|
# /etc/snmp is private mount now
|
||||||
self.cmd('echo "agentXSocket /etc/frr/agentx" > /etc/snmp/frr.conf')
|
self.cmd('echo "agentXSocket /etc/frr/agentx" > /etc/snmp/frr.conf')
|
||||||
|
self.cmd('echo "mibs +ALL" > /etc/snmp/snmp.conf')
|
||||||
|
|
||||||
if (daemon == "zebra") and (self.daemons["staticd"] == 0):
|
if (daemon == "zebra") and (self.daemons["staticd"] == 0):
|
||||||
# Add staticd with zebra - if it exists
|
# Add staticd with zebra - if it exists
|
||||||
|
try:
|
||||||
staticd_path = os.path.join(self.daemondir, "staticd")
|
staticd_path = os.path.join(self.daemondir, "staticd")
|
||||||
|
except:
|
||||||
|
pdb.set_trace()
|
||||||
|
|
||||||
if os.path.isfile(staticd_path):
|
if os.path.isfile(staticd_path):
|
||||||
self.daemons["staticd"] = 1
|
self.daemons["staticd"] = 1
|
||||||
self.daemons_options["staticd"] = ""
|
self.daemons_options["staticd"] = ""
|
||||||
@ -1358,27 +1502,8 @@ class Router(Node):
|
|||||||
logger.info("No daemon {} known".format(daemon))
|
logger.info("No daemon {} known".format(daemon))
|
||||||
# print "Daemons after:", self.daemons
|
# print "Daemons after:", self.daemons
|
||||||
|
|
||||||
# Run a command in a new window (gnome-terminal, screen, tmux, xterm)
|
|
||||||
def runInWindow(self, cmd, title=None):
|
def runInWindow(self, cmd, title=None):
|
||||||
topo_terminal = os.getenv("FRR_TOPO_TERMINAL")
|
return self.run_in_window(cmd, title)
|
||||||
if topo_terminal or ("TMUX" not in os.environ and "STY" not in os.environ):
|
|
||||||
term = topo_terminal if topo_terminal else "xterm"
|
|
||||||
makeTerm(self, title=title if title else cmd, term=term, cmd=cmd)
|
|
||||||
else:
|
|
||||||
nscmd = self.ns_cmd + cmd
|
|
||||||
if "TMUX" in os.environ:
|
|
||||||
self.cmd("tmux select-layout main-horizontal")
|
|
||||||
wcmd = "tmux split-window -h"
|
|
||||||
cmd = "{} {}".format(wcmd, nscmd)
|
|
||||||
elif "STY" in os.environ:
|
|
||||||
if os.path.exists(
|
|
||||||
"/run/screen/S-{}/{}".format(os.environ["USER"], os.environ["STY"])
|
|
||||||
):
|
|
||||||
wcmd = "screen"
|
|
||||||
else:
|
|
||||||
wcmd = "sudo -u {} screen".format(os.environ["SUDO_USER"])
|
|
||||||
cmd = "{} {}".format(wcmd, nscmd)
|
|
||||||
self.cmd(cmd)
|
|
||||||
|
|
||||||
def startRouter(self, tgen=None):
|
def startRouter(self, tgen=None):
|
||||||
# Disable integrated-vtysh-config
|
# Disable integrated-vtysh-config
|
||||||
@ -1430,15 +1555,18 @@ class Router(Node):
|
|||||||
self.hasmpls = True
|
self.hasmpls = True
|
||||||
if self.hasmpls != True:
|
if self.hasmpls != True:
|
||||||
return "LDP/MPLS Tests need mpls kernel modules"
|
return "LDP/MPLS Tests need mpls kernel modules"
|
||||||
|
|
||||||
|
# Really want to use sysctl_atleast here, but only when MPLS is actually being
|
||||||
|
# used
|
||||||
self.cmd("echo 100000 > /proc/sys/net/mpls/platform_labels")
|
self.cmd("echo 100000 > /proc/sys/net/mpls/platform_labels")
|
||||||
|
|
||||||
shell_routers = g_extra_config["shell"]
|
shell_routers = g_extra_config["shell"]
|
||||||
if "all" in shell_routers or self.name in shell_routers:
|
if "all" in shell_routers or self.name in shell_routers:
|
||||||
self.runInWindow(os.getenv("SHELL", "bash"))
|
self.run_in_window(os.getenv("SHELL", "bash"))
|
||||||
|
|
||||||
vtysh_routers = g_extra_config["vtysh"]
|
vtysh_routers = g_extra_config["vtysh"]
|
||||||
if "all" in vtysh_routers or self.name in vtysh_routers:
|
if "all" in vtysh_routers or self.name in vtysh_routers:
|
||||||
self.runInWindow("vtysh")
|
self.run_in_window("vtysh")
|
||||||
|
|
||||||
if self.daemons["eigrpd"] == 1:
|
if self.daemons["eigrpd"] == 1:
|
||||||
eigrpd_path = os.path.join(self.daemondir, "eigrpd")
|
eigrpd_path = os.path.join(self.daemondir, "eigrpd")
|
||||||
@ -1464,7 +1592,7 @@ class Router(Node):
|
|||||||
return self.cmd("cat {}/{}/{}.{}".format(self.logdir, self.name, daemon, log))
|
return self.cmd("cat {}/{}/{}.{}".format(self.logdir, self.name, daemon, log))
|
||||||
|
|
||||||
def startRouterDaemons(self, daemons=None, tgen=None):
|
def startRouterDaemons(self, daemons=None, tgen=None):
|
||||||
"Starts all FRR daemons for this router."
|
"Starts FRR daemons for this router."
|
||||||
|
|
||||||
asan_abort = g_extra_config["asan_abort"]
|
asan_abort = g_extra_config["asan_abort"]
|
||||||
gdb_breakpoints = g_extra_config["gdb_breakpoints"]
|
gdb_breakpoints = g_extra_config["gdb_breakpoints"]
|
||||||
@ -1474,20 +1602,22 @@ class Router(Node):
|
|||||||
valgrind_memleaks = g_extra_config["valgrind_memleaks"]
|
valgrind_memleaks = g_extra_config["valgrind_memleaks"]
|
||||||
strace_daemons = g_extra_config["strace_daemons"]
|
strace_daemons = g_extra_config["strace_daemons"]
|
||||||
|
|
||||||
|
# Get global bundle data
|
||||||
|
if not self.path_exists("/etc/frr/support_bundle_commands.conf"):
|
||||||
|
# Copy global value if was covered by namespace mount
|
||||||
bundle_data = ""
|
bundle_data = ""
|
||||||
|
|
||||||
if os.path.exists("/etc/frr/support_bundle_commands.conf"):
|
if os.path.exists("/etc/frr/support_bundle_commands.conf"):
|
||||||
bundle_data = subprocess.check_output(
|
with open("/etc/frr/support_bundle_commands.conf", "r") as rf:
|
||||||
["cat /etc/frr/support_bundle_commands.conf"], shell=True
|
bundle_data = rf.read()
|
||||||
)
|
self.cmd_raises(
|
||||||
self.cmd(
|
"cat > /etc/frr/support_bundle_commands.conf",
|
||||||
"echo '{}' > /etc/frr/support_bundle_commands.conf".format(bundle_data)
|
stdin=bundle_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Starts actual daemons without init (ie restart)
|
# Starts actual daemons without init (ie restart)
|
||||||
# cd to per node directory
|
# cd to per node directory
|
||||||
self.cmd("install -d {}/{}".format(self.logdir, self.name))
|
self.cmd("install -m 775 -o frr -g frr -d {}/{}".format(self.logdir, self.name))
|
||||||
self.cmd("cd {}/{}".format(self.logdir, self.name))
|
self.set_cwd("{}/{}".format(self.logdir, self.name))
|
||||||
self.cmd("umask 000")
|
self.cmd("umask 000")
|
||||||
|
|
||||||
# Re-enable to allow for report per run
|
# Re-enable to allow for report per run
|
||||||
@ -1560,13 +1690,25 @@ class Router(Node):
|
|||||||
gdbcmd += " -ex 'b {}'".format(bp)
|
gdbcmd += " -ex 'b {}'".format(bp)
|
||||||
gdbcmd += " -ex 'run {}'".format(cmdopt)
|
gdbcmd += " -ex 'run {}'".format(cmdopt)
|
||||||
|
|
||||||
self.runInWindow(gdbcmd, daemon)
|
self.run_in_window(gdbcmd, daemon)
|
||||||
|
|
||||||
|
logger.info("%s: %s %s launched in gdb window", self, self.routertype, daemon)
|
||||||
else:
|
else:
|
||||||
if daemon != "snmpd":
|
if daemon != "snmpd":
|
||||||
cmdopt += " -d "
|
cmdopt += " -d "
|
||||||
cmdopt += rediropt
|
cmdopt += rediropt
|
||||||
self.cmd(" ".join([cmdenv, binary, cmdopt]))
|
|
||||||
logger.info("{}: {} {} started".format(self, self.routertype, daemon))
|
try:
|
||||||
|
self.cmd_raises(" ".join([cmdenv, binary, cmdopt]), warn=False)
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
self.logger.error(
|
||||||
|
'%s: Failed to launch "%s" daemon (%d) using: %s%s%s:',
|
||||||
|
self, daemon, error.returncode, error.cmd,
|
||||||
|
'\n:stdout: "{}"'.format(error.stdout.strip()) if error.stdout else "",
|
||||||
|
'\n:stderr: "{}"'.format(error.stderr.strip()) if error.stderr else "",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info("%s: %s %s started", self, self.routertype, daemon)
|
||||||
|
|
||||||
# Start Zebra first
|
# Start Zebra first
|
||||||
if "zebra" in daemons_list:
|
if "zebra" in daemons_list:
|
||||||
@ -1581,15 +1723,22 @@ class Router(Node):
|
|||||||
daemons_list.remove("staticd")
|
daemons_list.remove("staticd")
|
||||||
|
|
||||||
if "snmpd" in daemons_list:
|
if "snmpd" in daemons_list:
|
||||||
|
# Give zerbra a chance to configure interface addresses that snmpd daemon
|
||||||
|
# may then use.
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
start_daemon("snmpd")
|
start_daemon("snmpd")
|
||||||
while "snmpd" in daemons_list:
|
while "snmpd" in daemons_list:
|
||||||
daemons_list.remove("snmpd")
|
daemons_list.remove("snmpd")
|
||||||
|
|
||||||
# Fix Link-Local Addresses
|
if daemons is None:
|
||||||
|
# Fix Link-Local Addresses on initial startup
|
||||||
# Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
|
# Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
|
||||||
self.cmd(
|
_, output, _ = self.cmd_status(
|
||||||
"for i in `ls /sys/class/net/` ; do mac=`cat /sys/class/net/$i/address`; IFS=':'; set $mac; unset IFS; ip address add dev $i scope link fe80::$(printf %02x $((0x$1 ^ 2)))$2:${3}ff:fe$4:$5$6/64; done"
|
"for i in `ls /sys/class/net/` ; do mac=`cat /sys/class/net/$i/address`; echo $i: $mac; [ -z \"$mac\" ] && continue; IFS=':'; set $mac; unset IFS; ip address add dev $i scope link fe80::$(printf %02x $((0x$1 ^ 2)))$2:${3}ff:fe$4:$5$6/64; done",
|
||||||
|
stderr=subprocess.STDOUT
|
||||||
)
|
)
|
||||||
|
logger.debug("Set MACs:\n%s", output)
|
||||||
|
|
||||||
# Now start all the other daemons
|
# Now start all the other daemons
|
||||||
for daemon in daemons_list:
|
for daemon in daemons_list:
|
||||||
@ -1602,6 +1751,10 @@ class Router(Node):
|
|||||||
if re.search(r"No such file or directory", rundaemons):
|
if re.search(r"No such file or directory", rundaemons):
|
||||||
return "Daemons are not running"
|
return "Daemons are not running"
|
||||||
|
|
||||||
|
# Update the permissions on the log files
|
||||||
|
self.cmd("chown frr:frr -R {}/{}".format(self.logdir, self.name))
|
||||||
|
self.cmd("chmod ug+rwX,o+r -R {}/{}".format(self.logdir, self.name))
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def killRouterDaemons(
|
def killRouterDaemons(
|
||||||
@ -1630,7 +1783,6 @@ class Router(Node):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.cmd("kill -9 %s" % daemonpid)
|
self.cmd("kill -9 %s" % daemonpid)
|
||||||
self.waitOutput()
|
|
||||||
if pid_exists(int(daemonpid)):
|
if pid_exists(int(daemonpid)):
|
||||||
numRunning += 1
|
numRunning += 1
|
||||||
if wait and numRunning > 0:
|
if wait and numRunning > 0:
|
||||||
@ -1657,7 +1809,6 @@ class Router(Node):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.cmd("kill -9 %s" % daemonpid)
|
self.cmd("kill -9 %s" % daemonpid)
|
||||||
self.waitOutput()
|
|
||||||
self.cmd("rm -- {}".format(d.rstrip()))
|
self.cmd("rm -- {}".format(d.rstrip()))
|
||||||
if wait:
|
if wait:
|
||||||
errors = self.checkRouterCores(reportOnce=True)
|
errors = self.checkRouterCores(reportOnce=True)
|
||||||
@ -1914,53 +2065,9 @@ class Router(Node):
|
|||||||
leakfile.close()
|
leakfile.close()
|
||||||
|
|
||||||
|
|
||||||
class LinuxRouter(Router):
|
|
||||||
"A Linux Router Node with IPv4/IPv6 forwarding enabled."
|
|
||||||
|
|
||||||
def __init__(self, name, **params):
|
|
||||||
Router.__init__(self, name, **params)
|
|
||||||
|
|
||||||
def config(self, **params):
|
|
||||||
Router.config(self, **params)
|
|
||||||
# Enable forwarding on the router
|
|
||||||
assert_sysctl(self, "net.ipv4.ip_forward", 1)
|
|
||||||
assert_sysctl(self, "net.ipv6.conf.all.forwarding", 1)
|
|
||||||
# Enable coredumps
|
|
||||||
assert_sysctl(self, "kernel.core_uses_pid", 1)
|
|
||||||
assert_sysctl(self, "fs.suid_dumpable", 1)
|
|
||||||
# this applies to the kernel not the namespace...
|
|
||||||
# original on ubuntu 17.x, but apport won't save as in namespace
|
|
||||||
# |/usr/share/apport/apport %p %s %c %d %P
|
|
||||||
corefile = "%e_core-sig_%s-pid_%p.dmp"
|
|
||||||
assert_sysctl(self, "kernel.core_pattern", corefile)
|
|
||||||
|
|
||||||
def terminate(self):
|
|
||||||
"""
|
|
||||||
Terminate generic LinuxRouter Mininet instance
|
|
||||||
"""
|
|
||||||
set_sysctl(self, "net.ipv4.ip_forward", 0)
|
|
||||||
set_sysctl(self, "net.ipv6.conf.all.forwarding", 0)
|
|
||||||
Router.terminate(self)
|
|
||||||
|
|
||||||
|
|
||||||
class FreeBSDRouter(Router):
|
|
||||||
"A FreeBSD Router Node with IPv4/IPv6 forwarding enabled."
|
|
||||||
|
|
||||||
def __init__(self, name, **params):
|
|
||||||
Router.__init__(self, name, **params)
|
|
||||||
|
|
||||||
|
|
||||||
class LegacySwitch(OVSSwitch):
|
|
||||||
"A Legacy Switch without OpenFlow"
|
|
||||||
|
|
||||||
def __init__(self, name, **params):
|
|
||||||
OVSSwitch.__init__(self, name, failMode="standalone", **params)
|
|
||||||
self.switchIP = None
|
|
||||||
|
|
||||||
|
|
||||||
def frr_unicode(s):
|
def frr_unicode(s):
|
||||||
"""Convert string to unicode, depending on python version"""
|
"""Convert string to unicode, depending on python version"""
|
||||||
if sys.version_info[0] > 2:
|
if sys.version_info[0] > 2:
|
||||||
return s
|
return s
|
||||||
else:
|
else:
|
||||||
return unicode(s)
|
return unicode(s) # pylint: disable=E0602
|
||||||
|
@ -156,14 +156,6 @@ from lib.pim import (
|
|||||||
pytestmark = [pytest.mark.pimd, pytest.mark.staticd]
|
pytestmark = [pytest.mark.pimd, pytest.mark.staticd]
|
||||||
|
|
||||||
|
|
||||||
# Reading the data from JSON File for topology and configuration creation
|
|
||||||
jsonFile = "{}/multicast_pim_static_rp.json".format(CWD)
|
|
||||||
try:
|
|
||||||
with open(jsonFile, "r") as topoJson:
|
|
||||||
TOPO = json.load(topoJson)
|
|
||||||
except IOError:
|
|
||||||
logger.info("Could not read file: %s", jsonFile)
|
|
||||||
|
|
||||||
# Global variables
|
# Global variables
|
||||||
GROUP_RANGE_ALL = "224.0.0.0/4"
|
GROUP_RANGE_ALL = "224.0.0.0/4"
|
||||||
GROUP_RANGE = "225.1.1.1/32"
|
GROUP_RANGE = "225.1.1.1/32"
|
||||||
@ -241,7 +233,10 @@ def setup_module(mod):
|
|||||||
logger.info("Running setup_module to create topology")
|
logger.info("Running setup_module to create topology")
|
||||||
|
|
||||||
# This function initiates the topology build with Topogen...
|
# This function initiates the topology build with Topogen...
|
||||||
tgen = Topogen(CreateTopo, mod.__name__)
|
json_file = "{}/multicast_pim_static_rp.json".format(CWD)
|
||||||
|
tgen = Topogen(json_file, mod.__name__)
|
||||||
|
global TOPO
|
||||||
|
TOPO = tgen.json_topo
|
||||||
|
|
||||||
# ... and here it calls Mininet initialization functions.
|
# ... and here it calls Mininet initialization functions.
|
||||||
|
|
||||||
@ -1269,7 +1264,7 @@ def test_send_join_on_higher_preffered_rp_p1(request):
|
|||||||
shutdown_bringup_interface(tgen, dut, intf, False)
|
shutdown_bringup_interface(tgen, dut, intf, False)
|
||||||
|
|
||||||
dut = "r1"
|
dut = "r1"
|
||||||
intf = "r1-r3-eth1"
|
intf = "r1-r3-eth2"
|
||||||
shutdown_bringup_interface(tgen, dut, intf, False)
|
shutdown_bringup_interface(tgen, dut, intf, False)
|
||||||
|
|
||||||
step("r1 : Verify joinTx count before sending join")
|
step("r1 : Verify joinTx count before sending join")
|
||||||
|
Loading…
Reference in New Issue
Block a user