Showing posts with label device. Show all posts
Showing posts with label device. Show all posts

Monday, September 26, 2011

Hot-Plugging Data With Python And Libvirt

The CD-ROM drive revolutionized the way we're able to move data around on personal computers.  Never mind the internet, the notion of seamlessly transporting information over the wire between two machines was unthinkable fifteen years ago.  But besides transferring data back and forth, we've also come to depend on portable storage devices for practical reasons.

Sometimes the network isn't available to us.  Or, maybe there is a network, but we simply don't want our data traversing it.  We like to carry data around with us — on USB drives.

The portable storage abstraction persists in virtual infrastructures.  The same practical limitations we experience with physical machines still affect those that run on a hypervisor — machines aren't going to come pre-loaded with all the data they'll ever need.  This is where portable storage — hot-pluggable storage — becomes advantageous.

Data and devices
Plugging data into a virtual machine means setting up a virtual device and attaching it to the domain.  With Libvirt, this is easy to do because everything is defined using an XML description.  This means that we can create a device on the fly and plug it in — even after the domain is running.  Hot-plugging devices like this is important because we can't afford to shut-down the machine, attach a device, and start it up.  This incurs too much overhead — not to mention the inflexibility.  Imagine you had to reboot your laptop in order for your operating system to recognize a USB drive — you might as well install a new hard drive in the mean time.

Here is an example of how you can pass some arbitrary data to a running virtual machine.  There are three phases required here to take a string and transform it into a device the machine can read from:
  1. Write the string to a file.
  2. Create an ISO file.
  3. Build the device XML
The Python code that builds the device an attaches it to a running domain...

import shutil
import os.path
import tempfile
import subprocess

import libvirt

def mkdata(data):
    tmpdir = tempfile.mkdtemp()
    tmpfile = tempfile.mkstemp(dir=tmpdir)[1]
    with open(tmpfile, 'w') as ofile:
        ofile.write(data)
    return tmpdir

def mkiso(indir):
    tmpiso = tempfile.mkstemp(suffix='.iso')[1]
    subprocess.call(['mkisofs', '-o', tmpiso, indir])
    shutil.rmtree(os.path.abspath(indir))
    return tmpiso
    
def mkdevice(iso):
    return """<disk type='file' device='disk'>
                <driver name='qemu' type='raw'/>
                <source file='%s'/>
                <target dev='sdb' bus='usb'/>
              </disk>""" % iso

if __name__ == '__main__':
    
    conn = libvirt.open('qemu:///system')
    domain = conn.lookupByName('MyVM')
    
    tmpdir = mkdata('Some virtual machine data...')
    tmpiso = mkiso(tmpdir)
    device = mkdevice(tmpiso)
    
    domain.attachDevice(device)

Let's take a closer look at what we're doing here.

The first function, mkdata(), takes an input value and writes it to a temporary file.  But before doing so, it creates a temporary directory for the file.  The reason being, we need a directory to create the ISO format for the device.

The second function, mkiso(), takes an input directory and passes it to the mkisofs command.  Once the ISO is built, we can remove the temporary directory and return the ISO file location.

The third function, mkdevice(), builds the Libvirt device we're going to attach to the domain.  The iso parameter is the ISO file we want to pass to the virtual machine, generated by mkiso().  You'll notice that this is a disk device, as that is what an ISO image is — a disk.  The driver element specifies that this disk is in raw format.  There are other options available such as qcow, but with this approach, the format is almost always raw.  The source element in the device description points to the ISO file we've just generated.

The target element tells the domain what the device label should be.  As the Libvirt documentation spells-out, there is never any guarantee that the device label you give it here will be retained by the guest operating system.  For example, in our case, we're telling the guest domain to label the new device as sdbsda is probably taken.  However, if sdb is already taken, then we won't know for sure what the device will be called.  This is one area that this method is lacking in — ensuring that the device will be named appropriately so it may be referenced later on.

Finally, with these three functions that'll build the device we're ready to attach it to a domain.  We do this here by establishing a Libvirt connection and locating the domain we're interested in.  Next, we build the device and attach it.

Using the data
If you ran the above example and all went well, you now have a new disk device attached to your domain.  This is great — new data has been made available, no need to access a network and no need to reboot.  But inside the guest, applications need access to the data to make use of it.

Below is an example of how you could listen for the new device — from within the machine — and read it.

import os, os.path
import subprocess
import time

while True:
    if not os.path.exists('/dev/sdb'):
        time.sleep(10)
        continue
    
    subprocess.call(['mount', '/dev/sdb', '/mnt/sdb'])
    for fname in os.listdir('/mnt/sdb'):
        with open(os.path.join('/mnt/sdb', fname)) as ifile:
            print 'Got some data...'
            print ifile.read()

Here, all we're doing is checking, every ten seconds, if the device we just attached exists.  If it does, we're then able to mount it and read from the file within the ISO.

Thursday, September 10, 2009

Python Libvirt Example

The libvirt virtualization library is a programming API used to manage virtual machines with a variety of hypervisors. There are several language bindings available for the libvirt library including Python. Within a given Python application that uses the libvirt library, the application can potentially control every virtual machine running on the host if used correctly. Libvirt also has the ability to assume control of remote hypervisors.

Virtual machines, or guest domains, have primary disks and potentially secondary disks attached to them. These block devices and even be added to a running virtual machine. But just like a physical host, it helps to know exactly how the virtual block devices for a given virtual machine are being utilized. This way, potential problems may be addressed before they occur. Libvirt provides the ability retrieve such statistics for these devices. Here is a Python example of how to do this.
#Example; Libvirt block stats.

#We need libvirt and ElementTree.
import libvirt
from xml.etree import ElementTree

#Function to return a list of block devices used.
def get_target_devices(dom):
#Create a XML tree from the domain XML description.
tree=ElementTree.fromstring(dom.XMLDesc(0))

#The list of block device names.
devices=[]

#Iterate through all disk target elements of the domain.
for target in tree.findall("devices/disk/target"):
#Get the device name.
dev=target.get("dev")

#Check if we have already found the device name for this domain.
if not dev in devices:
devices.append(dev)

#Completed device name list.
return devices

if __name__=="__main__":
#Connect to some hypervisor.
conn=libvirt.open("qemu:///system")

#Iterate through all available domains.
for id in conn.listDomainsID():
#Initialize the domain object.
dom=conn.lookupByID(id)

#Initialize our block stat counters.
rreq=0
rbytes=0
wreq=0
wbytes=0

#Iterate through each device name used by this domain.
for dev in get_target_devices(dom):
#Retrieve the block stats for this device used by this domain.
stats=dom.blockStats(dev)

#Update the block stat counters
rreq+=stats[0]
rbytes+=stats[1]
wreq+=stats[2]
wbytes+=stats[3]

#display the results for this domain.
print "\n%s Block Stats"%(dom.UUIDString())
print "Read Requests: %s"%(rreq)
print "Read Bytes: %s"%(rbytes)
print "Write Requests: %s"%(wreq)
print "Written Bytes: %s"%(wbytes)