mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-08-04 23:42:27 +00:00
tests: switch to munet
Signed-off-by: Christian Hopps <chopps@labn.net>
This commit is contained in:
parent
352ddc72b7
commit
60e037780e
@ -7,21 +7,23 @@ import glob
|
||||
import os
|
||||
import pdb
|
||||
import re
|
||||
import resource
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import resource
|
||||
|
||||
import pytest
|
||||
|
||||
import lib.fixtures
|
||||
from lib import topolog
|
||||
from lib.micronet import Commander, proc_error
|
||||
from lib.micronet_cli import cli
|
||||
from lib.micronet_compat import Mininet, cleanup_current, cleanup_previous
|
||||
from lib.micronet_compat import Mininet
|
||||
from lib.topogen import diagnose_env, get_topogen
|
||||
from lib.topolog import logger
|
||||
from lib.topotest import g_extra_config as topotest_extra_config
|
||||
from lib.topotest import json_cmp_result
|
||||
from munet.base import Commander, proc_error
|
||||
from munet.cleanup import cleanup_current, cleanup_previous
|
||||
from munet import cli
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@ -501,7 +503,7 @@ def pytest_runtest_makereport(item, call):
|
||||
# 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)
|
||||
cli.cli(Mininet.g_mnet_inst, title=title, background=False)
|
||||
else:
|
||||
logger.error("Could not launch CLI b/c no mininet exists yet")
|
||||
|
||||
@ -515,7 +517,7 @@ def pytest_runtest_makereport(item, call):
|
||||
user = user.strip()
|
||||
|
||||
if user == "cli":
|
||||
cli(Mininet.g_mnet_inst)
|
||||
cli.cli(Mininet.g_mnet_inst)
|
||||
elif user == "pdb":
|
||||
pdb.set_trace() # pylint: disable=forgotten-debug-statement
|
||||
elif user:
|
||||
|
@ -21,7 +21,8 @@ try:
|
||||
import grpc
|
||||
import grpc_tools
|
||||
|
||||
from micronet import commander
|
||||
sys.path.append(os.path.dirname(CWD))
|
||||
from munet.base import commander
|
||||
|
||||
commander.cmd_raises(f"cp {CWD}/../../../grpc/frr-northbound.proto .")
|
||||
commander.cmd_raises(
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,306 +0,0 @@
|
||||
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# July 24 2021, Christian Hopps <chopps@labn.net>
|
||||
#
|
||||
# Copyright (c) 2021, LabN Consulting, L.L.C.
|
||||
#
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import pty
|
||||
import re
|
||||
import readline
|
||||
import select
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import termios
|
||||
import tty
|
||||
|
||||
|
||||
ENDMARKER = b"\x00END\x00"
|
||||
|
||||
|
||||
def lineiter(sock):
|
||||
s = ""
|
||||
while True:
|
||||
sb = sock.recv(256)
|
||||
if not sb:
|
||||
return
|
||||
|
||||
s += sb.decode("utf-8")
|
||||
i = s.find("\n")
|
||||
if i != -1:
|
||||
yield s[:i]
|
||||
s = s[i + 1 :]
|
||||
|
||||
|
||||
def spawn(unet, host, cmd):
|
||||
if sys.stdin.isatty():
|
||||
old_tty = termios.tcgetattr(sys.stdin)
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
try:
|
||||
master_fd, slave_fd = pty.openpty()
|
||||
|
||||
# use os.setsid() make it run in a new process group, or bash job
|
||||
# control will not be enabled
|
||||
p = unet.hosts[host].popen(
|
||||
cmd,
|
||||
preexec_fn=os.setsid,
|
||||
stdin=slave_fd,
|
||||
stdout=slave_fd,
|
||||
stderr=slave_fd,
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
while p.poll() is None:
|
||||
r, w, e = select.select([sys.stdin, master_fd], [], [], 0.25)
|
||||
if sys.stdin in r:
|
||||
d = os.read(sys.stdin.fileno(), 10240)
|
||||
os.write(master_fd, d)
|
||||
elif master_fd in r:
|
||||
o = os.read(master_fd, 10240)
|
||||
if o:
|
||||
os.write(sys.stdout.fileno(), o)
|
||||
finally:
|
||||
# restore tty settings back
|
||||
if sys.stdin.isatty():
|
||||
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
|
||||
|
||||
|
||||
def doline(unet, line, writef):
|
||||
def host_cmd_split(unet, cmd):
|
||||
csplit = cmd.split()
|
||||
for i, e in enumerate(csplit):
|
||||
if e not in unet.hosts:
|
||||
break
|
||||
hosts = csplit[:i]
|
||||
if not hosts:
|
||||
hosts = sorted(unet.hosts.keys())
|
||||
cmd = " ".join(csplit[i:])
|
||||
return hosts, cmd
|
||||
|
||||
line = line.strip()
|
||||
m = re.match(r"^(\S+)(?:\s+(.*))?$", line)
|
||||
if not m:
|
||||
return True
|
||||
|
||||
cmd = m.group(1)
|
||||
oargs = m.group(2) if m.group(2) else ""
|
||||
if cmd == "q" or cmd == "quit":
|
||||
return False
|
||||
if cmd == "hosts":
|
||||
writef("%% hosts: %s\n" % " ".join(sorted(unet.hosts.keys())))
|
||||
elif cmd in ["term", "vtysh", "xterm"]:
|
||||
args = oargs.split()
|
||||
if not args or (len(args) == 1 and args[0] == "*"):
|
||||
args = sorted(unet.hosts.keys())
|
||||
hosts = [unet.hosts[x] for x in args if x in unet.hosts]
|
||||
for host in hosts:
|
||||
if cmd == "t" or cmd == "term":
|
||||
host.run_in_window("bash", title="sh-%s" % host)
|
||||
elif cmd == "v" or cmd == "vtysh":
|
||||
host.run_in_window("vtysh", title="vt-%s" % host)
|
||||
elif cmd == "x" or cmd == "xterm":
|
||||
host.run_in_window("bash", title="sh-%s" % host, forcex=True)
|
||||
elif cmd == "sh":
|
||||
hosts, cmd = host_cmd_split(unet, oargs)
|
||||
for host in hosts:
|
||||
if sys.stdin.isatty():
|
||||
spawn(unet, host, cmd)
|
||||
else:
|
||||
if len(hosts) > 1:
|
||||
writef("------ Host: %s ------\n" % host)
|
||||
output = unet.hosts[host].cmd_legacy(cmd)
|
||||
writef(output)
|
||||
if len(hosts) > 1:
|
||||
writef("------- End: %s ------\n" % host)
|
||||
writef("\n")
|
||||
elif cmd == "h" or cmd == "help":
|
||||
writef(
|
||||
"""
|
||||
Commands:
|
||||
help :: this help
|
||||
sh [hosts] <shell-command> :: execute <shell-command> on <host>
|
||||
term [hosts] :: open shell terminals for hosts
|
||||
vtysh [hosts] :: open vtysh terminals for hosts
|
||||
[hosts] <vtysh-command> :: execute vtysh-command on hosts\n\n"""
|
||||
)
|
||||
else:
|
||||
hosts, cmd = host_cmd_split(unet, line)
|
||||
for host in hosts:
|
||||
if len(hosts) > 1:
|
||||
writef("------ Host: %s ------\n" % host)
|
||||
output = unet.hosts[host].cmd_legacy('vtysh -c "{}"'.format(cmd))
|
||||
writef(output)
|
||||
if len(hosts) > 1:
|
||||
writef("------- End: %s ------\n" % host)
|
||||
writef("\n")
|
||||
return True
|
||||
|
||||
|
||||
def cli_server_setup(unet):
|
||||
sockdir = tempfile.mkdtemp("-sockdir", "pyt")
|
||||
sockpath = os.path.join(sockdir, "cli-server.sock")
|
||||
try:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.settimeout(10)
|
||||
sock.bind(sockpath)
|
||||
sock.listen(1)
|
||||
return sock, sockdir, sockpath
|
||||
except Exception:
|
||||
unet.cmd_status("rm -rf " + sockdir)
|
||||
raise
|
||||
|
||||
|
||||
def cli_server(unet, server_sock):
|
||||
sock, addr = server_sock.accept()
|
||||
|
||||
# Go into full non-blocking mode now
|
||||
sock.settimeout(None)
|
||||
|
||||
for line in lineiter(sock):
|
||||
line = line.strip()
|
||||
|
||||
def writef(x):
|
||||
xb = x.encode("utf-8")
|
||||
sock.send(xb)
|
||||
|
||||
if not doline(unet, line, writef):
|
||||
return
|
||||
sock.send(ENDMARKER)
|
||||
|
||||
|
||||
def cli_client(sockpath, prompt="unet> "):
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.settimeout(10)
|
||||
sock.connect(sockpath)
|
||||
|
||||
# Go into full non-blocking mode now
|
||||
sock.settimeout(None)
|
||||
|
||||
print("\n--- Micronet CLI Starting ---\n\n")
|
||||
while True:
|
||||
if sys.version_info[0] == 2:
|
||||
line = raw_input(prompt) # pylint: disable=E0602
|
||||
else:
|
||||
line = input(prompt)
|
||||
if line is None:
|
||||
return
|
||||
|
||||
# Need to put \n back
|
||||
line += "\n"
|
||||
|
||||
# Send the CLI command
|
||||
sock.send(line.encode("utf-8"))
|
||||
|
||||
def bendswith(b, sentinel):
|
||||
slen = len(sentinel)
|
||||
return len(b) >= slen and b[-slen:] == sentinel
|
||||
|
||||
# Collect the output
|
||||
rb = b""
|
||||
while not bendswith(rb, ENDMARKER):
|
||||
lb = sock.recv(4096)
|
||||
if not lb:
|
||||
return
|
||||
rb += lb
|
||||
|
||||
# Remove the marker
|
||||
rb = rb[: -len(ENDMARKER)]
|
||||
|
||||
# Write the output
|
||||
sys.stdout.write(rb.decode("utf-8"))
|
||||
|
||||
|
||||
def local_cli(unet, outf, prompt="unet> "):
|
||||
print("\n--- Micronet CLI Starting ---\n\n")
|
||||
while True:
|
||||
if sys.version_info[0] == 2:
|
||||
line = raw_input(prompt) # pylint: disable=E0602
|
||||
else:
|
||||
line = input(prompt)
|
||||
if line is None:
|
||||
return
|
||||
if not doline(unet, line, outf.write):
|
||||
return
|
||||
|
||||
|
||||
def cli(
|
||||
unet,
|
||||
histfile=None,
|
||||
sockpath=None,
|
||||
force_window=False,
|
||||
title=None,
|
||||
prompt=None,
|
||||
background=True,
|
||||
):
|
||||
logger = logging.getLogger("cli-client")
|
||||
|
||||
if prompt is None:
|
||||
prompt = "unet> "
|
||||
|
||||
if force_window or not sys.stdin.isatty():
|
||||
# Run CLI in another window b/c we have no tty.
|
||||
sock, sockdir, sockpath = cli_server_setup(unet)
|
||||
|
||||
python_path = unet.get_exec_path(["python3", "python"])
|
||||
us = os.path.realpath(__file__)
|
||||
cmd = "{} {}".format(python_path, us)
|
||||
if histfile:
|
||||
cmd += " --histfile=" + histfile
|
||||
if title:
|
||||
cmd += " --prompt={}".format(title)
|
||||
cmd += " " + sockpath
|
||||
|
||||
try:
|
||||
unet.run_in_window(cmd, new_window=True, title=title, background=background)
|
||||
return cli_server(unet, sock)
|
||||
finally:
|
||||
unet.cmd_status("rm -rf " + sockdir)
|
||||
|
||||
if not unet:
|
||||
logger.debug("client-cli using sockpath %s", sockpath)
|
||||
|
||||
try:
|
||||
if histfile is None:
|
||||
histfile = os.path.expanduser("~/.micronet-history.txt")
|
||||
if not os.path.exists(histfile):
|
||||
if unet:
|
||||
unet.cmd("touch " + histfile)
|
||||
else:
|
||||
subprocess.run("touch " + histfile)
|
||||
if histfile:
|
||||
readline.read_history_file(histfile)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if sockpath:
|
||||
cli_client(sockpath, prompt=prompt)
|
||||
else:
|
||||
local_cli(unet, sys.stdout, prompt=prompt)
|
||||
except EOFError:
|
||||
pass
|
||||
except Exception as ex:
|
||||
logger.critical("cli: got exception: %s", ex, exc_info=True)
|
||||
raise
|
||||
finally:
|
||||
readline.write_history_file(histfile)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG, filename="/tmp/topotests/cli-client.log")
|
||||
logger = logging.getLogger("cli-client")
|
||||
logger.info("Start logging cli-client")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--histfile", help="file to user for history")
|
||||
parser.add_argument("--prompt-text", help="prompt string to use")
|
||||
parser.add_argument("socket", help="path to pair of sockets to communicate over")
|
||||
args = parser.parse_args()
|
||||
|
||||
prompt = "{}> ".format(args.prompt_text) if args.prompt_text else "unet> "
|
||||
cli(None, args.histfile, args.socket, prompt=prompt)
|
@ -3,140 +3,43 @@
|
||||
#
|
||||
# July 11 2021, Christian Hopps <chopps@labn.net>
|
||||
#
|
||||
# Copyright (c) 2021, LabN Consulting, L.L.C
|
||||
# Copyright (c) 2021-2023, LabN Consulting, L.L.C
|
||||
#
|
||||
|
||||
import glob
|
||||
import logging
|
||||
import ipaddress
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
|
||||
from lib.micronet import LinuxNamespace, Micronet
|
||||
from lib.micronet_cli import cli
|
||||
|
||||
|
||||
def get_pids_with_env(has_var, has_val=None):
|
||||
result = {}
|
||||
for pidenv in glob.iglob("/proc/*/environ"):
|
||||
pid = pidenv.split("/")[2]
|
||||
try:
|
||||
with open(pidenv, "rb") as rfb:
|
||||
envlist = [
|
||||
x.decode("utf-8").split("=", 1) for x in rfb.read().split(b"\0")
|
||||
]
|
||||
envlist = [[x[0], ""] if len(x) == 1 else x for x in envlist]
|
||||
envdict = dict(envlist)
|
||||
if has_var not in envdict:
|
||||
continue
|
||||
if has_val is None:
|
||||
result[pid] = envdict
|
||||
elif envdict[has_var] == str(has_val):
|
||||
result[pid] = envdict
|
||||
except Exception:
|
||||
# E.g., process exited and files are gone
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
def _kill_piddict(pids_by_upid, sig):
|
||||
for upid, pids in pids_by_upid:
|
||||
logging.info(
|
||||
"Sending %s to (%s) of micronet pid %s", sig, ", ".join(pids), upid
|
||||
)
|
||||
for pid in pids:
|
||||
try:
|
||||
os.kill(int(pid), sig)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _get_our_pids():
|
||||
ourpid = str(os.getpid())
|
||||
piddict = get_pids_with_env("MICRONET_PID", ourpid)
|
||||
pids = [x for x in piddict if x != ourpid]
|
||||
if pids:
|
||||
return {ourpid: pids}
|
||||
return {}
|
||||
|
||||
|
||||
def _get_other_pids():
|
||||
piddict = get_pids_with_env("MICRONET_PID")
|
||||
unet_pids = {d["MICRONET_PID"] for d in piddict.values()}
|
||||
pids_by_upid = {p: set() for p in unet_pids}
|
||||
for pid, envdict in piddict.items():
|
||||
pids_by_upid[envdict["MICRONET_PID"]].add(pid)
|
||||
# Filter out any child pid sets whos micronet pid is still running
|
||||
return {x: y for x, y in pids_by_upid.items() if x not in y}
|
||||
|
||||
|
||||
def _get_pids_by_upid(ours):
|
||||
if ours:
|
||||
return _get_our_pids()
|
||||
return _get_other_pids()
|
||||
|
||||
|
||||
def _cleanup_pids(ours):
|
||||
pids_by_upid = _get_pids_by_upid(ours).items()
|
||||
if not pids_by_upid:
|
||||
return
|
||||
|
||||
_kill_piddict(pids_by_upid, signal.SIGTERM)
|
||||
|
||||
# Give them 5 second to exit cleanly
|
||||
logging.info("Waiting up to 5s to allow for clean exit of abandon'd pids")
|
||||
for _ in range(0, 5):
|
||||
pids_by_upid = _get_pids_by_upid(ours).items()
|
||||
if not pids_by_upid:
|
||||
return
|
||||
time.sleep(1)
|
||||
|
||||
pids_by_upid = _get_pids_by_upid(ours).items()
|
||||
_kill_piddict(pids_by_upid, signal.SIGKILL)
|
||||
|
||||
|
||||
def cleanup_current():
|
||||
"""Attempt to cleanup preview runs.
|
||||
|
||||
Currently this only scans for old processes.
|
||||
"""
|
||||
logging.info("reaping current micronet processes")
|
||||
_cleanup_pids(True)
|
||||
|
||||
|
||||
def cleanup_previous():
|
||||
"""Attempt to cleanup preview runs.
|
||||
|
||||
Currently this only scans for old processes.
|
||||
"""
|
||||
logging.info("reaping past micronet processes")
|
||||
_cleanup_pids(False)
|
||||
from munet import cli
|
||||
from munet.base import BaseMunet, LinuxNamespace
|
||||
|
||||
|
||||
class Node(LinuxNamespace):
|
||||
"""Node (mininet compat)."""
|
||||
|
||||
def __init__(self, name, **kwargs):
|
||||
"""
|
||||
Create a Node.
|
||||
"""
|
||||
self.params = kwargs
|
||||
def __init__(self, name, rundir=None, **kwargs):
|
||||
|
||||
nkwargs = {}
|
||||
|
||||
if "unet" in kwargs:
|
||||
nkwargs["unet"] = kwargs["unet"]
|
||||
if "private_mounts" in kwargs:
|
||||
private_mounts = kwargs["private_mounts"]
|
||||
else:
|
||||
private_mounts = kwargs.get("privateDirs", [])
|
||||
nkwargs["private_mounts"] = kwargs["private_mounts"]
|
||||
|
||||
logger = kwargs.get("logger")
|
||||
# This is expected by newer munet CLI code
|
||||
self.config_dirname = ""
|
||||
self.config = {"kind": "frr"}
|
||||
self.mgmt_ip = None
|
||||
self.mgmt_ip6 = None
|
||||
|
||||
super(Node, self).__init__(name, logger=logger, private_mounts=private_mounts)
|
||||
super().__init__(name, **nkwargs)
|
||||
|
||||
self.rundir = self.unet.rundir.joinpath(self.name)
|
||||
|
||||
def cmd(self, cmd, **kwargs):
|
||||
"""Execute a command, joins stdout, stderr, ignores exit status."""
|
||||
|
||||
return super(Node, self).cmd_legacy(cmd, **kwargs)
|
||||
|
||||
def config(self, lo="up", **params):
|
||||
def config_host(self, lo="up", **params):
|
||||
"""Called by Micronet when topology is built (but not started)."""
|
||||
# mininet brings up loopback here.
|
||||
del params
|
||||
@ -148,20 +51,76 @@ class Node(LinuxNamespace):
|
||||
def terminate(self):
|
||||
return
|
||||
|
||||
def add_vlan(self, vlanname, linkiface, vlanid):
|
||||
self.logger.debug("Adding VLAN interface: %s (%s)", vlanname, vlanid)
|
||||
ip_path = self.get_exec_path("ip")
|
||||
assert ip_path, "XXX missing ip command!"
|
||||
self.cmd_raises(
|
||||
[
|
||||
ip_path,
|
||||
"link",
|
||||
"add",
|
||||
"link",
|
||||
linkiface,
|
||||
"name",
|
||||
vlanname,
|
||||
"type",
|
||||
"vlan",
|
||||
"id",
|
||||
vlanid,
|
||||
]
|
||||
)
|
||||
self.cmd_raises([ip_path, "link", "set", "dev", vlanname, "up"])
|
||||
|
||||
def add_loop(self, loopname):
|
||||
self.logger.debug("Adding Linux iface: %s", loopname)
|
||||
ip_path = self.get_exec_path("ip")
|
||||
assert ip_path, "XXX missing ip command!"
|
||||
self.cmd_raises([ip_path, "link", "add", loopname, "type", "dummy"])
|
||||
self.cmd_raises([ip_path, "link", "set", "dev", loopname, "up"])
|
||||
|
||||
def add_l3vrf(self, vrfname, tableid):
|
||||
self.logger.debug("Adding Linux VRF: %s", vrfname)
|
||||
ip_path = self.get_exec_path("ip")
|
||||
assert ip_path, "XXX missing ip command!"
|
||||
self.cmd_raises(
|
||||
[ip_path, "link", "add", vrfname, "type", "vrf", "table", tableid]
|
||||
)
|
||||
self.cmd_raises([ip_path, "link", "set", "dev", vrfname, "up"])
|
||||
|
||||
def del_iface(self, iface):
|
||||
self.logger.debug("Removing Linux Iface: %s", iface)
|
||||
ip_path = self.get_exec_path("ip")
|
||||
assert ip_path, "XXX missing ip command!"
|
||||
self.cmd_raises([ip_path, "link", "del", iface])
|
||||
|
||||
def attach_iface_to_l3vrf(self, ifacename, vrfname):
|
||||
self.logger.debug("Attaching Iface %s to Linux VRF %s", ifacename, vrfname)
|
||||
ip_path = self.get_exec_path("ip")
|
||||
assert ip_path, "XXX missing ip command!"
|
||||
if vrfname:
|
||||
self.cmd_raises(
|
||||
[ip_path, "link", "set", "dev", ifacename, "master", vrfname]
|
||||
)
|
||||
else:
|
||||
self.cmd_raises([ip_path, "link", "set", "dev", ifacename, "nomaster"])
|
||||
|
||||
set_cwd = LinuxNamespace.set_ns_cwd
|
||||
|
||||
|
||||
class Topo(object): # pylint: disable=R0205
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise Exception("Remove Me")
|
||||
|
||||
|
||||
class Mininet(Micronet):
|
||||
class Mininet(BaseMunet):
|
||||
"""
|
||||
Mininet using Micronet.
|
||||
"""
|
||||
|
||||
g_mnet_inst = None
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, rundir=None):
|
||||
"""
|
||||
Create a Micronet.
|
||||
"""
|
||||
@ -179,7 +138,146 @@ class Mininet(Micronet):
|
||||
# to set permissions to root:frr 770 to make this unneeded in that case
|
||||
# os.umask(0)
|
||||
|
||||
super(Mininet, self).__init__()
|
||||
super(Mininet, self).__init__(pid=False, rundir=rundir)
|
||||
|
||||
# From munet/munet/native.py
|
||||
with open(os.path.join(self.rundir, "nspid"), "w", encoding="ascii") as f:
|
||||
f.write(f"{self.pid}\n")
|
||||
|
||||
with open(os.path.join(self.rundir, "nspids"), "w", encoding="ascii") as f:
|
||||
f.write(f'{" ".join([str(x) for x in self.pids])}\n')
|
||||
|
||||
hosts_file = os.path.join(self.rundir, "hosts.txt")
|
||||
with open(hosts_file, "w", encoding="ascii") as hf:
|
||||
hf.write(
|
||||
f"""127.0.0.1\tlocalhost {self.name}
|
||||
::1\tip6-localhost ip6-loopback
|
||||
fe00::0\tip6-localnet
|
||||
ff00::0\tip6-mcastprefix
|
||||
ff02::1\tip6-allnodes
|
||||
ff02::2\tip6-allrouters
|
||||
"""
|
||||
)
|
||||
self.bind_mount(hosts_file, "/etc/hosts")
|
||||
|
||||
# Common CLI commands for any topology
|
||||
cdict = {
|
||||
"commands": [
|
||||
#
|
||||
# Window commands.
|
||||
#
|
||||
{
|
||||
"name": "pcap",
|
||||
"format": "pcap NETWORK",
|
||||
"help": (
|
||||
"capture packets from NETWORK into file capture-NETWORK.pcap"
|
||||
" the command is run within a new window which also shows"
|
||||
" packet summaries. NETWORK can also be an interface specified"
|
||||
" as HOST:INTF. To capture inside the host namespace."
|
||||
),
|
||||
"exec": "tshark -s 9200 -i {0} -P -w capture-{0}.pcap",
|
||||
"top-level": True,
|
||||
"new-window": {"background": True},
|
||||
},
|
||||
{
|
||||
"name": "term",
|
||||
"format": "term HOST [HOST ...]",
|
||||
"help": "open terminal[s] (TMUX or XTerm) on HOST[S], * for all",
|
||||
"exec": "bash",
|
||||
"new-window": True,
|
||||
},
|
||||
{
|
||||
"name": "vtysh",
|
||||
"exec": "/usr/bin/vtysh",
|
||||
"format": "vtysh ROUTER [ROUTER ...]",
|
||||
"new-window": True,
|
||||
"kinds": ["frr"],
|
||||
},
|
||||
{
|
||||
"name": "xterm",
|
||||
"format": "xterm HOST [HOST ...]",
|
||||
"help": "open XTerm[s] on HOST[S], * for all",
|
||||
"exec": "bash",
|
||||
"new-window": {
|
||||
"forcex": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "logd",
|
||||
"exec": "tail -F %RUNDIR%/{}.log",
|
||||
"format": "logd HOST [HOST ...] DAEMON",
|
||||
"help": (
|
||||
"tail -f on the logfile of the given "
|
||||
"DAEMON for the given HOST[S]"
|
||||
),
|
||||
"new-window": True,
|
||||
},
|
||||
{
|
||||
"name": "stdlog",
|
||||
"exec": (
|
||||
"[ -e %RUNDIR%/frr.log ] && tail -F %RUNDIR%/frr.log "
|
||||
"|| tail -F /var/log/frr.log"
|
||||
),
|
||||
"format": "stdlog HOST [HOST ...]",
|
||||
"help": "tail -f on the `frr.log` for the given HOST[S]",
|
||||
"new-window": True,
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"exec": "tail -F %RUNDIR%/{0}.err",
|
||||
"format": "stdout HOST [HOST ...] DAEMON",
|
||||
"help": (
|
||||
"tail -f on the stdout of the given DAEMON for the given HOST[S]"
|
||||
),
|
||||
"new-window": True,
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"exec": "tail -F %RUNDIR%/{0}.out",
|
||||
"format": "stderr HOST [HOST ...] DAEMON",
|
||||
"help": (
|
||||
"tail -f on the stderr of the given DAEMON for the given HOST[S]"
|
||||
),
|
||||
"new-window": True,
|
||||
},
|
||||
#
|
||||
# Non-window commands.
|
||||
#
|
||||
{
|
||||
"name": "",
|
||||
"exec": "vtysh -c '{}'",
|
||||
"format": "[ROUTER ...] COMMAND",
|
||||
"help": "execute vtysh COMMAND on the router[s]",
|
||||
"kinds": ["frr"],
|
||||
},
|
||||
{
|
||||
"name": "sh",
|
||||
"format": "[HOST ...] sh <SHELL-COMMAND>",
|
||||
"help": "execute <SHELL-COMMAND> on hosts",
|
||||
"exec": "{}",
|
||||
},
|
||||
{
|
||||
"name": "shi",
|
||||
"format": "[HOST ...] shi <INTERACTIVE-COMMAND>",
|
||||
"help": "execute <INTERACTIVE-COMMAND> on HOST[s]",
|
||||
"exec": "{}",
|
||||
"interactive": True,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
cli.add_cli_config(self, cdict)
|
||||
|
||||
# shellopt = (
|
||||
# self.pytest_config.getoption("--shell") if self.pytest_config else None
|
||||
# )
|
||||
# shellopt = shellopt if shellopt is not None else ""
|
||||
# if shellopt == "all" or "." in shellopt.split(","):
|
||||
# self.run_in_window("bash")
|
||||
|
||||
# This is expected by newer munet CLI code
|
||||
self.config_dirname = ""
|
||||
self.config = {}
|
||||
|
||||
self.logger.debug("%s: Creating", self)
|
||||
|
||||
@ -217,12 +315,15 @@ class Mininet(Micronet):
|
||||
|
||||
host.cmd_raises("ip addr add {}/{} dev {}".format(ip, plen, first_intf))
|
||||
|
||||
# can be used by munet cli
|
||||
host.mgmt_ip = ipaddress.ip_address(ip)
|
||||
|
||||
if "defaultRoute" in params:
|
||||
host.cmd_raises(
|
||||
"ip route add default {}".format(params["defaultRoute"])
|
||||
)
|
||||
|
||||
host.config()
|
||||
host.config_host()
|
||||
|
||||
self.configured_hosts.add(name)
|
||||
|
||||
@ -248,4 +349,4 @@ class Mininet(Micronet):
|
||||
Mininet.g_mnet_inst = None
|
||||
|
||||
def cli(self):
|
||||
cli(self)
|
||||
cli.cli(self)
|
||||
|
@ -212,7 +212,7 @@ class Topogen(object):
|
||||
# Mininet(Micronet) to build the actual topology.
|
||||
assert not inspect.isclass(topodef)
|
||||
|
||||
self.net = Mininet()
|
||||
self.net = Mininet(rundir=self.logdir)
|
||||
|
||||
# Adjust the parent namespace
|
||||
topotest.fix_netns_limits(self.net)
|
||||
@ -752,8 +752,8 @@ class TopoRouter(TopoGear):
|
||||
"""
|
||||
super(TopoRouter, self).__init__(tgen, name, **params)
|
||||
self.routertype = params.get("routertype", "frr")
|
||||
if "privateDirs" not in params:
|
||||
params["privateDirs"] = self.PRIVATE_DIRS
|
||||
if "private_mounts" not in params:
|
||||
params["private_mounts"] = self.PRIVATE_DIRS
|
||||
|
||||
# Propagate the router log directory
|
||||
logfile = self._setup_tmpdir()
|
||||
@ -1100,7 +1100,7 @@ class TopoHost(TopoGear):
|
||||
* `ip`: the IP address (string) for the host interface
|
||||
* `defaultRoute`: the default route that will be installed
|
||||
(e.g. 'via 10.0.0.1')
|
||||
* `privateDirs`: directories that will be mounted on a different domain
|
||||
* `private_mounts`: directories that will be mounted on a different domain
|
||||
(e.g. '/etc/important_dir').
|
||||
"""
|
||||
super(TopoHost, self).__init__(tgen, name, **params)
|
||||
@ -1120,10 +1120,10 @@ class TopoHost(TopoGear):
|
||||
|
||||
def __str__(self):
|
||||
gear = super(TopoHost, self).__str__()
|
||||
gear += ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format(
|
||||
gear += ' TopoHost<ip="{}",defaultRoute="{}",private_mounts="{}">'.format(
|
||||
self.params["ip"],
|
||||
self.params["defaultRoute"],
|
||||
str(self.params["privateDirs"]),
|
||||
str(self.params["private_mounts"]),
|
||||
)
|
||||
return gear
|
||||
|
||||
@ -1146,10 +1146,10 @@ class TopoExaBGP(TopoHost):
|
||||
(e.g. 'via 10.0.0.1')
|
||||
|
||||
Note: the different between a host and a ExaBGP peer is that this class
|
||||
has a privateDirs already defined and contains functions to handle ExaBGP
|
||||
things.
|
||||
has a private_mounts already defined and contains functions to handle
|
||||
ExaBGP things.
|
||||
"""
|
||||
params["privateDirs"] = self.PRIVATE_DIRS
|
||||
params["private_mounts"] = self.PRIVATE_DIRS
|
||||
super(TopoExaBGP, self).__init__(tgen, name, **params)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -1318,7 +1318,7 @@ def setup_node_tmpdir(logdir, name):
|
||||
class Router(Node):
|
||||
"A Node with IPv4/IPv6 forwarding enabled"
|
||||
|
||||
def __init__(self, name, **params):
|
||||
def __init__(self, name, *posargs, **params):
|
||||
|
||||
# Backward compatibility:
|
||||
# Load configuration defaults like topogen.
|
||||
@ -1347,7 +1347,7 @@ class Router(Node):
|
||||
l = topolog.get_logger(name, log_level="debug", target=logfile)
|
||||
params["logger"] = l
|
||||
|
||||
super(Router, self).__init__(name, **params)
|
||||
super(Router, self).__init__(name, *posargs, **params)
|
||||
|
||||
self.daemondir = None
|
||||
self.hasmpls = False
|
||||
@ -1407,8 +1407,8 @@ class Router(Node):
|
||||
|
||||
# pylint: disable=W0221
|
||||
# Some params are only meaningful for the parent class.
|
||||
def config(self, **params):
|
||||
super(Router, self).config(**params)
|
||||
def config_host(self, **params):
|
||||
super(Router, self).config_host(**params)
|
||||
|
||||
# User did not specify the daemons directory, try to autodetect it.
|
||||
self.daemondir = params.get("daemondir")
|
||||
|
@ -24,7 +24,7 @@ log_file_date_format = %Y-%m-%d %H:%M:%S
|
||||
junit_logging = all
|
||||
junit_log_passing_tests = true
|
||||
|
||||
norecursedirs = .git example_test example_topojson_test lib docker
|
||||
norecursedirs = .git example_test example_topojson_test lib munet docker
|
||||
|
||||
# Directory to store test results and run logs in, default shown
|
||||
# rundir = /tmp/topotests
|
||||
|
Loading…
Reference in New Issue
Block a user