]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
API2/Qemu: add unsecure and websocket options to vncpoxy also set qemu vnc server...
[qemu-server.git] / PVE / API2 / Qemu.pm
index 46228cf2dfcc10772c332292c9691b0d978665d0..ebacc95cecefd58ca0517eaf5f2680f06c5d53fa 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;
@@ -17,6 +18,7 @@ use PVE::RPCEnvironment;
 use PVE::AccessControl;
 use PVE::INotify;
 use PVE::Network;
+use PVE::API2::Firewall::VM;
 
 use Data::Dumper; # fixme: remove
 
@@ -126,7 +128,7 @@ my $create_disks = sub {
            $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
        } else {
 
-           my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
+           $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
 
            my $volid_is_new = 1;
 
@@ -371,13 +373,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);
            }
        }
 
@@ -498,11 +495,18 @@ __PACKAGE__->register_method({
            { subdir => 'monitor' },
            { subdir => 'snapshot' },
            { subdir => 'spiceproxy' },
+           { subdir => 'sendkey' },
+           { subdir => 'firewall' },
            ];
 
        return $res;
     }});
 
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Firewall::VM",  
+    path => '{vmid}/firewall',
+});
+
 __PACKAGE__->register_method({
     name => 'rrd',
     path => '{vmid}/rrd',
@@ -696,22 +700,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);
     }
 
@@ -766,14 +770,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", 
+               &$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_rd} || 0)*1024*1024,
                                                           ($drive->{mbps_wr} || 0)*1024*1024,
-                                                          $drive->{iops} || 0, 
-                                                          $drive->{iops_rd} || 0, 
-                                                          $drive->{iops_wr} || 0)
+                                                          $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);
             }
        }
@@ -822,9 +838,9 @@ my $vmconfig_update_net = sub {
                    PVE::Network::tap_rate_limit($iface, $newnet->{rate});
                }
 
-               if(($newnet->{bridge} ne $oldnet->{bridge}) || ($newnet->{tag} ne $oldnet->{tag})){
-                   eval{PVE::Network::tap_unplug($iface, $oldnet->{bridge}, $oldnet->{tag});};
-                   PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag});
+               if(($newnet->{bridge} ne $oldnet->{bridge}) || ($newnet->{tag} ne $oldnet->{tag}) || ($newnet->{firewall} ne $oldnet->{firewall})){
+                   PVE::Network::tap_unplug($iface);
+                   PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
                }
 
            }else{
@@ -979,6 +995,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);
@@ -1250,6 +1270,16 @@ __PACKAGE__->register_method({
        properties => {
            node => get_standard_option('pve-node'),
            vmid => get_standard_option('pve-vmid'),
+           unsecure => {
+               optional => 1,
+               type => 'boolean',
+               description => "disables x509 auth",
+           },
+           websocket => {
+               optional => 1,
+               type => 'boolean',
+               description => "starts websockify instead of vncproxy",
+           },
        },
     },
     returns => {
@@ -1271,6 +1301,10 @@ __PACKAGE__->register_method({
 
        my $vmid = $param->{vmid};
        my $node = $param->{node};
+       my $unsecure = $param->{unsecure} // 0;
+       my $websocket = $param->{websocket} // 0;
+
+       my $conf = PVE::QemuServer::load_config($vmid, $node); # check if VM exists
 
        my $authpath = "/vms/$vmid";
 
@@ -1282,14 +1316,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 or is known unsecure
+           $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 {
@@ -1297,12 +1331,64 @@ __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+$/)) {
+
+               die "Unsecure mode is not supported in vga serial mode!" if $unsecure;
 
-           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 {
+
+               my $vnc_socket = PVE::QemuServer::vnc_socket($vmid);
+
+               if (defined $remip) {
+                   my $perlcode = "";
+                   if ($unsecure) {
+                       $perlcode = qq|
+                               use PVE::QemuServer;
 
-           # also redirect stderr (else we get RFB protocol errors)
-           my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
+                               PVE::QemuServer::vm_mon_cmd($vmid, "change", device => "vnc", target => "unix:$vnc_socket,password");
+
+                               PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => "vnc", password => "$ticket");
+
+                               PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => "vnc", time => "+30");
+                               |;
+                   } else {
+                       $perlcode = qq|
+                               use PVE::QemuServer;
+
+                               PVE::QemuServer::vm_mon_cmd($vmid, "change", device => "vnc", target => "unix:$vnc_socket,x509,password");
+                               |;
+                   }
+
+                   PVE::Tools::run_command([@$remcmd, 'perl', '-'], input => $perlcode, outfunc => sub {print shift;}, errfunc => sub {print STDERR shift;});
+
+               } else {
+                   if ($unsecure) {
+                       PVE::QemuServer::vm_mon_cmd($vmid, "change", device => 'vnc', target => "unix:$vnc_socket,password");
+                       PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'vnc', password => $ticket);
+                       PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'vnc', time => "+30");
+                   } else {
+                       PVE::QemuServer::vm_mon_cmd($vmid, "change", device => 'vnc', target => "unix:$vnc_socket,x509,password");
+                   }
+               }
+
+               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"];
+
+               if ($websocket) {
+                   $cmd = ["/usr/share/novnc/utils/wsproxy.py", '--run-once', "--timeout=$timeout", "--idle-timeout=$timeout", '--ssl-only', '--cert', '/etc/pve/local/pve-ssl.pem', '--key', '/etc/pve/local/pve-ssl.key', $port, '--', @$cmd];
+               }
+           }
 
            PVE::Tools::run_command($cmd);
 
