libvirt-rb User's Guide
Overview
libvirt-rb
is a Ruby library for libvirt, a library sponsored
by RedHat for talking to various hypervisors with a single API. libvirt-rb
is backwards
compatible to libvirt 0.6.0 and consists of two namespaces for users to interact
with depending on their level of comfort with the libvirt API:
Libvirt
- All objects under this namespace provide a very Ruby-like object oriented interface above the raw libvirt API. This is the recommended way of using libvirt with Ruby. These objects are mostly compatible with the FFI layer as well (you may pass in aConnection
object in place of avirConnectPtr
for example).FFI::Libvirt
- A direct interface to the libvirt API using FFI. This is for the power players out there who need extremely customized functionality, with the tradeoff being that it is more complicated to use and you must manage your own pointers.
Installation
Prerequisites
Before the library itself is installed, libvirt must be installed on the system. How to do this for various operating systems is shown below.
Mac OS X
On Mac OS X, homebrew is the recommended way of installing Mac OS X. There is currently not a MacPort for it.
brew install libvirt
Linux
Most linux flavors have a libvirt installation in their respective package managers. The libvirt library might be slightly out of date but if you find you need the most up-to-date libvirt, its very easy to compile by hand as well.
apt-get install libvirt
Windows
While libvirt supposedly compiles on Windows, this is still a work in progress on my part to verify the install and test the library. Hang in there!
Install libvirt-rb
libvirt-rb
is packaged as a RubyGem, making the installation a one-step
process:
gem install libvirt
Basic Concepts and Usage
This section will cover the basic concepts of the higher level portion of
the libvirt ruby library (that is, it won't talk directly about the FFI
portion, which is mentioned in a later section). This is the portion of the
library that most people will use and is the recommended way of interacting
with libvirt from ruby.
This section will not try cover how libvirt works or the various uses of libvirt. Libvirt itself has many pages of documentation on this which you should read to learn more about it.
Establishing a Connection to Libvirt
The first step before anything can be done with libvirt is to establish
a connection to either a local or remote instance of libvirt (if remote,
you would be connecting to a libvirtd
instance). Libvirt establishes
connections using a URI. Each line below is another example on how to
establish a connection:
conn = Libvirt.connect
conn = Libvirt.connect("test:///default")
conn = Libvirt.connect("qemu+ssh://10.0.0.1/system")
conn = Libvirt.connect("vbox:///system", :readonly => true)
This connection is the base object used to do most other things. Libvirt has been made so that when you're done with the connection and it is garbage collected, the underlying connection data is properly freed as well. In fact, this is true for all objects in the libvirt library. If you find a memory leak with the libvirt objects, it is a bug.
Accessing Collections
Libvirt objects, especially the connection, exposes a variety of collection objects, such as domains, interfaces, networks, etc. These collections are all exposed in a similar way, and this section is meant to give a rough overview on how they work. Exact details for each collection can be seen by reading their respective documentation (such as Libvirt::Collection::DomainCollection).
Using and Accessing Objects in Collections
All collections can be treated as Array
-like objects. They are
Enumerable
and provide additional methods as well. All collections
implement the all
method to retrieve all of their collection.
Some collections can be drilled down further, such as domains, with
inactive
and just active
.
Examples of using a collection are shown below:
# You can use a collection like an array:
conn.domains.include?(other)
# You can iterate over a collection:
conn.domains.each do |domain|
puts "Domain: #{domain.name}"
end
# You can drill down further on some collections:
puts "Inactive domains: #{conn.domains.inactive.length}"
# You can explicitly access all domains, though this is
# the same as treating the whole collection as an array:
puts "All domains: #{conn.domains.all.length}"
Understanding these collections are fundamental to using the library, so play around with them and get comfortable.
Creating and Defining Objects in Collections
Most collection objects support creation and definition. The difference in libvirt is that defining will create a persistent object that survives node reboots, but it will not start immediately. Creating, on the other hand, creates and starts the object but it won't persist after reboots, typically. Refer to the actual libvirt documentation for verification on this.
Everything in libvirt is defined using XML. In the future, the libvirt-rb library will probably provide Ruby object wrappers around these XML structures but in the current version, you are expected to pass in the XML as a string. An example is shown below:
volume_xml = <<-XML
<volume>
<name>test</name>
<key>g9eba311-ea76-4e4a-ad7b-401fa81e38c8</key>
<source>
</source>
<capacity>8589934592</capacity>
<allocation>33280</allocation>
<target>
<format type='raw'/>
<permissions>
<mode>00</mode>
<owner>0</owner>
<group>0</group>
</permissions>
</target>
</volume>
XML
volume = conn.storage_pools.first.volumes.create(volume_xml)
Note: You can also retrieve the XML for an object using the xml
method which is available on most objects, such as Libvirt::Domain#xml.
Using Libvirt Objects
Once you have retrieved a single libvirt object, such as Connection or Domain, there are common ways to access information and manipulate the object.
All objects have attributes which are accessed using simple method calls, an example using a domain object below:
puts domain.name
puts domain.uuid
puts domain.xml
puts domain.state
Additionally, there are usually methods to control the state of an object, and transition it to new states, such as starting or stopping a domain:
domain.start
domain.stop
domain.undefine
These methods return a boolean true
or false
depending on whether
they succeed or not.
Error Handling
Libvirt itself provides a great error handling mechanism through callbacks. By default, libvirt-rb will raise an Libvirt::Exception::LibvirtError whenever an error occurs. That error contains a human readable message and an error code, as well as a backtrace as to where the error could have occurred. The exception simply wraps a Libvirt::Error object.
If, instead of raising an exception for every error, you'd like custom behavior, you can set a new callback which will be called every time:
Libvirt::Error.on_error do |error|
# Do anything you want with `error`...
end
You can also disable error callbacks alltogether by specifying no block:
Libvirt::Error.on_error
Most methods return nil
on error. Some methods have no reasonable
way of representing an error (for example nil
might just mean a
find failed), so doing nothing is not recommended.
Advanced: Direct FFI Access
For advanced users out there, there is nearly 100% FFI coverage over
the libvirt API from the FFI::Libvirt
namespace. This is a mostly
flat namespace for all the API methods, with classes for the various
structs. An example of using this API directly:
c = FFI::Libvirt.virConnectOpen("test:///default")
puts "Connected URI: #{FFI::Libvirt.virConnectGetURI(c)}"
Creating libvirt-rb Objects with FFI Pointers
Many of the pointers returned by the FFI layer can be used to initialize higher level objects, allowing a smooth transition between FFI and libvirt-rb. An example of this is shown below:
c = FFI::Libvirt.virConnectOpen("test:///default")
# Use the pointer to create a Connection object, this is the
# same as doing `Libvirt.connect("test:///default")`
conn = Libvirt::Connection.new(c)
# We are responsible for cleaning up the pointer. Since the Connection
# object took ownership, this free simply decreases a reference count
FFI::Libvirt.virConnectFree(c)
Warning: As you can see through the example above, you must remember to ALWAYS free your pointers after initializing a higher level object. This is to ensure that memory is properly freed. Using the FFI API makes it very easy to leak memory, so you have been warned.
Using libvirt-rb Objects as Pointer Arguments
In addition to creating libvirt-rb objects, the libvirt-rb objects can
also be used as pointer arguments for the FFI API. This is because all
of the objects implement the to_ptr
method. An example is shown below:
c = Libvirt.connect("test:///default")
puts "URI: #{FFI::Libvirt.virConnectGetURI(c)}"
Warning: If you store the pointer directly somewhere (assigning
the to_ptr
value to a variable), do not forget to increase the
reference count by calling the respective API, since when the libvirt-rb
objects are garbage collected, they automatically free the pointer.