Query kvm xml information script

Introduction

One of the problems I was expieriencing after creating a machine is to gather information for further post scripts. I had thaught of creating a mysql table with all the information of the hosts, but I have’nt designed it yet, and decided to rather query information straight from the domain xml.

A further possibility for the scipt is to run it from cron, and check if the database is updated with the correct information.

Setup

According to my previous post ( http://wp.me/p1kux7-bi ), the machine setup has not changed.

Script

root@localhost # cat host_info.pl
#!/usr/bin/perl
###############
#
#  Query information about the host
#
use strict;
use warnings;
use Getopt::Long;
use Switch;

##############
#  XML location and variables
my $XMLDIR="/etc/libvirt/qemu";
my $debug = 0;
my $help = 0;
my $host = 0;
my $list = 0;
my $memory = 0;
my $vcpu = 0;
my $disk = 0;
my $mac = 0;
my $name = 0;
my $verbose =0;
my $all = 1;

################
# Arguments
GetOptions("help|?" => \$help,
           "verbose|v" => \$verbose,
           "h|host=s" => \$host,
           "l|list" => \$list,
           "xml=s"   => \$XMLDIR,
           "mem" => sub { $memory=1 , $all=0 } ,
           "disk" => sub { $disk=1 , $all=0 } ,
           "cpu" => sub { $vcpu=1 , $all=0 } ,
           "name" => sub { $name=1, $all=0 },
           "mac" => sub { $mac=1, $all=0},
           "all" => \$all,
           "d|debug" => \$debug
        );

if ( $help ) {
        print "How to use the query host tool\n";
        print "$0 \$options \n";
        print "-? or help               help\n";
        print "-l or list               list all host information\n";
        print "-h or host               show information for one host\n";
        print "-xml                     Change default XML directory (default: '$XMLDIR')\n";
        print "-v or verbose            be verbose by output\n";
        print "-mem                     show memory\n";
        print "-name                    show name in output\n";
        print "-disk                    show disk\n";
        print "-cpu                     return number of cpus\n";
        print "-mac                     return mac address\n";
        print "-all                     print all info\n";
        print "-debug                   print debug information\n";
}

sub openxml($) {
    my $file= $_[0];
    $debug and print $file."\n";
    my $omemory = 0 ;
    my $ovcpu = 0 ;
    my $odisk = 0 ;
    my $omac = 0 ;
    my $oname = 0 ;
    my ( $prefix,$postfix ) = 0;
    open ( my $FILE, $file );
    while ( my $line = <$FILE>) {
        chomp $line;
        $debug and print $line."\n";
        if ( $line =~ /^\s*<mac address='/ ) {
            ( $prefix, $omac, $postfix ) = $line =~ /(^\s*<mac address=')([^']*[^'])('\/>\s*$)/;
            $debug and print "The mac address is:'$omac'\n";
        }
        if ( $line =~ /^\s*<memory>/ ) {
            ( $prefix, $omemory, $postfix ) = $line =~ /(^\s*<memory>)([^>]*[^<])(<\/memory>\s*$)/;
            $debug and print "My memory is:'$omemory'\n";
        }
        if ( $line =~ /^\s*<vcpu>/ ) {
            ( $prefix, $ovcpu, $postfix ) = $line =~ /(^\s*<vcpu>)([^<>]*)(<\/vcpu>\s*$)/;
            $debug and print "My Vcpu is:'$ovcpu'\n";
        }
        if ( $line =~ /^\s*<source dev='/ ) {
            ( $prefix, $odisk, $postfix ) = $line =~ /(^\s*<source dev=')([^']*[^'])('\/>\s*$)/;
            $debug and print "My disk is:'$odisk'\n";
        }
        if ( $line =~ /^\s*<name>/ ) {
            ( $prefix, $oname, $postfix ) = $line =~ /(^\s*<name>)([^']*[^'])(<\/name>\s*$)/;
            $debug and print "My name is:'$oname'\n";
        }
    }
    return ( $oname, $omemory, $omac, $ovcpu, $odisk );
    close $FILE;
}

sub format(@) {
    my ( $fname, $fmemory, $fmac, $fcpu, $fdisk) = @_;
    my $outstring = "";
    $debug and print "My variables for format sub:name =>'$fname' memory =>'$fmemory' mac =>'$fmac' cpu =>'$fcpu' disk =>'$fdisk'\n";
    if ( $all ) {
        $debug and print "All argument found.\n";
        ($verbose and $outstring= "host=$fname memory=$fmemory mac=$fmac cpu=$fcpu disk=$fdisk" ) or $outstring="$fname $fmemory $fmac $fcpu $fdisk";
    } else {
        $debug and print "I am in the else tree\n";
        if ( $name ne 0 ) {
            $debug and print "Name specified\n";
            ($verbose and $outstring .= "host=$fname ") or $outstring.="$fname ";
            $debug and print "My outstring is:'$outstring'\n";
        }
        if ( $memory ne 0 ) {
            $debug and print "Memory specified.\n";
            ($verbose and $outstring .= "memory=$fmemory ") or $outstring.="$fmemory ";
            $debug and print "\n";
        }
        if ( $mac ne 0 ) {
            $debug and print "Mac specified.\n";
            ($verbose and $outstring .= "mac=$fmac ") or $outstring.="$fmac ";
            $debug and print "\n";
        }
        if ( $vcpu ne 0 ) {
            $debug and print "CPU specified.\n";
            ($verbose and $outstring .= "cpu=$fcpu " ) or $outstring.="$fcpu ";
            $debug and print "\n";
        }
        if ( $disk ne 0 ) {
            $debug and print "Disk specified.\n";
            ($verbose and $outstring .= "disk=$fdisk") or $outstring.="$fdisk ";
            $debug and print "\n";
        }
    }
    $debug and print "My outstring is:'$outstring'\n";
    return($outstring);
}

if ( $host ) {
    $debug and print "Host specified :'$host'\n";
    my $file="$XMLDIR/$host.xml";
    $debug and print "My file is '$file'\n";
    if ( -e $file ) {
        $debug and print "File exists\n";
        my @output = openxml($file);
        $debug and print "My output was:'@output'\n";
        ( $verbose and print &format(@output)."\n") or print &format(@output)."\n";
    } else {
        $debug and print "File doesnt exist:'$file'\n";
    }
}
elsif ( $list ) {
    $debug and print "List specified\n";
    my @files=<$XMLDIR/*.xml>;
    $debug and print "My list is:'@files'\n";
    foreach my $file ( @files ) {
        $debug and print "File found doing sub on it:'$file'\n";
        my @output = openxml($file);
        $debug and print "My output was:'@output'\n";
        ( $verbose and print &format(@output)."\n" ) or print &format(@output)."\n";
    }
} else {
        $debug and print "No host or list specified.\n";
}

Explanation

Let’s iterate through the lines of code.

The entry point of the script is at line 137. There are 3 possbilities. Either 1 machine information is requested then a host is specified. If every information is requested from all hosts then a list is  generated. An unexpected possibility that neither a specific host or list is requested then nothing is printed  (except if we specify the debug option).

Since kvm names its xml files according to the domainname, this can be used for querying. It is possible in some setups when the domainname.xml is renamed and the filename and the domainname entry in the xml are not the same. Some checks or monitoring solutions need to find such cases aswell.

In the case that a list has to be processed, all xml files within the requested folder  (default for kvm is /etc/libvirt/qemu on debian) are added to a array, and with a loop every file is treated like a single host, passed onto openxml sub, which will be discussed later.

If only a single host is required the above step can be skipped and the file can be opened with openxml sub.

Openxml sub

This is the heart of the program, which can be attuned to your requirments. Attributes which I found important were mac address, maximum memory, virtual cpu limit, name and disk(this is useful for example for resize.sh at http://wp.me/p1kux7-bi ). If other information is required this sub should be changed.

One interesting question is if all xml files are according to standard format, and what are those standards in your enviroment. For example I have vcpus defined as follows:


<vcpu>2</vcpu>

According to libvirt domain xml format guide at http://libvirt.org/formatdomain.html this should be the correct and accepted version,although I have seen setups using different format:


<vcpu=2/>

After openxml sub is run  it returns with all information.

For large scale deployments this is not optimised. There is alot of unneeded information queried which should never be touched if not requested. CPU time and IO is expensive and should be kept to minimum.

Format sub

Format sub prints the information. This has been prepared for user and script handling. The main idea is that if we specify the -v (verbose) argument, the output is verbose  showing which value represents what:

root@localhost# host_info.pl -v -h debian
host=debian memory=262144 mac=ac:de:48:4a:88:f0 cpu=1 disk=/dev/mapper/lvm-vg_debian

or without the verbose argument for script parsing:

root@localhost# host_info.pl -h debian
debian 262144 ac:de:48:4a:88:f0 1 /dev/mapper/lvm-vg_debian

There are also some extra arguments which can be supplied if specific information is required. If we only need the memory, or mac address we can simly query that:

root@localhost# host_info.pl -h debian -v -mac
mac=ac:de:48:4a:88:f0

Also the requested information can be chained together:


root@localhost# host_info.pl -h debian -v -mac -mem
memory=262144 mac=ac:de:48:4a:88:f0

In case a list is requested all information is printed in a new line.

Thaughts

My purpose with this script is to feed post scripts and have the possibility to update sql database with information or to check data cosistency after updates.

If we are in a large scale enviroment then disk IO for querying files can be costful and a backend database file would be cheaper and easier to query.

Author: S4mur4i

Happy in the unhappy world.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s