In invirt-remote:
authorEvan Broder <broder@mit.edu>
Wed, 12 Aug 2009 02:38:42 +0000 (22:38 -0400)
committerEvan Broder <broder@mit.edu>
Wed, 12 Aug 2009 02:38:42 +0000 (22:38 -0400)
  * Instead of running all of the disk-wiping dds simultaneously, run them
    sequentially using a janitor daemon (LP: #411486).

svn path=/trunk/packages/invirt-remote/; revision=2436

debian/changelog
debian/control
debian/invirt-remote-host.dirs [new file with mode: 0644]
debian/invirt-remote-host.init
host/usr/sbin/invirt-janitor [new file with mode: 0755]
host/usr/sbin/invirt-lvm

index 2c1de9d..c541704 100644 (file)
@@ -1,3 +1,10 @@
+invirt-remote (0.4.0) unstable; urgency=low
+
+  * Instead of running all of the disk-wiping dds simultaneously, run them
+    sequentially using a janitor daemon (LP: #411486).
+
+ -- Evan Broder <broder@mit.edu>  Tue, 11 Aug 2009 19:37:55 -0700
+
 invirt-remote (0.3.15) unstable; urgency=low
 
   * If a particular VM is being autoinstalled, include that in the
index c683795..7df832e 100644 (file)
@@ -21,8 +21,9 @@ Description: Invirt remote-control server
 
 Package: invirt-remote-host
 Architecture: all
-Depends: ${misc:Depends}, remctl-server, invirt-console-host,
- invirt-vnc-server, python-cjson, python-yaml, util-linux
+Depends: ${misc:Depends}, daemon, remctl-server, invirt-console-host,
+ invirt-vnc-server, python-cjson, python-pyinotify, python-yaml,
+ util-linux
 Description: Installs the Invirt host remctl configuration
  This is the remctl configuration for an Invirt host. It allows any
  commands to be run from the Invirt remote server
diff --git a/debian/invirt-remote-host.dirs b/debian/invirt-remote-host.dirs
new file mode 100644 (file)
index 0000000..aa12455
--- /dev/null
@@ -0,0 +1 @@
+var/lib/invirt-remote/cleanup
index d3c3849..1de5697 100755 (executable)
@@ -20,17 +20,35 @@ gen_config()
     done
 }
 
-case "$1" in
-  start|reload|force-reload|restart)
+reload_cfg()
+{
     log_begin_msg "Reloading config for $PACKAGE"
     gen_config
     log_end_msg $?
     invoke-rc.d openbsd-inetd start # returns 1 if inetd is running
-    [ $? -eq 1 ] && exit 0
+}
+
+case "$1" in
+  start)
+    reload_cfg
+
+    log_begin_msg "Starting the invirt-janitor for $PACKAGE"
+    daemon -n invirt-janitor -r invirt-janitor
+    log_end_msg $?
+    ;;
+  force-reload|restart)
+    reload_cfg
+
+    log_begin_msg "Restarting the invirt-janitor for $PACKAGE"
+    daemon -n invirt-janitor --restart
+    log_end_msg $?
     ;;
   stop)
+    log_begin_msg "Stopping the invirt-janitor for $PACKAGE"
+    daemon -n invirt-janitor --stop
+    log_end_msg $?
     ;;
   *)
-    log_success_msg "Usage: /etc/init.d/$PACKAGE {start|reload|force-reload|restart|stop}"
+    log_success_msg "Usage: /etc/init.d/$PACKAGE {start|force-reload|restart|stop}"
     ;;
 esac
