[pve-devel] [PATCH] add qemu guest agent client

Stefan Priebe - Profihost AG s.priebe at profihost.ag
Fri Nov 23 09:22:43 CET 2012


Hello,

the QGA Support in QemuServer.pm was added but the guest agent client isn't.

Is there any status on this one?

Thanks!

greets,
Stefan
Am 04.09.2012 09:04, schrieb Alexandre Derumier:
> This implement qemu guest agent client.
>
> I Have take the qmp client code and try to adapt it.
>
> We cannot pass id in the quest agent client,so I always execute
>
> { "execute": "guest-sync", "arguments": { "id": sequenceid } }
> before each command
>
> exemple:
> sending commands
> ---------------
> { "execute": "guest-sync", "arguments": { "id": 123456 } }
> {"execute":"guest-ping"}
>
> parse result
> ------------
> { "return": 123456}\n{"return": {}}
>
> I don't know if it's the right way, so please review code.
>
> Signed-off-by: Alexandre Derumier <aderumier at odiso.com>
> ---
>   PVE/Makefile      |    5 +-
>   PVE/QGAClient.pm  |  279 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>   PVE/QemuServer.pm |   38 +++++++
>   3 files changed, 320 insertions(+), 2 deletions(-)
>   create mode 100755 PVE/QGAClient.pm
>
> diff --git a/PVE/Makefile b/PVE/Makefile
> index 232c881..8a70aeb 100644
> --- a/PVE/Makefile
> +++ b/PVE/Makefile
> @@ -1,11 +1,12 @@
>   PERLSOURCE = 			\
>   	QemuServer.pm		\
>   	QemuMigrate.pm		\
> -	QMPClient.pm
> +	QMPClient.pm		\
> +	QGAClient.pm
>
>   .PHONY: install
>   install:
>   	install -d ${DESTDIR}${PERLDIR}/PVE
>   	install -m 0644 ${PERLSOURCE} ${DESTDIR}${PERLDIR}/PVE/
>   	make -C VZDump install
> -	make -C API2 install
> \ No newline at end of file
> +	make -C API2 install
> diff --git a/PVE/QGAClient.pm b/PVE/QGAClient.pm
> new file mode 100755
> index 0000000..8880b5a
> --- /dev/null
> +++ b/PVE/QGAClient.pm
> @@ -0,0 +1,279 @@
> +package PVE::QGAClient;
> +
> +use strict;
> +use PVE::QemuServer;
> +use IO::Multiplex;
> +use POSIX qw(EINTR EAGAIN);
> +use JSON;
> +use Time::HiRes qw(usleep gettimeofday tv_interval);
> +
> +use Data::Dumper;
> +
> +# Qemu Guest Agent client.
> +#
> +# This implementation uses IO::Multiplex (libio-multiplex-perl) and
> +# allows you to issue quest agent commands to different VMs in parallel.
> +
> +# Note: kvm can onyl handle 1 connection, so we close connections asap
> +
> +sub new {
> +    my ($class, $eventcb) = @_;
> +
> +    my $mux = new IO::Multiplex;
> +
> +    my $self = bless {
> +	mux => $mux,
> +	fhs => {}, # $vmid => fh
> +	fhs_lookup => {}, # $fh => $vmid
> +	queue => {},
> +	current => {},
> +	errors => {},
> +    }, $class;
> +
> +    $self->{eventcb} = $eventcb if $eventcb;
> +
> +    $mux->set_callback_object($self);
> +
> +    return $self;
> +}
> +
> +# add a single command to the queue for later execution
> +# with queue_execute()
> +sub queue_cmd {
> +    my ($self, $vmid, $callback, $execute, %params) = @_;
> +
> +    my $cmd = {};
> +    $cmd->{execute} = $execute;
> +    $cmd->{arguments} = \%params;
> +    $cmd->{callback} = $callback;
> +
> +    push @{$self->{queue}->{$vmid}}, $cmd;
> +}
> +
> +# execute a single command
> +sub cmd {
> +    my ($self, $vmid, $cmd, $timeout) = @_;
> +
> +    my $result;
> +
> +    my $callback = sub {
> +	my ($vmid, $resp) = @_;
> +	$result = $resp->{'return'};
> +    };
> +
> +    die "no command specified" if !($cmd &&  $cmd->{execute});
> +
> +    $cmd->{callback} = $callback;
> +    $cmd->{arguments} = {} if !defined($cmd->{arguments});
> +
> +    $self->{queue}->{$vmid} = [ $cmd ];
> +
> +    if (!$timeout) {
> +	    $timeout = 3; # default
> +    }
> +
> +    $self->queue_execute($timeout);
> +
> +    my $cmdstr = $cmd->{execute} || '';
> +    die "VM $vmid qmp command '$cmdstr' failed - $self->{errors}->{$vmid}"
> +	if defined($self->{errors}->{$vmid});
> +
> +    return $result;
> +};
> +
> +my $cmdid_seq = 0;
> +my $next_cmdid = sub {
> +    $cmdid_seq++;
> +    return "$cmdid_seq";
> +};
> +
> +my $close_connection = sub {
> +    my ($self, $vmid) = @_;
> +	
> +    my $fh = $self->{fhs}->{$vmid};
> +    return if !$fh;
> +
> +    delete $self->{fhs}->{$vmid};
> +    delete $self->{fhs_lookup}->{$fh};
> +
> +    $self->{mux}->close($fh);
> +};
> +
> +my $open_connection = sub {
> +    my ($self, $vmid) = @_;
> +
> +    my $sname = PVE::QemuServer::qga_socket($vmid);
> +
> +    my $fh;
> +    my $starttime = [gettimeofday];
> +    my $count = 0;
> +    for (;;) {
> +	$count++;
> +	$fh = IO::Socket::UNIX->new(Peer => $sname, Blocking => 0, Timeout => 1);
> +	last if $fh;
> +	if ($! != EINTR && $! != EAGAIN) {
> +	    die "unable to connect to VM $vmid socket - $!\n";
> +	}
> +	my $elapsed = tv_interval($starttime, [gettimeofday]);
> +	if ($elapsed > 1) {
> +	    die "unable to connect to VM $vmid socket - timeout after $count retries\n";
> +	}
> +	usleep(100000);
> +    }
> +
> +    $self->{fhs}->{$vmid} = $fh;
> +    $self->{fhs_lookup}->{$fh} = $vmid;
> +    $self->{mux}->add($fh);
> +
> +    return $fh;
> +};
> +
> +my $check_queue = sub {
> +    my ($self) = @_;
> +
> +    my $running = 0;
> +	
> +    foreach my $vmid (keys %{$self->{queue}}) {
> +	my $fh = $self->{fhs}->{$vmid};
> +	next if !$fh;
> +
> +	if ($self->{errors}->{$vmid}) {
> +	    &$close_connection($self, $vmid);
> +	    next;
> +	}
> +
> +	if ($self->{current}->{$vmid}) { # command running, waiting for response
> +	    $running++;
> +	    next;
> +	}
> +
> +	if (!scalar(@{$self->{queue}->{$vmid}})) { # no more commands for the VM
> +	    &$close_connection($self, $vmid);
> +	    next;
> +	}
> +
> +	eval {
> +
> +	    my $cmd = $self->{current}->{$vmid} = shift @{$self->{queue}->{$vmid}};
> +	    $cmd->{id} = &$next_cmdid();
> +
> +	    my $qmpcmdid =to_json({
> +                execute => 'guest-sync',
> +                arguments => { id => int($cmd->{id}) } });
> +
> +	    my $qmpcmd = to_json({
> +		execute => $cmd->{execute},
> +		arguments => $cmd->{arguments}});
> +
> +	    $self->{mux}->write($fh, $qmpcmdid.$qmpcmd);
> +	};
> +	if (my $err = $@) {
> +	    $self->{errors}->{$vmid} = $err;
> +	} else {
> +	    $running++;
> +	}
> +    }
> +
> +    $self->{mux}->endloop() if !$running;
> +
> +    return $running;
> +};
> +
> +# execute all queued command
> +sub queue_execute {
> +    my ($self, $timeout) = @_;
> +
> +    $timeout = 3 if !$timeout;
> +
> +    $self->{current} = {};
> +    $self->{errors} = {};
> +
> +    # open all necessary connections
> +    foreach my $vmid (keys %{$self->{queue}}) {
> +	next if !scalar(@{$self->{queue}->{$vmid}}); # no commands for the VM
> +
> +	eval {
> +	    my $fh = &$open_connection($self, $vmid);
> +	    $self->{mux}->set_timeout($fh, $timeout);
> +	};
> +	if (my $err = $@) {
> +	    warn $err;
> +	    $self->{errors}->{$vmid} = $err;
> +	}
> +    }
> +
> +    my $running;
> +    for (;;) {
> +
> +	$running = &$check_queue($self);
> +
> +	last if !$running;
> +
> +	$self->{mux}->loop;
> +    }
> +
> +    # make sure we close everything
> +    foreach my $vmid (keys %{$self->{fhs}}) {
> +	&$close_connection($self, $vmid);
> +    }
> +
> +    $self->{queue} = $self->{current} = $self->{fhs} = $self->{fhs_lookup} = {};
> +}
> +
> +# mux_input is called when input is available on one of
> +# the descriptors.
> +sub mux_input {
> +    my ($self, $mux, $fh, $input) = @_;
> +
> +    return if $$input !~ m/}\n(.+)}\n$/;
> +    my $raw = $$input;
> +    # Remove the input from the input buffer.
> +    $$input = '';
> +    my $vmid = $self->{fhs_lookup}->{$fh};
> +    if (!$vmid) {
> +	warn "internal error - unable to lookup vmid";
> +	return;
> +    }
> +    eval {
> +	my @jsons = split("\n", $raw);
> +
> +	my $obj = from_json($jsons[0]);
> +
> +	my $curcmd = $self->{current}->{$vmid};
> +	die "unable to lookup current command for VM $vmid\n" if (!$curcmd);
> +
> +        my $cmdid = $obj->{return};
> +        die "received responsed without command id\n" if !$cmdid;
> +
> +    	if ($curcmd->{id} ne $cmdid) {
> +	    die "got wrong command id '$cmdid' (expected $curcmd->{id})\n";
> +    	}
> +
> +    	delete $self->{current}->{$vmid};
> +
> + 	$obj = from_json($jsons[1]);
> +
> +	if (my $callback = $curcmd->{callback}) {
> +  	    &$callback($vmid, $obj);
> +	}
> +
> +    };
> +    if (my $err = $@) {
> +	$self->{errors}->{$vmid} = $err;
> +    }
> +
> +    &$check_queue($self);
> +}
> +
> +# This gets called every second to update player info, etc...
> +sub mux_timeout {
> +    my ($self, $mux, $fh) = @_;
> +
> +    if (my $vmid = $self->{fhs_lookup}->{$fh}) {
> +	$self->{errors}->{$vmid} = "got timeout\n";
> +    }
> +
> +    &$check_queue($self);
> +}
> +
> +1;
> diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
> index bb0be42..c412283 100644
> --- a/PVE/QemuServer.pm
> +++ b/PVE/QemuServer.pm
> @@ -26,6 +26,7 @@ use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file
>   use PVE::INotify;
>   use PVE::ProcFSTools;
>   use PVE::QMPClient;
> +use PVE::QGAClient;
>   use Time::HiRes qw(gettimeofday);
>
>   my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
> @@ -2840,6 +2841,13 @@ sub vm_start {
>       });
>   }
>
> +sub vm_qga_cmd {
> +    my ($vmid, $execute, %params) = @_;
> +
> +    my $cmd = { execute => $execute, arguments => \%params };
> +    vm_qga_command($vmid, $cmd);
> +}
> +
>   sub vm_mon_cmd {
>       my ($vmid, $execute, %params) = @_;
>
> @@ -2888,6 +2896,36 @@ sub vm_qmp_command {
>       return $res;
>   }
>
> +sub vm_qga_command {
> +    my ($vmid, $cmd) = @_;
> +
> +    my $res;
> +
> +    my $timeout;
> +    if ($cmd->{arguments} && $cmd->{arguments}->{timeout}) {
> +        $timeout = $cmd->{arguments}->{timeout};
> +        delete $cmd->{arguments}->{timeout};
> +    }
> +
> +    eval {
> +        die "VM $vmid not running\n" if !check_running($vmid);
> +        my $sname = PVE::QemuServer::qga_socket($vmid);
> +        if (-e $sname) {
> +            my $qgaclient = PVE::QGAClient->new();
> +
> +            $res = $qgaclient->cmd($vmid, $cmd, $timeout);
> +        } else {
> +            die "unable to open qga socket\n";
> +        }
> +    };
> +    if (my $err = $@) {
> +        syslog("err", "VM $vmid qga command failed - $err");
> +        die $err;
> +    }
> +
> +    return $res;
> +}
> +
>   sub vm_human_monitor_command {
>       my ($vmid, $cmdline) = @_;
>
>



More information about the pve-devel mailing list