tweaks
[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(all_domains=False):
26     command = [
27         'virsh',
28         '-c',
29         'lxc:///',
30         'list',
31         '--name',
32     ]
33     if all_domains:
34         command.append('--all')
35     names_string = subprocess.check_output(
36         command,
37         universal_newlines = True,
38         stdin = subprocess.DEVNULL,
39         )
40     names = [ name for name in names_string.strip().split("\n") if name ]
41     return names
42
43 def existing_vservers():
44     all_dirs = glob.glob("/vservers/*")
45     dirs = ( dir for dir in all_dirs if os.path.isdir(dir) )
46     dirnames = ( path.replace("/vservers/", "") for path in dirs)
47     return dirnames
48
49 def display_or_run_commands(commands, run):
50     if commands:
51         if not run:
52             print("---- You should run")
53             for command in commands:
54                 print(" ".join(command))
55         else:
56             for command in commands:
57                 print("Running {}".format(" ".join(command)))
58                 retcod = subprocess.call(command)
59                 if retcod != 0:
60                     print("Warning: failed with retcod = {}".format(retcod))
61
62 def main():
63     parser = ArgumentParser()
64     # the default is to cowardly show commands to run
65     # use --run to actually do it
66     parser.add_argument("-r", "--run", action='store_true', default=False,
67                         help="actually run commands, that otherwise are just displayed")
68     parser.add_argument("-d", "--deep", action='store_true', default=False,
69                         help="spot and destroy containers that are known to libvirt, but not running")
70     parser.add_argument("-v", "--verbose",
71                         help="also displays variable definitions to cut-and-paste for the shell")
72     args = parser.parse_args()
73
74     known_containers = set(running_domains(all_domains=True))
75     running_containers = set(running_domains())
76     not_running_containers = known_containers - running_containers
77     
78     if args.deep:
79         commands = []
80         print("Found {} containers that are known but not running".format(len(not_running_containers)))
81         for not_running_container in not_running_containers:
82             commands.append(['userdel', not_running_container])
83             commands.append(['virsh', '-c', 'lxc:///', 'undefine', not_running_container])
84         display_or_run_commands(commands, args.run)
85
86     existing_containers = set(existing_vservers())
87     zombies_containers = existing_containers - running_containers
88
89     # the prefix used to locate subvolumes
90     flavour_prefixes = [
91         'onelab-',
92         'lxc-',
93         'omf-',
94         'planetflow-',
95         ]
96
97     # we need to call 'btrfs subvolume delete' on these remainings
98     # instead of just 'rm'
99     if zombies_containers:
100         print("-------- Found {} existing, but not running, containers".format(len(zombies_containers)))
101         commands = []
102         zombie_dirs = ["/vservers/"+z for z in zombies_containers]
103         if args.verbose:
104             print("zombie_dirs='{}'".format(" ".join(zombie_dirs)))
105         subvolumes = [ path
106                        for z in zombies_containers
107                        for prefix in flavour_prefixes
108                        for path in glob.glob("/vservers/{z}/{prefix}*".format(z=z, prefix=prefix))]
109         if subvolumes:
110             if args.verbose:
111                 print("zombie_subvolumes='{}'".format(" ".join(subvolumes)))
112             for subvolume in subvolumes:
113                 commands.append([ 'btrfs', 'subvolume', 'delete', subvolume])
114         for zombie_dir in zombie_dirs:
115             commands.append([ 'btrfs', 'subvolume', 'delete', zombie_dir ])
116         display_or_run_commands(commands, args.run)
117         # find the containers dirs that might still exist
118         zombie_dirs = [ path for path in zombie_dirs if os.path.isdir(path) ]
119         commands = [ ['rm', '-rf', path] for path in zombie_dirs ]
120         display_or_run_commands(commands, args.run)
121         
122     #### should happen much less frequently
123     weirdos_containers = running_containers - existing_containers
124     if weirdos_containers:
125         print("-------- Found {} running but non existing".format(len(weirdos_containers)))
126         for w in weirdos_containers:
127             print("/vservers/{}".format(w))
128
129     print("{} slices are currently running".format(len(running_containers)))
130 main()