X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=tools%2Fvirt-uname.pl;h=3ff8d4c8bbab8e156aa1954ca72b875423351fce;hb=f3691aa4f1c48682da967b2f53a32f55e732746e;hp=9bca435bb3b37e5f41dcab07d59121e3a2afbe7a;hpb=aa37cb49b40b16c31b8da25d5dbf251ef2b3209c;p=virt-tools.git diff --git a/tools/virt-uname.pl b/tools/virt-uname.pl index 9bca435..3ff8d4c 100755 --- a/tools/virt-uname.pl +++ b/tools/virt-uname.pl @@ -19,6 +19,7 @@ use strict; use Net::SNMP; +use Sys::Virt; use Pod::Usage; use Getopt::Long; use Locale::TextDomain 'virt-tools'; @@ -27,17 +28,19 @@ use Locale::TextDomain 'virt-tools'; =head1 NAME -virt-uname, virt-ps, virt-ping - virtual machine information and statistics +virt-ps, virt-ping, virt-uname, virt-uptime - virtual machine information and statistics =head1 SYNOPSIS - virt-uname [--options] [domname] + virt-ifconfig [--options] [domname] virt-ps [--options] [domname] virt-ping [--options] [domname] - virt-ifconfig [--options] [domname] + virt-uname [--options] [domname] + + virt-uptime [--options] [domname] =head1 COMMON OPTIONS @@ -87,6 +90,14 @@ Write out the results in CSV format (comma-separated values). This format can be imported easily into databases and spreadsheets, but read L below. +=cut + +my $verbose; + +=item B<--verbose> | B<-v> + +Enable verbose messages, useful for debugging. + =back =cut @@ -95,6 +106,7 @@ GetOptions ("help|?" => \$help, "version" => \$version, "connect|c=s" => \$uri, "csv" => \$csv, + "verbose|v" => \$verbose, ) or pod2usage (2); pod2usage (1) if $help; if ($version) { @@ -103,15 +115,17 @@ if ($version) { } my %subcommands = ( - "virt-uname" => [ &do_uname, &title_uname ], - "virt-ps" => [ &do_ps, &title_ps ], - "virt-ping" => [ &do_ping, &title_ping ], + "virt-ps" => [ \&do_ps, \&title_ps ], + "virt-ping" => [ \&do_ping, \&title_ping ], + "virt-uname" => [ \&do_uname, \&title_uname ], + "virt-uptime" => [ \&do_uptime, \&title_uptime ], ); # Which subcommand? my ($do_it, $title_it); foreach (keys %subcommands) { if ($0 =~ /$_/) { + print STDERR "subcommand = $_\n" if $verbose; $do_it = $subcommands{$_}->[0]; $title_it = $subcommands{$_}->[1]; last; @@ -119,6 +133,18 @@ foreach (keys %subcommands) { } die "$0: cannot determine which sub-command to run\n" unless $do_it; +# If we are being run from a local directory, add that directory to +# the path, so we can be run from the source directory without being +# installed. +if (substr ($0, 0, 1) ne "/") { + $_ = $0; + s{/[^/]+$}{}; + $ENV{PATH} = "$_:$ENV{PATH}"; # XXX Windows? + print STDERR "PATH set to $ENV{PATH}\n" if $verbose; +} + +our $errors = 0; + # Do we have named guests? if (@ARGV == 0) { my $conn; @@ -135,51 +161,42 @@ if (@ARGV == 0) { my @domnames = map { $_->get_name () } @doms; if (@domnames) { - title_it (); + &$title_it (); foreach (@domnames) { - my ($key, $transport); - eval { - $key = get_key ($_); - $transport = get_transport ($_); - }; - if (!$@) { do_it ($_, $key, $transport) } + get_and_do_it ($_); } } } else { - title_it (); + &$title_it (); foreach (@ARGV) { - my ($key, $transport); - eval { - $key = get_key ($_); - $transport = get_transport ($_); - }; - if (!$@) { do_it ($_, $key, $transport) } + get_and_do_it ($_); } } -exit 0; - -=head1 virt-uname - -C displays the system information (kernel version etc) of -the guest. - -=cut - -sub title_uname +sub get_and_do_it { - print_row (__"Guest"); + # Turn any errors into warnings. + eval { + my ($key, $transport); + $key = get_key ($_); + $transport = get_transport ($_); + &$do_it ($_, $key, $transport); + }; + if ($@) { + $errors++; + warn "$@"; + } } -sub do_uname -{ - my $domname = shift; - my $key = shift; - my $transport = shift; +print STDERR "errors = $errors\n" if $verbose; +exit ($errors == 0 ? 0 : 1); +# virt-ifconfig is implemented as a separate program. -} +=head1 virt-ifconfig + +C displays the IP address of the guest. =head1 virt-ps @@ -225,14 +242,85 @@ sub do_ping } -# virt-ifconfig is implemented separately. +=head1 virt-uname -=head1 virt-ifconfig +C displays the system information (kernel version etc) of +the guest. -C displays the IP address of the guest. +=cut + +sub title_uname +{ + print_row (__"Guest", __"System name"); +} + +sub do_uname +{ + my $domname = shift; + my $key = shift; + my $transport = shift; + + my $session = get_snmp_session ($key, $transport); + my $sysDescr = "1.3.6.1.2.1.1.1.0"; + my $r = $session->get_request (-varbindlist => [$sysDescr]) + or die __x("SNMP error: {e}", $session->error); + print_row ($domname, $r->{$sysDescr}); + $session->close; +} + +=head1 virt-uptime + +C displays the uptime of the guest =cut +sub title_uptime +{ + print_row (__"Guest", __"Uptime"); +} + +sub do_uptime +{ + my $domname = shift; + my $key = shift; + my $transport = shift; + + my $session = get_snmp_session ($key, $transport); + my $sysUpTime = "1.3.6.1.2.1.1.3.0"; + my $r = $session->get_request (-varbindlist => [$sysUpTime]) + or die __x("SNMP error: {e}", $session->error); + print_row ($domname, $r->{$sysUpTime}); + $session->close; +} + +sub print_row +{ + my @fields = @_; + + local $_; + my $comma = 0; + + foreach (@fields) { + if (!$csv) { + printf "%-16s ", $_ + } else { + print "," if $comma; + $comma = 1; + + # XXX Use Text::CSV here. + if ($_ =~ /"/) { + s/"/""/; + printf "\"%s\"", $_; + } elsif ($_ =~ /,/ || $_ =~ /\n/) { + printf "\"%s\"", $_; + } else { + print $_; + } + } + } + print "\n"; +} + =head1 OVERVIEW Virt-tools are a set of tools that you can install in your virtual @@ -248,39 +336,95 @@ There are two parts to any virt-tools installation: some client programs like C and C that you run on the host, to query guest information. On the guest, you have to install and run a virt-tools service. Between the host and guest is a transport which -should be secured. The L section describes how -to configure guests and secure the transport. +should be secured. + +The L section describes how virt-tools appears +from the guest side. -Finally the L section describes the architecture of -virt-tools and provides information about diagnosing problems. +The L section describes the architecture of +virt-tools on the host side. -=head1 GUEST CONFIGURATION +=head1 GUEST ARCHITECTURE +In most cases, you can just install the C package in +your Linux guests, or the Windows virt-tools guest package in your +Windows guests, and everything should just work. In this section we +describe more about how it works (or is supposed to work) from the +guest side. +=head2 COMMUNICATIONS DIRECTORY +The guest writes various static, mostly unchanging, information into +its own directory. On Linux the directory is +C<@localstatedir@/lib/virt-tools/> and under Windows it is +C<%systemroot%\virttool\>. In the discussion below, this +communications directory is referred to as C<$GUESTCOMMSDIR>. +The host is able to read files out of this directory using +L (without any cooperation needed by the guest). +=head2 IP ADDRESSES +The host can't easily see the guest's IP address. The host provides +the guest with a network interface connected to a bridge, but the +guest can use any IP address it likes (although well-behaved guests +will usually have some static IPs or are allocated one by DHCP). +So when the guest starts up, or its IP address changes (usually these +are rare events) the guest writes a file +C<$GUESTCOMMSDIR/ip-EifaceE> which contains details of the IP +address of the interface EifaceE (eg. the file might be called +C under Linux). +C reads this file directly using L. +=head2 KEYS +When the guest is first installed (or more precisely, when the +virt-tools-guest package is first installed in the guest), a random +secret key is generated. This is used to encrypt communications with +the guest, and it is described in more detail below. -=head1 ARCHITECTURE +The key is written to C<$GUESTCOMMSDIR/key>. -Guests run an SNMP (Simple Network Management Protocol) server. The -host client tools access this server in order to query information -about the guest. They query this using standard SNMP calls. +=head2 SNMP DAEMON + +For process listings, and just about every other piece of data except +for IP address, guests run a completely standard SNMP (Simple Network +Management Protocol) server. The host client tools access this server +in order to query information about the guest. They query this using +standard SNMP calls. The protocol used is SNMPv3 (RFC 2571) which addresses security concerns in earlier versions of the protocol. In order to ensure that -only the host can access the SNMP server, the guest generates a random -secret key which the host must find out. Also the host must find a -suitable transport to connect to the SNMP server (eg. by finding the -IP address of the guest or using another transport into the guest). +only the host can access the SNMP server and see the results, all +communications are encrypted and authenticated using the guest's key. + +=head2 TRANSPORT + +There is not necessarily a network connection between the host and the +guest. There are many configurations of virtualization in which the +host has no network access to the guest: for example, if the host +firewalls itself off from the guest (or vice versa), or if the guest +has a physically separate network card from the host. + +Therefore the guest to host SNMP transport is not necessarily over an +IP network. Other transports are possible, including "vmchannel" +(where "vmchannel" is the generic name for a collection of specialized +host-guest communication channels implemented in different ways by +different hypervisors). + +The transport is written to C<$GUESTCOMMSDIR/transport>. -Once the key and the transport to the guest are worked out, the query -is a straightforward SNMP call: +=head1 HOST ARCHITECTURE + +On the host side, the host uses L to read the guest's +IP address and key, and uses some heuristics to determine the +transport to use. + +Once the key and the transport to the guest are worked out, programs +like C, C and so on are just making +straightforward SNMP calls: +-----------------+ +-----------------+ | host | | guest | @@ -290,7 +434,7 @@ is a straightforward SNMP call: The difficulty is in determining the key and the transport to use, which is what this section covers. You can also use this knowledge to -diagnose problems, and to create non-standard configurations. +diagnose problems or to create non-standard configurations. =head2 DETERMINE KEY @@ -298,33 +442,30 @@ All the host tools use an external helper program called C to get the key of the guest. (See L for the precise usage of this program). -The key is generated by the guest once -- when the virt-tools package -is installed in the guest. The key is written to a file -C (in the guest) which is readable only by -root. - -On Windows guests the key is written to -C<%systemroot%\virttools.key> +The key is generated by the guest once -- when the virt-tools-guest +package is installed in the guest. The key is written to a file +C<$GUESTCOMMSDIR/key> (in the guest) which is readable only by root. Using L the host can read any file in the guest, so it can read this key out directly. This is what the C program does, and you can run it by hand to verify its operation: - # virt-tools-get-key -v domname|uuid + # virt-tools-get-key -v domname abcdef1234567890 =head3 KEY CACHE C caches the keys of guests that it has seen before so it doesn't have to read them each time. The cache is in -C (in the host). +C<@localstatedir@/lib/virt-tools/keys/> (in the host). You can just delete the files in this directory at any time, I you can drop a file in here which contains the key of a guest. -To do this, create a file CUUIDE> -where EUUIDE is the guest's UUID as displayed by this command: +To do this, create a file +C<@localstatedir@/lib/virt-tools/keys/EUUIDE> where +EUUIDE is the guest's UUID as displayed by this command: virsh domuuid @@ -338,6 +479,23 @@ This cache never expires, unless you remove the files by hand. sub get_key { + my $domname = shift; + + my $cmd = "virt-tools-get-key"; + $cmd .= " -v" if $verbose; + # XXX quoting + $cmd .= " -c '$uri'" if $uri; + $cmd .= " '$domname'"; + + print STDERR "$cmd\n" if $verbose; + + open PIPE, "$cmd |" or die "$cmd: $!"; + my $line = ; + die __"no response from virt-tools-get-key\n" unless $line; + chomp $line; + close PIPE; + + $line } =head2 DETERMINE TRANSPORT @@ -351,7 +509,7 @@ This program tries a series of methods to determine how to access a guest, be it through a direct network connection or over some hypervisor-specific vmchannel. - # virt-tools-get-transport -v domname|uuid + # virt-tools-get-transport -v domname udp:192.168.122.33 You can diagnose problems with the transport by trying to run this @@ -361,7 +519,8 @@ command by hand. C caches the transports of guests that it has seen before so it doesn't have to determine them each time. The -cache is in C (in the host). +cache is in C<@localstatedir@/lib/virt-tools/transports/> (in the +host). As for the L, this directory is just some files that are named after the UUID of the guest, containing the transport. @@ -374,8 +533,80 @@ corresponding entry in the transport cache if it is not valid. sub get_transport { + my $domname = shift; + + my $cmd = "virt-tools-get-transport"; + $cmd .= " -v" if $verbose; + # XXX quoting + $cmd .= " -c '$uri'" if $uri; + $cmd .= " '$domname'"; + + print STDERR "$cmd\n" if $verbose; + + open PIPE, "$cmd |" or die "$cmd: $!"; + my $line = ; + die __"no response from virt-tools-get-transport\n" unless $line; + chomp $line; + close PIPE; + + $line +} + +=head2 SNMP QUERIES + +Standard SNMP queries are used between the host and guest. + +SNMP already supports many of the features we are trying to query +(eg. the UCD SNMP MIB provides a way to query the process list of a +machine in a form which is a de facto standard). + +To determine what precise queries are sent, run the tools in verbose +mode or examine the source. + +=cut + +sub get_snmp_session +{ + my $key = shift; + my $transport = shift; + + my ($hostname, $port, $domain); + if ($transport =~ /^udp:(.*):(.*)/) { + $domain = "udp"; + $hostname = $1; + $port = $2; + } elsif ($transport =~ /^tcp:(.*):(.*)/) { + $domain = "tcp"; + $hostname = $1; + $port = $2; + } else { + die __x("unknown transport type: {t}", t => $transport); + } + + if ($verbose) { + print STDERR "creating Net::SNMP session to $domain:$hostname:$port with key $key\n" + } + + my ($session, $error) = Net::SNMP->session ( + -version => 3, + -username => "virttools", + -authpassword => $key, + -authprotocol => "sha", + -privpassword => $key, + -privprotocol => "aes", + -hostname => $hostname, + -port => $port, + -domain => $domain, + ); + die __x("SNMP failure: {e}", e => $error) unless $session; + + $session; } +=head2 RUNNING YOUR OWN SNMP SERVER IN A GUEST + +I<(To be written)> + =head1 NOTE ABOUT CSV FORMAT Comma-separated values (CSV) is a deceptive format. It I like