diff options
Diffstat (limited to 'tests/topotests/munet/mucmd.py')
-rw-r--r-- | tests/topotests/munet/mucmd.py | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/tests/topotests/munet/mucmd.py b/tests/topotests/munet/mucmd.py new file mode 100644 index 0000000..5518c6d --- /dev/null +++ b/tests/topotests/munet/mucmd.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# December 5 2021, Christian Hopps <chopps@labn.net> +# +# Copyright 2021, LabN Consulting, L.L.C. +# +"""A command that allows external command execution inside nodes.""" +import argparse +import json +import os +import subprocess +import sys + +from pathlib import Path + + +def newest_file_in(filename, paths, has_sibling=None): + new = None + newst = None + items = (x for y in paths for x in Path(y).rglob(filename)) + for e in items: + st = os.stat(e) + if has_sibling and not e.parent.joinpath(has_sibling).exists(): + continue + if not new or st.st_mtime_ns > newst.st_mtime_ns: + new = e + newst = st + continue + return new, newst + + +def main(*args): + ap = argparse.ArgumentParser(args) + ap.add_argument("-d", "--rundir", help="runtime directory for tempfiles, logs, etc") + ap.add_argument("node", nargs="?", help="node to enter or run command inside") + ap.add_argument( + "shellcmd", + nargs=argparse.REMAINDER, + help="optional shell-command to execute on NODE", + ) + args = ap.parse_args() + if args.rundir: + configpath = Path(args.rundir).joinpath("config.json") + else: + configpath, _ = newest_file_in( + "config.json", + ["/tmp/munet", "/tmp/mutest", "/tmp/unet-test"], + has_sibling=args.node, + ) + print(f'Using "{configpath}"') + + if not configpath.exists(): + print(f'"{configpath}" not found') + return 1 + rundir = configpath.parent + + nodes = [] + config = json.load(open(configpath, encoding="utf-8")) + nodes = list(config.get("topology", {}).get("nodes", [])) + envcfg = config.get("mucmd", {}).get("env", {}) + + # If args.node is not a node it's part of shellcmd + if args.node and args.node not in nodes: + if args.node != ".": + args.shellcmd[0:0] = [args.node] + args.node = None + + if args.node: + name = args.node + nodedir = rundir.joinpath(name) + if not nodedir.exists(): + print('"{name}" node doesn\'t exist in "{rundir}"') + return 1 + rundir = nodedir + else: + name = "munet" + pidpath = rundir.joinpath("nspid") + pid = open(pidpath, encoding="ascii").read().strip() + + env = {**os.environ} + env["MUNET_NODENAME"] = name + env["MUNET_RUNDIR"] = str(rundir) + + for k in envcfg: + envcfg[k] = envcfg[k].replace("%NAME%", str(name)) + envcfg[k] = envcfg[k].replace("%RUNDIR%", str(rundir)) + + # Can't use -F if it's a new pid namespace + ecmd = "/usr/bin/nsenter" + eargs = [ecmd] + + output = subprocess.check_output(["/usr/bin/nsenter", "--help"], encoding="utf-8") + if " -a," in output: + eargs.append("-a") + else: + # -U doesn't work + for flag in ["-u", "-i", "-m", "-n", "-C", "-T"]: + if f" {flag}," in output: + eargs.append(flag) + eargs.append(f"--pid=/proc/{pid}/ns/pid_for_children") + eargs.append(f"--wd={rundir}") + eargs.extend(["-t", pid]) + eargs += args.shellcmd + # print("Using ", eargs) + return os.execvpe(ecmd, eargs, {**env, **envcfg}) + + +if __name__ == "__main__": + exit_status = main() + sys.exit(exit_status) |