]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
bump version to 3.0-29
[qemu-server.git] / PVE / API2 / Qemu.pm
index 39a18ed215d596b9fbfb0b712a2389211c0cad43..9450b16d36c96f916cfcfdde5133014b45409ccd 100644 (file)
@@ -3,6 +3,7 @@ package PVE::API2::Qemu;
 use strict;
 use warnings;
 use Cwd 'abs_path';
+use Net::SSLeay;
 
 use PVE::Cluster qw (cfs_read_file cfs_write_file);;
 use PVE::SafeSyslog;
@@ -128,17 +129,17 @@ my $create_disks = sub {
 
            my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
 
-           my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+           my $volid_is_new = 1;
 
-           my $volid_is_new = 1; 
-
-           if ($conf->{$ds}) { 
-               my $olddrive = PVE::QemuServer::parse_drive($ds, $conf->{$ds}); 
-               $volid_is_new = undef if $olddrive->{file} && $olddrive->{file} eq $volid; 
+           if ($conf->{$ds}) {
+               my $olddrive = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
+               $volid_is_new = undef if $olddrive->{file} && $olddrive->{file} eq $volid;
            }
 
            if ($volid_is_new) {
 
+               my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
                PVE::Storage::activate_volumes($storecfg, [ $volid ]) if $storeid;
 
                my $size = PVE::Storage::volume_size_info($storecfg, $volid);
@@ -497,6 +498,7 @@ __PACKAGE__->register_method({
            { subdir => 'rrddata' },
            { subdir => 'monitor' },
            { subdir => 'snapshot' },
+           { subdir => 'spiceproxy' },
            ];
 
        return $res;
@@ -662,16 +664,16 @@ my $delete_drive = sub {
        my $volid = $drive->{file};
 
        if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
-           if ($force || $key =~ m/^unused/) {    
-               eval { 
-                   # check if the disk is really unused 
+           if ($force || $key =~ m/^unused/) {
+               eval {
+                   # check if the disk is really unused
                    my $used_paths = PVE::QemuServer::get_used_paths($vmid, $storecfg, $conf, 1, $key);
-                   my $path = PVE::Storage::path($storecfg, $volid); 
+                   my $path = PVE::Storage::path($storecfg, $volid);
 
                    die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
                        if $used_paths->{$path};
 
-                   PVE::Storage::vdisk_free($storecfg, $volid); 
+                   PVE::Storage::vdisk_free($storecfg, $volid);
                };
                die $@ if $@;
            } else {
@@ -766,9 +768,13 @@ my $vmconfig_update_disk = sub {
                &$safe_num_ne($drive->{iops}, $old_drive->{iops}) ||
                &$safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd}) ||
                &$safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr})) {
-               PVE::QemuServer::qemu_block_set_io_throttle($vmid,"drive-$opt", $drive->{mbps}*1024*1024,
-                                                          $drive->{mbps_rd}*1024*1024, $drive->{mbps_wr}*1024*1024,
-                                                          $drive->{iops}, $drive->{iops_rd}, $drive->{iops_wr})
+               PVE::QemuServer::qemu_block_set_io_throttle($vmid,"drive-$opt", 
+                                                          ($drive->{mbps} || 0)*1024*1024,
+                                                          ($drive->{mbps_rd} || 0)*1024*1024, 
+                                                          ($drive->{mbps_wr} || 0)*1024*1024,
+                                                          $drive->{iops} || 0, 
+                                                          $drive->{iops_rd} || 0, 
+                                                          $drive->{iops_wr} || 0)
                   if !PVE::QemuServer::drive_is_cdrom($drive);
             }
        }
@@ -845,11 +851,11 @@ my $vmconfig_update_net = sub {
 # involve hot-plug actions, or disk alloc/free. Such actions can take long
 # time to complete and have side effects (not idempotent).
 #
-# The new implementation uses POST and forks a worker process. We added 
+# The new implementation uses POST and forks a worker process. We added
 # a new option 'background_delay'. If specified we wait up to
-# 'background_delay' second for the worker task to complete. It returns null 
+# 'background_delay' second for the worker task to complete. It returns null
 # if the task is finished within that time, else we return the UPID.
+
 my $update_vm_api  = sub {
     my ($param, $sync) = @_;
 
@@ -894,7 +900,7 @@ my $update_vm_api  = sub {
        raise_param_exc({ delete => "you can't use '-$opt' and " .
                              "-delete $opt' at the same time" })
            if defined($param->{$opt});
-       
+
        if (!PVE::QemuServer::option_exists($opt)) {
            raise_param_exc({ delete => "unknown option '$opt'" });
        }
@@ -929,11 +935,11 @@ my $update_vm_api  = sub {
            if $digest && $digest ne $conf->{digest};
 
        PVE::QemuServer::check_lock($conf) if !$skiplock;
-       
+
        if ($param->{memory} || defined($param->{balloon})) {
            my $maxmem = $param->{memory} || $conf->{memory} || $defaults->{memory};
            my $balloon = defined($param->{balloon}) ?  $param->{balloon} : $conf->{balloon};
-           
+
            die "balloon value too large (must be smaller than assigned memory)\n"
                if $balloon && $balloon > $maxmem;
        }
@@ -950,11 +956,11 @@ my $update_vm_api  = sub {
            }
 
            my $running = PVE::QemuServer::check_running($vmid);
-           
+
            foreach my $opt (keys %$param) { # add/change
-               
+
                $conf = PVE::QemuServer::load_config($vmid); # update/reload
-               
+
                next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # skip if nothing changed
 
                if (PVE::QemuServer::valid_drivename($opt)) {
@@ -997,7 +1003,7 @@ my $update_vm_api  = sub {
            if ($background_delay) {
 
                # Note: It would be better to do that in the Event based HTTPServer
-               # to avoid blocking call to sleep. 
+               # to avoid blocking call to sleep.
 
                my $end_time = time() + $background_delay;
 
@@ -1015,7 +1021,7 @@ my $update_vm_api  = sub {
                    return undef if $status eq 'OK';
                    die $status;
                }
-           } 
+           }
 
            return $upid;
        }
@@ -1267,6 +1273,8 @@ __PACKAGE__->register_method({
        my $vmid = $param->{vmid};
        my $node = $param->{node};
 
+       my $conf = PVE::QemuServer::load_config($vmid, $node); # check if VM exists
+
        my $authpath = "/vms/$vmid";
 
        my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
@@ -1277,14 +1285,14 @@ __PACKAGE__->register_method({
        my $port = PVE::Tools::next_vnc_port();
 
        my $remip;
+       my $remcmd = [];
 
        if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
            $remip = PVE::Cluster::remote_node_ip($node);
+           # NOTE: kvm VNC traffic is already TLS encrypted
+           $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
        }
 
-       # NOTE: kvm VNC traffic is already TLS encrypted
-       my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
-
        my $timeout = 10;
 
        my $realcmd = sub {
@@ -1292,12 +1300,24 @@ __PACKAGE__->register_method({
 
            syslog('info', "starting vnc proxy $upid\n");
 
-           my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
+           my $cmd;
+
+           if ($conf->{vga} =~ m/^serial\d+$/) {
+
+               my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga} ];
+               #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
+               $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
+                       '-timeout', $timeout, '-authpath', $authpath, 
+                       '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
+           } else {
+
+               my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
 
-           my $qmstr = join(' ', @$qmcmd);
+               my $qmstr = join(' ', @$qmcmd);
 
-           # also redirect stderr (else we get RFB protocol errors)
-           my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
+               # also redirect stderr (else we get RFB protocol errors)
+               $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
+           }
 
            PVE::Tools::run_command($cmd);
 
@@ -1317,6 +1337,83 @@ __PACKAGE__->register_method({
        };
     }});
 
+__PACKAGE__->register_method({
+    name => 'spiceproxy',
+    path => '{vmid}/spiceproxy',
+    method => 'GET',
+    protected => 1,
+    proxyto => 'node', # fixme: use direct connections or ssh tunnel?
+    permissions => {
+       check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
+    },
+    description => "Returns a SPICE configuration to connect to the VM.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+           proxy => {
+               description => "This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI).",
+               type => 'string', format => 'dns-name',
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       description => "Returned values can be directly passed to the 'remote-viewer' application.",
+       additionalProperties => 1,
+       properties => {
+           type => { type => 'string' },
+           password => { type => 'string' },
+           proxy => { type => 'string' },
+           host => { type => 'string' },
+           'tls-port' => { type => 'integer' },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $authuser = $rpcenv->get_user();
+
+       my $vmid = $param->{vmid};
+       my $node = $param->{node};
+       my $proxy = $param->{proxy};
+
+       my ($ticket, $proxyticket) = PVE::AccessControl::assemble_spice_ticket($authuser, $vmid, $node);
+
+       my $timeout = 10;
+
+       my $port = PVE::QemuServer::spice_port($vmid);
+       PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket);
+       PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
+
+       if (!$proxy) {
+           my $host = `hostname -f` || PVE::INotify::nodename();
+           chomp $host;
+           $proxy = $host;
+       }
+
+       my $filename = "/etc/pve/local/pve-ssl.pem";
+       my $subject = PVE::QemuServer::read_x509_subject_spice($filename);
+
+       my $cacert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192);
+       $cacert =~ s/\n/\\n/g;
+
+       return {
+           type => 'spice',
+           title => "VM $vmid",
+           host => $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
+           proxy => "http://$proxy:3128",
+           'tls-port' => $port,
+           'host-subject' => $subject,
+           ca => $cacert,
+           password => $ticket,
+           'delete-this-file' => 1,
+       };
+    }});
+
 __PACKAGE__->register_method({
     name => 'vmcmdidx',
     path => '{vmid}/status',
@@ -1397,6 +1494,8 @@ __PACKAGE__->register_method({
 
        $status->{ha} = &$vm_is_ha_managed($param->{vmid});
 
+       $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
+
        return $status;
     }});
 
@@ -1449,6 +1548,14 @@ __PACKAGE__->register_method({
        raise_param_exc({ migratedfrom => "Only root may use this option." })
            if $migratedfrom && $authuser ne 'root@pam';
 
+       # read spice ticket from STDIN
+       my $spice_ticket;
+       if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
+           my $line = <>;
+           chomp $line;
+           $spice_ticket = $line if $line;
+       }
+
        my $storecfg = PVE::Storage::config();
 
        if (&$vm_is_ha_managed($vmid) && !$stateuri &&
@@ -1477,7 +1584,8 @@ __PACKAGE__->register_method({
 
                syslog('info', "start VM $vmid: $upid\n");
 
-               PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
+               PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, 
+                                         $machine, $spice_ticket);
 
                return;
            };
@@ -1877,7 +1985,7 @@ __PACKAGE__->register_method({
        type => "object",
        properties => {
            hasFeature => { type => 'boolean' },
-           nodes => {  
+           nodes => {
                type => 'array',
                items => { type => 'string' },
            }
@@ -1907,11 +2015,11 @@ __PACKAGE__->register_method({
 
        my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
        my $hasFeature = PVE::QemuServer::has_feature($feature, $conf, $storecfg, $snapname, $running);
-       
+
        return {
            hasFeature => $hasFeature,
            nodes => [ keys %$nodelist ],
-       }; 
+       };
     }});
 
 __PACKAGE__->register_method({
@@ -2188,7 +2296,7 @@ __PACKAGE__->register_method({
     permissions => {
        description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
            "and 'Datastore.AllocateSpace' permissions on the storage.",
-       check => 
+       check =>
        [ 'and',
          ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
          ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
@@ -2271,7 +2379,7 @@ __PACKAGE__->register_method({
                $oldfmt = $1;
            }
 
-           die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid && 
+           die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
                 (!$format || !$oldfmt || $oldfmt eq $format);
 
            PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
@@ -2293,7 +2401,7 @@ __PACKAGE__->register_method({
                    $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
 
                    PVE::QemuServer::add_unused_volume($conf, $old_volid) if !$param->{delete};
-                  
+
                    PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
                };
                if (my $err = $@) {