[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