Prevent the janitor from trying to clean up nonexistent LVs.
[invirt/packages/invirt-remote.git] / host / usr / sbin / invirt-janitor
1 #!/usr/bin/python
2
3 """Clean-up after people's deleted VMs.
4
5 The Invirt janitor goes through and finds virtual disk images that
6 users have requested we delete. For their privacy, it writes over the
7 entire disk with /dev/zero, then removes the logical volume, restoring
8 the space to the pool.
9
10 A request is indicated to the janitor by creating a file in
11 /var/lib/invirt-remote/cleanup/ corresponding to the name of the LV to
12 delete. The janitor notices these requests using inotify.
13 """
14
15
16 import os
17 import subprocess
18 import syslog
19 import traceback
20
21 import pyinotify
22
23
24 _JANITOR_DIR = '/var/lib/invirt-remote/cleanup'
25
26
27 def cleanup():
28     """Actually cleanup deleted LVs.
29
30     When triggered, continue to iterate over cleanup queue files,
31     deleting LVs one at a time, until there are no more pending
32     cleanups.
33     """
34     while True:
35         lvs = os.listdir(_JANITOR_DIR)
36         if not lvs:
37             break
38
39         lv = lvs.pop()
40         lv_path = '/dev/xenvg/%s' % lv
41
42         try:
43             # If the LV name doesn't start with old_, we probably
44             # don't actually want to be deleting it.
45             #
46             # Put it in the try block because we still want to delete
47             # the state file.
48             if not lv.startswith('old_'):
49                 continue
50
51             # If the LV doesn't exist, for whatever reason, don't
52             # proceed because the dd will simply fill the devfs
53             # by creating a regular file and filling it with zeros.
54             if not os.path.exists(lv_path):
55                 continue
56
57             syslog.syslog(syslog.LOG_INFO, "Cleaning up LV '%s'" % lv_path)
58
59             # In a perfect world, this should be erroring out with
60             # ENOSPC, so we ignore errors
61             subprocess.call(['/usr/bin/ionice',
62                              '-c', '2',
63                              '-n', '7',
64                              '/usr/bin/nice',
65                              '/bin/dd',
66                              'if=/dev/zero',
67                              'of=%s' % lv_path,
68                              'bs=1M'])
69
70             # Ignore any errors here, because there's really just not
71             # anything we can do.
72             subprocess.call(['/sbin/lvchange', '-a', 'n', lv_path])
73             subprocess.call(['/sbin/lvchange', '-a', 'ey', lv_path])
74             subprocess.check_call(['/sbin/lvremove', '--force', lv_path])
75
76             syslog.syslog(syslog.LOG_INFO, "Successfully cleaned up LV '%s'" % lv_path)
77         except:
78             syslog.syslog(syslog.LOG_ERR, "Error cleaning up LV '%s':" % lv_path)
79
80             for line in traceback.format_exc().split('\n'):
81                 syslog.syslog(syslog.LOG_ERR, line)
82         finally:
83             # Regardless of what happens, we always want to remove the
84             # cleanup queue file, because even if there's an error, we
85             # don't want to waste time wiping the same disk repeatedly
86             os.unlink(os.path.join(_JANITOR_DIR, lv))
87
88
89 class Janitor(pyinotify.ProcessEvent):
90     """Process inotify events by wiping and deleting LVs.
91
92     The Janitor class receives inotify events when a new file is
93     created in the state directory.
94     """
95     def process_IN_CREATE(self, event):
96         """Handle a created file or directory.
97
98         When an IN_CREATE event comes in, trigger a cleanup.
99         """
100         cleanup()
101
102
103 def main():
104     """Initialize the inotifications and start the main loop."""
105     syslog.openlog('invirt-janitor', syslog.LOG_PID, syslog.LOG_DAEMON)
106
107     watch_manager = pyinotify.WatchManager()
108     janitor = Janitor()
109     notifier = pyinotify.Notifier(watch_manager, janitor)
110     watch_manager.add_watch(_JANITOR_DIR,
111                             pyinotify.EventsCodes.ALL_FLAGS['IN_CREATE'])
112
113     # Before inotifying, run any pending cleanups; otherwise we won't
114     # get notified for them.
115     cleanup()
116
117     while True:
118         notifier.process_events()
119         if notifier.check_events():
120             notifier.read_events()
121
122
123 if __name__ == '__main__':
124     main()