Getting started with Clojure and Libvirt, Part 1
In this post I will give a gentle introduction to interacting with and controlling libvirt from Clojure.
But first a couple of definitions:
- Libvirt is an API/toolkit for interacting with Linux virtualization systems (including KVM/QEMU, Xen, OpenVZ, Oracle VirtualBox, VMWare ESX/GSX/Workstation/Player, Microsoft Hyper-V, LXC, UML, etc). It is a very active project that is widely used by many (maybe most) higher level virtualization platforms.
- Clojure is a relative new language that brings the dynamic power of LISP to the huge Java ecosystem. At LonoCloud we are using Clojure to create an operating system for cloud applications (more on that in later posts).
It is a truism that any time you have two awesome (or not so awesome) ideas, eventually somebody will try to put them together. Fortunately in this case, Libvirt provides Java API bindings which means it also has Clojure bindings so combining them is quite easy. Let's get started...
My assumption is that you have a access to Linux system on which you have administrator/sudo permission.
Familiarity with Clojure and libvirt will be helpful but not critical for following this walkthrough. If you are familiar with libvirt and the libvirt APIs but are new to Clojure then this walkthrough will give you a taste for the power and expressiveness of Clojure.
Install a Java JDK if you don't have one already. On an Ubuntu-like system you can do something like this:
sudo apt-get install openjdk-7-jdk # 6 for older systems sudo update-alternatives --config java # select the one just installed
Install the libvirt library and libvirtd service. On a Ubuntu-like system you can do something like this:
sudo apt-get install libvirt-bin libvirt0
Download the leiningen script to somewhere on your PATH. Here is an example that assumes you have $HOME/bin on your PATH.
wget https://raw.github.com/technomancy/leiningen/stable/bin/lein -O $HOME/bin/lein chmod +x $HOME/bin/lein
Launch the REPL
Use lein to create a new project skeleton.
lein new clj-libvirt cd clj-libvirt
Add JNA and libvirt Java bindings to the leiningen project.clj file. It should look something like this:
(defproject clj-libvirt "1.0.0-SNAPSHOT" :description "Use Libvirt from Clojure" :dependencies [[org.clojure/clojure "1.2.1"] [net.java.dev.jna/jna "3.3.0"] [org.libvirt/libvirt "0.4.7"]])
Use lein to pull down the project dependencies.
Start a Clojure REPL using lein. This REPL environment will have all the project dependencies available on the Java CLASSPATH.
Libvirt from Clojure
You should now have a Clojure REPL (read eval print loop) running with the "user=>" prompt indicating that we are in the default REPL namespace called "user" and that the REPL is waiting for input. In the following examples I will show the REPL prompt followed by the text to type followed by the output and/or return value resulting from running the command.
Start by importing the Libvirt Connect object.
user=> (import '(org.libvirt Connect)) org.libvirt.Connect
Now create a connection to the libvirt mock/test driver which is an ephemeral memory only driver for test purposes. The "Connect." syntax is the Clojure way of calling a Java constructor.
user=> (def mock-conn (Connect. "test:///default" false)) #'user/mock-conn user=> mock-conn #<Connect org.libvirt.Connect@50903025>
Make sure things are working by querying the Libvirt library version. Note that getLibVirVersion is a (Java) method of the connection object we defined above.
user=> (.getLibVirVersion mock-conn) 7005
The test driver automatically starts with a faux virtual machine that is defined and "running". List the running domains (virtual machine instances are called domains in libvirt terminology).
user=> (.listDomains mock-conn) #<int [I@4a504ec1>
That's less than useful. This ugly output indicates that we have a Java array of ints. Wrap this in Clojure sequence which the REPL knows how to print.
user=> (seq (.listDomains mock-conn)) (1)
Much better. This indicates we have one running domain that has an ID of 1. To interact with the domain you need use one of the Connect methods that returns a Domain object (domainLookupBy*). We know the running domain ID, so use domainLookupByID method to get a domain object. Then get the name of the domain and info about it.
user=> (def mock-dom (.domainLookupByID mock-conn 1)) #`user/mock-dom user=> mock-dom #<Domain org.libvirt.Domain@5a56b93a> user=> (.getName mock-dom) "test" user=> (.getInfo mock-dom) #<DomainInfo state:VIR_DOMAIN_RUNNING maxMem:8388608 memory:2097152 nrVirtCpu:2 cpuTime:1322765442068879000 >
Now stop the domain and verify that it is off.
user=> (.destroy mock-dom) nil user=> (.getInfo mock-dom) #<DomainInfo state:VIR_DOMAIN_SHUTOFF maxMem:8388608 memory:2097152 nrVirtCpu:2 cpuTime:1323714453027037000 >
Now start it back up again.
user=> (.create mock-dom) 0 user=> (.getInfo mock-dom) #<DomainInfo state:VIR_DOMAIN_RUNNING maxMem:8388608 memory:2097152 nrVirtCpu:2 cpuTime:1323714627116985000 >
Getting More Info
The Connection and Domain objects have a number of useful methods. List their methods names by mapping the getName method onto the list of methods.
user=> (map #(.getName %) (.getMethods (class mock-conn))) ("finalize" "close" "getType" "getHostName" ...) user=> (map #(.getName %) (.getMethods (class mock-dom))) ("shutdown" "finalize" "getName" "destroy" ...)
Those are mind-numblingly long lists of methods. For a user-readable reference you will probably want to use the Libvirt javadoc page.
That's all for now. I hope you have seen in this post how easy it is to interact with and control Libvirt from Clojure. Of course, we haven't actually done anything useful yet since the only interaction was with Libvirt mock/test driver. In a follow-up post I will walk through creating and interacting with real virtual domains instead of just virtual virtual domains.