c3df0ac53d441b7d1d340d4f73a1863790fc73b3
[myplc.git] / support-scripts / cleanup-zombies.py
1 #!/usr/bin/env python3
2
3 # node manager has a few working assumptions, like
4 # if a domain d does not exist, there is no /vservers/d 
5
6 # this utility tries to detect and assess potentially
7 # conflictual situations, that could prevent nodemanager
8 # from recovering properly
9 #
10 # the logic is simply to find zombie-containers, i.e.
11 # VMs that do have a workdir in /vservers/<zombie>
12 # but that are not reported as running by virsh --list
13 # which suggests they have been improperly trashed
14 ###
15 #
16 # then we trash them but for that some subdirs must be
17 # btrfs-subvolume-delete'd and not rm-rf'ed
18
19
20 import subprocess
21 import glob
22 import os, os.path
23 from argparse import ArgumentParser
24
25 def running_domains():
26     command = [
27         'virsh',
28         '-c',
29         'lxc:///',
30         'list',
31         '--name',
32     ]
33     names_string = subprocess.check_output(
34         command,
35         universal_newlines = True,
36         stdin = subprocess.DEVNULL,
37         )
38     names = [ name for name in names_string.strip().split("\n") if name ]
39     return names
40
41 def existing_vservers():
42     all_dirs = glob.glob("/vservers/*")
43     dirs = ( dir for dir in all_dirs if os.path.isdir(dir) )
44     dirnames = ( path.replace("/vservers/", "") for path in dirs)
45     return dirnames
46
47 def display_or_run_commands(commands, run):
48     if commands:
49         if not run:
50             print("========== You should run")
51             for command in commands:
52                 print(" ".join(command))
53         else:
54             for command in commands:
55                 print("Running {}".format(" ".join(command)))
56                 retcod = subprocess.call(command)
57                 if retcod != 0:
58                     print("Warning: failed with retcod = {}".format(retcod))
59
60 def main():
61     parser = ArgumentParser()
62     # the default is to cowardly show commands to run
63     # use --run to actually do it
64     parser.add_argument("-r", "--run", action='store_true', default=False)
65     args = parser.parse_args()
66
67     running_containers = set(running_domains())
68     existing_containers = set(existing_vservers())
69     zombies_containers = existing_containers - running_containers
70
71     # the prefix used to locate subvolumes
72     flavour_prefixes = [
73         'onelab-',
74         'lxc-',
75         'omf-',
76         ]
77
78     # we need to call 'btrfs subvolume delete' on these remainings
79     # instead of just 'rm'
80     if zombies_containers:
81         commands = []
82         zombie_dirs = ["/vservers/"+z for z in zombies_containers]
83         print("-------- Found {} existing, but not running, containers".format(len(zombies_containers)))
84         print("zombie_dirs='{}'".format(" ".join(zombie_dirs)))
85         subvolumes = [ path
86                        for z in zombies_containers
87                        for prefix in flavour_prefixes
88                        for path in glob.glob("/vservers/{z}/{prefix}*".format(z=z, prefix=prefix))]
89         if subvolumes:
90             print("zombie_subvolumes='{}'".format(" ".join(subvolumes)))
91             for subvolume in subvolumes:
92                 commands.append([ 'btrfs', 'subvolume', 'delete', subvolume])
93         for zombie_dir in zombie_dirs:
94             commands.append([ 'btrfs', 'subvolume', 'delete', zombie_dir ])
95         display_or_run_commands(commands, args.run)
96         # find the containers dirs that might still exist
97         zombie_dirs = [ path for path in zombie_dirs if os.path.isdir(path) ]
98         commands = [ ['rm', '-rf', path] for path in zombie_dirs ]
99         display_or_run_commands(commands, args.run)
100         
101     #### should happen much less frequently
102     weirdos_containers = running_containers - existing_containers
103     if weirdos_containers:
104         print("-------- Found {} running but non existing".format(len(weirdos_containers)))
105         for w in weirdos_containers:
106             print("/vservers/{}".format(w))
107
108 main()