summaryrefslogtreecommitdiffstats
path: root/tests/topotests/munet/cleanup.py
blob: 12ea6e2840e628d54893c4beb455374b2b106447 (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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# SPDX-License-Identifier: GPL-2.0-or-later
#
# September 30 2021, Christian Hopps <chopps@labn.net>
#
# Copyright 2021, LabN Consulting, L.L.C.
#
"""Provides functionality to cleanup processes on posix systems."""
import glob
import logging
import os
import signal


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):
    ourpid = str(os.getpid())
    for upid, pids in pids_by_upid:
        logging.info("Sending %s to (%s) of munet pid %s", sig, ", ".join(pids), upid)
        for pid in pids:
            try:
                if pid != ourpid:
                    cmdline = open(f"/proc/{pid}/cmdline", "r", encoding="ascii").read()
                    cmdline = cmdline.replace("\x00", " ")
                    logging.info("killing proc %s (%s)", pid, cmdline)
                    os.kill(int(pid), sig)
            except Exception:
                pass


def _get_our_pids():
    ourpid = str(os.getpid())
    piddict = get_pids_with_env("MUNET_PID", ourpid)
    pids = [x for x in piddict if x != ourpid]
    if pids:
        return {ourpid: pids}
    return {}


def _get_other_pids(rundir):
    if rundir:
        # get only munet pids using the given rundir
        piddict = get_pids_with_env("MUNET_RUNDIR", str(rundir))
    else:
        # Get all munet pids
        piddict = get_pids_with_env("MUNET_PID")
    unet_pids = {d["MUNET_PID"] for d in piddict.values() if "MUNET_PID" in d}
    pids_by_upid = {p: set() for p in unet_pids}
    for pid, envdict in piddict.items():
        if "MUNET_PID" not in envdict:
            continue
        unet_pid = envdict["MUNET_PID"]
        pids_by_upid[unet_pid].add(pid)
    # Filter out any child pid sets whos munet 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, rundir):
    if ours:
        assert rundir is None
        return _get_our_pids()
    return _get_other_pids(rundir)


def _cleanup_pids(ours, rundir):
    pids_by_upid = _get_pids_by_upid(ours, rundir).items()
    if not pids_by_upid:
        return

    t = "current" if ours else "previous"
    logging.info("Reaping %s munet processes", t)

    # _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, rundir).items()
    _kill_piddict(pids_by_upid, signal.SIGKILL)


def cleanup_current():
    """Attempt to cleanup preview runs.

    Currently this only scans for old processes.
    """
    _cleanup_pids(True, None)


def cleanup_previous(rundir=None):
    """Attempt to cleanup preview runs.

    Currently this only scans for old processes.
    """
    _cleanup_pids(False, rundir)


def is_running_in_rundir(rundir):
    return bool(get_pids_with_env("MUNET_RUNDIR", str(rundir)))