A strange concoction
Libvirtweb is a bland and unoriginal name for a spiffy new web interface to libvirt. Huzzah, I am releasing something!
This is an early prototype kind of thing. Here’s what it looks like:
You will find the code and some quick instructions are hosted at Github:
http://github.com/stephank/libvirtweb/
The rest of this post is some background.
Our office is a battlefield split in two
On the one side, there’s Linux on the desktop, Mac OS X on the desktop, Linux machines powering all primary services we sell. And then on the other, Microsoft Exchange, and Windows administrators. Sarcastic retorts and counter retorts go back and forth.
So far, this is holding up fairly okayish. There’s just one piece of iron powering our office, and a Windows virtual machine gobbling up about half of it’s resources.
We’ve tried a couple of alternative virtualization technologies, but surprisingly, an Ubuntu Server install with KVM was the most stable of all! Now comes the problem of colleagues wanting to manage the thing from their Windows desktops.
Turns out it was a good choice not to make Windows the host. When we doubled up the processor, we found our Windows license goes up to just 4 cores. Score one for the *nix side.
Face to face with libvirt
No doubt one of nastiest obstacles in creating this was libvirt. When you look at it’s goals from a high level (a very very high level), it sounds nice: a single API to manage virtual machines of any kind.
In practice, that has become a single API to manage local and remote iron over various types of connections and tunnels, and running various virtualization technologies. All that in a blocking fashion. Hell, I can’t even imagine how to do all that in a nice asynchronous API.
It looks like the only useful thing the libvirt API can be used for is to script some things in Python. And the Python bindings are very limited. Scripting things in bash works just as well using virsh.
If I had a say in it, the libvirtd daemon would not be optional, and would simply expose a DBus API. Rather like NetworkManager.
Mixing Twisted and CherryPy
The above is written in Twisted and CherryPy. However, when dealing with SSH tunnels, I had to find a way to take the tunnel out of the equation for the browser. The approach I took is a proxy for the RFB protocol, which is what is used to view consoles in KVM and QEMU.
Adding an RFB server to a CherryPy server is not really one of CherryPy’s use cases. However, CherryPy can be treated like a WSGI application. This allowed me to host CherryPy in a twisted.web.wsgi.WSGIResource, and mix the rest of my Twisted components with the webapp.
Using CherryPy as a WSGI application, and specifically in Twisted, is not really documented well anywhere. So here’s a snippit of code to do it. This is based on CherryPy 3.1.2 and Twisted 8.2.0.
#!/usr/bin/env python
import cherrypy
from twisted.internet import reactor
from twisted.web import wsgi, server
from twisted.python import threadpool
# Here's our hello world CherryPy application
class Root(object):
@cherrypy.expose
def index(self):
return "Hello world!"
# Use the 'embedded' configuration template
cherrypy.config.update({'environment': 'embedded'})
# We need to unsubscribe the CherryPy server to prevent a port conflict
cherrypy.server.unsubscribe()
# Start CherryPy internals
cherrypy.engine.start()
# Make sure we shut down CherryPy when we're done
reactor.addSystemEventTrigger('after', 'shutdown', cherrypy.engine.exit)
# Create a WSGI callable from our application
app = cherrypy.Application(Root())
# Twisted needs a threadpool to run WSGI applications
threads = threadpool.ThreadPool()
threads.start()
# Make sure we shut this down too
reactor.addSystemEventTrigger('after', 'shutdown', threads.stop)
# Setup the twisted.web factory, and listen on 8080
resource = wsgi.WSGIResource(reactor, threads, app)
factory = server.Site(resource, 'server.log')
reactor.listenTCP(8080, factory)
# The main loop
reactor.run()