1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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)
|