@@ -1325,9 +1411,9 @@ __PACKAGE__->register_method({
 __PACKAGE__->register_method({
     name => 'spiceproxy',
     path => '{vmid}/spiceproxy',
-    method => 'GET', # fixme: should be POST, but howto handle that in the HTML client
+    method => 'POST',
     protected => 1,
-    proxyto => 'node', # fixme: use direct connections or ssh tunnel?
+    proxyto => 'node',
     permissions => {
        check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
     },
@@ -1337,17 +1423,10 @@ __PACKAGE__->register_method({
        properties => {
            node => get_standard_option('pve-node'),
            vmid => get_standard_option('pve-vmid'),
+           proxy => get_standard_option('spice-proxy', { optional => 1 }),
        },
     },
-    returns => {
-       additionalProperties => 1,
-       properties => {
-           type => { type => 'string' },
-           password => { type => 'string' },
-           host => { type => 'string' },
-           port => { type => 'integer' },
-       },
-    },
+    returns => get_standard_option('remote-viewer-config'),
     code => sub {
        my ($param) = @_;
 
@@ -1357,89 +1436,20 @@ __PACKAGE__->register_method({
 
        my $vmid = $param->{vmid};
        my $node = $param->{node};
+       my $proxy = $param->{proxy};
 
-       my $port = PVE::Tools::next_vnc_port();
-
-        my $remip;
+    my $conf = PVE::QemuServer::load_config($vmid, $node);
+    my $title = "VM $vmid - $conf->{'name'}",
 
-       # Note: we currectly use "proxyto => 'node'", so this code will never trigger
-        if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
-            $remip = PVE::Cluster::remote_node_ip($node);
-        }
+       my $port = PVE::QemuServer::spice_port($vmid);
 
-       my $authpath = "/vms/$vmid";
-
-       my $ticket = PVE::AccessControl::assemble_spice_ticket($authuser, $authpath);
-
-       # limit ticket length to 59 charachters
-       $ticket = substr($ticket, 0, 59);
-
-       my $timeout = 10;
-
-       # Note: this only works if VM is on local node
+       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");
-
-       my $remcmd = []; #fixme
-
-       my $realcmd = sub {
-           my $upid = shift;
-
-           syslog('info', "starting spice proxy $upid\n");
        
-           my $socket = PVE::QemuServer::spice_socket($vmid);
-       
-           my $cmd = ['/usr/bin/socat', '-d', '-d', 
-                      "TCP-LISTEN:$port,reuseaddr,fork" ];
-
-           if ($remip) {
-               push @$cmd, "EXEC:'ssh root@$remip socat STDIO UNIX-CONNECT:$socket";
-           } else {
-               push @$cmd, "UNIX-CONNECT:$socket";
-           }
-
-           my $conn_count = 0;
-
-           my $parser = sub {
-               my $line = shift;
-               print "$line\n";
-               if ($line =~ /successfully connected from/) {
-                   $conn_count++;
-               } elsif ($line =~ /N exiting with status/ || $line =~ m/N exit\(/) {
-                   $conn_count--;
-                   die "client exit\n" if $conn_count <= 0;
-               }
-           };
-           
-           eval { 
-               # kill socat if we do not get any connection within $timeout seconds
-               local $SIG{ALRM} = sub { die "got timeout\n" if $conn_count <= 0; };
-               alarm($timeout);
-
-               PVE::Tools::run_command($cmd, errfunc => $parser, outfunc => sub{}); 
-           };
-           if (my $err = $@) {
-               die $err if $err !~ m/client exit$/;
-           }
-
-           return;
-       };
-
-       my $upid = $rpcenv->fork_worker('spiceproxy', $vmid, $authuser, $realcmd);
-
-       PVE::Tools::wait_for_vnc_port($port);
-
-       # fimxe: ??
-       my $host = `hostname -f` || PVE::INotify::nodename();
-       chomp $host;
-
-       return {
-           type => 'spice',
-           host => $host,
-           port => $port,
-           password => $ticket,
-           upid => $upid,
-       };
+       return $remote_viewer_config;
     }});
 
 __PACKAGE__->register_method({
@@ -1522,6 +1532,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;
     }});
 
@@ -1574,6 +1586,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 &&
@@ -1602,7 +1623,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;
            };
@@ -2209,7 +2231,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 {
@@ -2271,6 +2295,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);
@@ -2420,6 +2447,13 @@ __PACKAGE__->register_method({
                    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 = $@) {
 
@@ -2431,8 +2465,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 $@;
+                   }
                }
            };