]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
qmrestore: removed short timeout
[qemu-server.git] / PVE / API2 / Qemu.pm
index 39a18ed215d596b9fbfb0b712a2389211c0cad43..c06e5c7a7b33e9325431ee75770c7fd352744e58 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;
@@ -126,19 +127,19 @@ my $create_disks = sub {
            $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
        } else {
 
-           my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
-
-           my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+           $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
 
-           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);
@@ -371,13 +372,8 @@ __PACKAGE__->register_method({
                die "pipe requires cli environment\n"
                    if $rpcenv->{type} ne 'cli';
            } else {
-               my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
-
-               PVE::Storage::activate_volumes($storecfg, [ $archive ])
-                   if PVE::Storage::parse_volume_id ($archive, 1);
-
-               die "can't find archive file '$archive'\n" if !($path && -f $path);
-               $archive = $path;
+               $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive);
+               $archive = PVE::Storage::abs_filesystem_path($storecfg, $archive);
            }
        }
 
@@ -497,6 +493,8 @@ __PACKAGE__->register_method({
            { subdir => 'rrddata' },
            { subdir => 'monitor' },
            { subdir => 'snapshot' },
+           { subdir => 'spiceproxy' },
+           { subdir => 'sendkey' },
            ];
 
        return $res;
@@ -662,16 +660,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 {
@@ -695,22 +693,22 @@ my $vmconfig_delete_option = sub {
 
        my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
        if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
-           $rpcenv->check($authuser, "/storage/$sid", ['Datastore.Allocate']);
+           $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
        }
     }
 
     my $unplugwarning = "";
-    if($conf->{ostype} && $conf->{ostype} eq 'l26'){
+    if ($conf->{ostype} && $conf->{ostype} eq 'l26') {
        $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug modules loaded in your guest VM";
-    }elsif($conf->{ostype} && $conf->{ostype} eq 'l24'){
+    } elsif ($conf->{ostype} && $conf->{ostype} eq 'l24') {
        $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable hotplug in options";
-    }elsif(!$conf->{ostype} || ($conf->{ostype} && $conf->{ostype} eq 'other')){
+    } elsif (!$conf->{ostype} || ($conf->{ostype} && $conf->{ostype} eq 'other')) {
        $unplugwarning = "<br>verify that your guest support acpi hotplug";
     }
 
-    if($opt eq 'tablet'){
+    if ($opt eq 'tablet') {
        PVE::QemuServer::vm_deviceplug(undef, $conf, $vmid, $opt);
-    }else{
+    } else {
         die "error hot-unplug $opt $unplugwarning" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
     }
 
@@ -765,10 +763,26 @@ my $vmconfig_update_disk = sub {
                &$safe_num_ne($drive->{mbps_wr}, $old_drive->{mbps_wr}) ||
                &$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})
+               &$safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr}) ||
+               &$safe_num_ne($drive->{mbps_max}, $old_drive->{mbps_max}) ||
+               &$safe_num_ne($drive->{mbps_rd_max}, $old_drive->{mbps_rd_max}) ||
+               &$safe_num_ne($drive->{mbps_wr_max}, $old_drive->{mbps_wr_max}) ||
+               &$safe_num_ne($drive->{iops_max}, $old_drive->{iops_max}) ||
+               &$safe_num_ne($drive->{iops_rd_max}, $old_drive->{iops_rd_max}) ||
+               &$safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max})) {
+               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,
+                                                          ($drive->{mbps_max} || 0)*1024*1024,
+                                                          ($drive->{mbps_rd_max} || 0)*1024*1024,
+                                                          ($drive->{mbps_wr_max} || 0)*1024*1024,
+                                                          $drive->{iops_max} || 0,
+                                                          $drive->{iops_rd_max} || 0,
+                                                          $drive->{iops_wr_max} || 0)
                   if !PVE::QemuServer::drive_is_cdrom($drive);
             }
        }
@@ -845,11 +859,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 +908,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 +943,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 +964,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)) {
@@ -974,6 +988,10 @@ my $update_vm_api  = sub {
                    } elsif($opt eq 'tablet' && $param->{$opt} == 0){
                        PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
                    }
+               
+                   if($opt eq 'cores' && $conf->{maxcpus}){
+                       PVE::QemuServer::qemu_cpu_hotplug($vmid, $conf, $param->{$opt});
+                   }
 
                    $conf->{$opt} = $param->{$opt};
                    PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
@@ -997,7 +1015,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 +1033,7 @@ my $update_vm_api  = sub {
                    return undef if $status eq 'OK';
                    die $status;
                }
-           } 
+           }
 
            return $upid;
        }