diff --git a/host/usr/sbin/invirt-janitor b/host/usr/sbin/invirt-janitor
new file mode 100755 (executable)
index 0000000..75a637a
--- /dev/null
@@ -0,0 +1,107 @@
+#!/usr/bin/python
+
+"""Clean-up after people's deleted VMs.
+
+The Invirt janitor goes through and finds virtual disk images that
+users have requested we delete. For their privacy, it writes over the
+entire disk with /dev/zero, then removes the logical volume, restoring
+the space to the pool.
+
+A request is indicated to the janitor by creating a file in
+/var/lib/invirt-remote/cleanup/ corresponding to the name of the LV to
+delete. The janitor notices these requests using inotify.
+"""
+
+
+import os
+import subprocess
+import syslog
+import traceback
+
+import pyinotify
+
+
+_JANITOR_DIR = '/var/lib/invirt-remote/cleanup'
+
+
+def cleanup():
+    """Actually cleanup deleted LVs.
+
+    When triggered, continue to iterate over cleanup queue files,
+    deleting LVs one at a time, until there are no more pending
+    cleanups.
+    """
+    while True:
+        lvs = os.listdir(_JANITOR_DIR)
+        if not lvs:
+            break
+
+        lv = lvs.pop()
+        lv_path = '/dev/xenvg/%s' % lv
+
+        try:
+            syslog.syslog(syslog.LOG_INFO, "Cleaning up LV '%s'" % lv_path)
+
+            subprocess.check_call(['/usr/bin/ionice',
+                                   '-c', '2',
+                                   '-n', '7',
+                                   '/bin/dd',
+                                   'if=/dev/zero',
+                                   'of=%s' % lv_path,
+                                   'bs=1M'])
+
+            # Ignore any errors here, because there's really just not
+            # anything we can do.
+            subprocess.call(['/sbin/lvchange', '-a', 'n', lv_path])
+            subprocess.call(['/sbin/lvchange', '-a', 'ey', lv_path])
+            subprocess.check_call(['/sbin/lvremove', '--force', lv_path])
+
+            syslog.syslog(syslog.LOG_INFO, "Successfully cleaned up LV '%s'" % lv_path)
+        except:
+            syslog.syslog(syslog.LOG_ERR, "Error cleaning up LV '%s':" % lv_path)
+
+            for line in traceback.format_exc().split('\n'):
+                syslog.syslog(syslog.LOG_ERR, line)
+        finally:
+            # Regardless of what happens, we always want to remove the
+            # cleanup queue file, because even if there's an error, we
+            # don't want to waste time wiping the same disk repeatedly
+            os.unlink(os.path.join(_JANITOR_DIR, lv))
+
+
+class Janitor(pyinotify.ProcessEvent):
+    """Process inotify events by wiping and deleting LVs.
+
+    The Janitor class receives inotify events when a new file is
+    created in the state directory.
+    """
+    def process_IN_CREATE(self, event):
+        """Handle a created file or directory.
+
+        When an IN_CREATE event comes in, trigger a cleanup.
+        """
+        cleanup()
+
+
+def main():
+    """Initialize the inotifications and start the main loop."""
+    syslog.openlog('invirt-janitor', syslog.LOG_PID, syslog.LOG_DAEMON)
+
+    watch_manager = pyinotify.WatchManager()
+    janitor = Janitor()
+    notifier = pyinotify.Notifier(watch_manager, janitor)
+    watch_manager.add_watch(_JANITOR_DIR,
+                            pyinotify.EventsCodes.ALL_FLAGS['IN_CREATE'])
+
+    # Before inotifying, run any pending cleanups; otherwise we won't
+    # get notified for them.
+    cleanup()
+
+    while True:
+        notifier.process_events()
+        if notifier.check_events():
+            notifier.read_events()
+
+
+if __name__ == '__main__':
+    main()
index a2a6cc9..a505978 100755 (executable)
@@ -63,15 +63,9 @@ if subcommand == "lvremove":
             break
     ensureoff(machine)
     
-    # Fork. The child process wipes the LV and then deletes
-    # it. There's not really anything sane to do with errors (since
-    # this is running non-interactively), so let's just drop them on
-    # the floor for now.
-    if os.fork() == 0:
-        call(["/usr/bin/ionice", "-c", "2", "-n", "7", "/bin/dd", "if=/dev/zero", "of=%s" % new_lvpath])
-        call(["/sbin/lvchange", "-a", "n", new_lvpath])
-        call(["/sbin/lvchange", "-a", "ey", new_lvpath])
-        call(["/sbin/lvremove", "--force", new_lvpath])
+    # Touch a file corresponding to the new name of the LV; a separate
+    # daemon will handle wiping and deleting it.
+    open(os.path.join('/var/lib/invirt-remote/cleanup', new_lvname), 'w')
 elif subcommand == "lvresize":
     size = sys.argv[4]
     ensureoff(machine)