summaryrefslogtreecommitdiffstats
path: root/tests/topotests/munet/mucmd.py
blob: 5518c6dcfee9a55b01bb2e80a5e8ba10cbd5519d (plain)
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)