summaryrefslogtreecommitdiffstats
path: root/tests/topotests/munet/mucmd.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/topotests/munet/mucmd.py')
-rw-r--r--tests/topotests/munet/mucmd.py111
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)