[2436] | 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: |
---|
[2438] | 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 | |
---|
[2436] | 51 | syslog.syslog(syslog.LOG_INFO, "Cleaning up LV '%s'" % lv_path) |
---|
| 52 | |
---|
[2437] | 53 | # In a perfect world, this should be erroring out with |
---|
| 54 | # ENOSPC, so we ignore errors |
---|
| 55 | subprocess.call(['/usr/bin/ionice', |
---|
| 56 | '-c', '2', |
---|
| 57 | '-n', '7', |
---|
| 58 | '/bin/dd', |
---|
| 59 | 'if=/dev/zero', |
---|
| 60 | 'of=%s' % lv_path, |
---|
| 61 | 'bs=1M']) |
---|
[2436] | 62 | |
---|
| 63 | # Ignore any errors here, because there's really just not |
---|
| 64 | # anything we can do. |
---|
| 65 | subprocess.call(['/sbin/lvchange', '-a', 'n', lv_path]) |
---|
| 66 | subprocess.call(['/sbin/lvchange', '-a', 'ey', lv_path]) |
---|
| 67 | subprocess.check_call(['/sbin/lvremove', '--force', lv_path]) |
---|
| 68 | |
---|
| 69 | syslog.syslog(syslog.LOG_INFO, "Successfully cleaned up LV '%s'" % lv_path) |
---|
| 70 | except: |
---|
| 71 | syslog.syslog(syslog.LOG_ERR, "Error cleaning up LV '%s':" % lv_path) |
---|
| 72 | |
---|
| 73 | for line in traceback.format_exc().split('\n'): |
---|
| 74 | syslog.syslog(syslog.LOG_ERR, line) |
---|
| 75 | finally: |
---|
| 76 | # Regardless of what happens, we always want to remove the |
---|
| 77 | # cleanup queue file, because even if there's an error, we |
---|
| 78 | # don't want to waste time wiping the same disk repeatedly |
---|
| 79 | os.unlink(os.path.join(_JANITOR_DIR, lv)) |
---|
| 80 | |
---|
| 81 | |
---|
| 82 | class Janitor(pyinotify.ProcessEvent): |
---|
| 83 | """Process inotify events by wiping and deleting LVs. |
---|
| 84 | |
---|
| 85 | The Janitor class receives inotify events when a new file is |
---|
| 86 | created in the state directory. |
---|
| 87 | """ |
---|
| 88 | def process_IN_CREATE(self, event): |
---|
| 89 | """Handle a created file or directory. |
---|
| 90 | |
---|
| 91 | When an IN_CREATE event comes in, trigger a cleanup. |
---|
| 92 | """ |
---|
| 93 | cleanup() |
---|
| 94 | |
---|
| 95 | |
---|
| 96 | def main(): |
---|
| 97 | """Initialize the inotifications and start the main loop.""" |
---|
| 98 | syslog.openlog('invirt-janitor', syslog.LOG_PID, syslog.LOG_DAEMON) |
---|
| 99 | |
---|
| 100 | watch_manager = pyinotify.WatchManager() |
---|
| 101 | janitor = Janitor() |
---|
| 102 | notifier = pyinotify.Notifier(watch_manager, janitor) |
---|
| 103 | watch_manager.add_watch(_JANITOR_DIR, |
---|
| 104 | pyinotify.EventsCodes.ALL_FLAGS['IN_CREATE']) |
---|
| 105 | |
---|
| 106 | # Before inotifying, run any pending cleanups; otherwise we won't |
---|
| 107 | # get notified for them. |
---|
| 108 | cleanup() |
---|
| 109 | |
---|
| 110 | while True: |
---|
| 111 | notifier.process_events() |
---|
| 112 | if notifier.check_events(): |
---|
| 113 | notifier.read_events() |
---|
| 114 | |
---|
| 115 | |
---|
| 116 | if __name__ == '__main__': |
---|
| 117 | main() |
---|