@@ -1267,6 +1285,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 +1297,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 +1312,24 @@ __PACKAGE__->register_method({
 
            syslog('info', "starting vnc proxy $upid\n");
 
-           my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
+           my $cmd;
+
+           if ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/)) {
 
-           my $qmstr = join(' ', @$qmcmd);
+               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 {
 
-           # also redirect stderr (else we get RFB protocol errors)
-           my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
+               my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
+
+               my $qmstr = join(' ', @$qmcmd);
+
+               # 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 +1349,50 @@ __PACKAGE__->register_method({
        };
     }});
 
+__PACKAGE__->register_method({
+    name => 'spiceproxy',
+    path => '{vmid}/spiceproxy',
+    method => 'POST',
+    protected => 1,
+    proxyto => 'node',
+    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 => get_standard_option('spice-proxy', { optional => 1 }),
+       },
+    },
+    returns => get_standard_option('remote-viewer-config'),
+    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 $conf = PVE::QemuServer::load_config($vmid, $node);
+    my $title = "VM $vmid - $conf->{'name'}",
+
+       my $port = PVE::QemuServer::spice_port($vmid);
+
+       my ($ticket, undef, $remote_viewer_config) = 
+           PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port);
+       
+       PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket);
+       PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
+       
+       return $remote_viewer_config;
+    }});
+
 __PACKAGE__->register_method({
     name => 'vmcmdidx',
     path => '{vmid}/status',
@@ -1397,6 +1473,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 +1527,15 @@ __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')) {
+           if (defined(my $line = <>)) {
+               chomp $line;
+               $spice_ticket = $line;
+           }
+       }
+
        my $storecfg = PVE::Storage::config();
 
        if (&$vm_is_ha_managed($vmid) && !$stateuri &&
@@ -1477,7 +1564,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 +1965,7 @@ __PACKAGE__->register_method({
        type => "object",
        properties => {
            hasFeature => { type => 'boolean' },
-           nodes => {  
+           nodes => {
                type => 'array',
                items => { type => 'string' },
            }
@@ -1907,11 +1995,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({
@@ -2084,7 +2172,9 @@ __PACKAGE__->register_method({
                    my $net = PVE::QemuServer::parse_net($value);
                    $net->{macaddr} =  PVE::Tools::random_ether_addr();
                    $newconf->{$opt} = PVE::QemuServer::print_net($net);
-               } elsif (my $drive = PVE::QemuServer::parse_drive($opt, $value)) {
+               } elsif (PVE::QemuServer::valid_drivename($opt)) {
+                   my $drive = PVE::QemuServer::parse_drive($opt, $value);
+                   die "unable to parse drive options for '$opt'\n" if !$drive;
                    if (PVE::QemuServer::drive_is_cdrom($drive)) {
                        $newconf->{$opt} = $value; # simply copy configuration
                    } else {
@@ -2146,6 +2236,9 @@ __PACKAGE__->register_method({
                    PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
 
                     if ($target) {
+                       # always deactivate volumes - avoid lvm LVs to be active on several nodes
+                       PVE::Storage::deactivate_volumes($storecfg, $vollist);
+
                        my $newconffile = PVE::QemuServer::config_file($newid, $target);
                        die "Failed to move config to node '$target' - rename failed: $!\n"
                            if !rename($conffile, $newconffile);
@@ -2188,7 +2281,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 +2364,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,8 +2386,15 @@ __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);
+
+                   eval { 
+                       # try to deactivate volumes - avoid lvm LVs to be active on several nodes
+                       PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ]) 
+                           if !$running;
+                   };
+                   warn $@ if $@;
                };
                if (my $err = $@) {
 
@@ -2306,8 +2406,16 @@ __PACKAGE__->register_method({
                 }
 
                if ($param->{delete}) {
-                   eval { PVE::Storage::vdisk_free($storecfg, $old_volid); };
-                   warn $@ if $@;
+                    my $used_paths = PVE::QemuServer::get_used_paths($vmid, $storecfg, $conf, 1, 1);
+                    my $path = PVE::Storage::path($storecfg, $old_volid);
+                   if ($used_paths->{$path}){
+                       warn "volume $old_volid have snapshots. Can't delete it\n";
+                       PVE::QemuServer::add_unused_volume($conf, $old_volid);
+                       PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+                   } else {
+                       eval { PVE::Storage::vdisk_free($storecfg, $old_volid); };
+                       warn $@ if $@;
+                   }
                }
            };