X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPI2%2FQemu.pm;h=2f07ae57449565400afb9c4a34d9083459fba1e6;hb=3e7567e05e1df6b11c2d3c4dee86aec9c8f57a14;hp=5d9cbb92cfec0f1be4ba380dd1980ae79374dfeb;hpb=8bd0cbf5c3b52be6acd85b79d142113f1fd11636;p=qemu-server.git
diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index 5d9cbb92..2f07ae57 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -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 = "
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 = "
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 = "
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,11 @@ __PACKAGE__->register_method({
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
+ websocket => {
+ optional => 1,
+ type => 'boolean',
+ description => "starts websockify instead of vncproxy",
+ },
},
},
returns => {
@@ -1271,6 +1296,9 @@ __PACKAGE__->register_method({
my $vmid = $param->{vmid};
my $node = $param->{node};
+ my $websocket = $param->{websocket};
+
+ my $conf = PVE::QemuServer::load_config($vmid, $node); # check if VM exists
my $authpath = "/vms/$vmid";
@@ -1282,14 +1310,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 +1325,28 @@ __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 "Websocket mode is not supported in vga serial mode!" if $websocket;
+
+ 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 {
+
+ $ENV{LC_PVE_TICKET} = $ticket if $websocket; # set ticket with "qm vncproxy"
- my $qmstr = join(' ', @$qmcmd);
+ my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
- # also redirect stderr (else we get RFB protocol errors)
- my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
+ 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);
@@ -1323,29 +1367,31 @@ __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
- protected => 1,
- proxyto => 'node', # fixme: use direct connections or ssh tunnel?
+ name => 'vncwebsocket',
+ path => '{vmid}/vncwebsocket',
+ method => 'GET',
+ proxyto => 'node',
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
},
- description => "Returns a SPICE configuration to connect to the VM.",
+ description => "Opens a weksocket for VNV traffic.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
+ port => {
+ description => "Port number returned by previous vncproxy call.",
+ type => 'integer',
+ minimum => 5900,
+ maximum => 5999,
+ },
},
},
returns => {
- additionalProperties => 1,
+ type => "object",
properties => {
- type => { type => 'string' },
- password => { type => 'string' },
- host => { type => 'string' },
- port => { type => 'integer' },
+ port => { type => 'string' },
},
},
code => sub {
@@ -1358,82 +1404,59 @@ __PACKAGE__->register_method({
my $vmid = $param->{vmid};
my $node = $param->{node};
- my $port = PVE::Tools::next_vnc_port();
-
- my $remip;
+ my $conf = PVE::QemuServer::load_config($vmid, $node); # VM exists ?
- # 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);
- }
+ # Note: VNC ports are acessible from outside, so we do not gain any
+ # security if we verify that $param->{port} belongs to VM $vmid. This
+ # check is done by verifying the VNC ticket (inside VNC protocol).
- my $authpath = "/vms/$vmid";
+ my $port = $param->{port};
+
+ return { port => $port };
+ }});
- my $ticket = PVE::AccessControl::assemble_spice_ticket($authuser, $authpath);
+__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) = @_;
- # limit ticket length to 59 charachters
- $ticket = substr($ticket, 0, 59);
+ my $rpcenv = PVE::RPCEnvironment::get();
- my $timeout = 10;
+ my $authuser = $rpcenv->get_user();
- # Note: this only works if VM is on local node
- 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 $vmid = $param->{vmid};
+ my $node = $param->{node};
+ my $proxy = $param->{proxy};
- my $remcmd = []; #fixme
+ my $conf = PVE::QemuServer::load_config($vmid, $node);
+ my $title = "VM $vmid - $conf->{'name'}",
- my $realcmd = sub {
- my $upid = shift;
+ my $port = PVE::QemuServer::spice_port($vmid);
- syslog('info', "starting spice proxy $upid\n");
+ my ($ticket, undef, $remote_viewer_config) =
+ PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port);
- my $socket = PVE::QemuServer::spice_socket($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");
- 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 { 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({
@@ -1516,6 +1539,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;
}});
@@ -1568,6 +1593,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 &&
@@ -1596,7 +1630,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;
};
@@ -2203,7 +2238,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 {
@@ -2265,6 +2302,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);
@@ -2414,6 +2454,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 = $@) {
@@ -2425,8 +2472,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 $@;
+ }
}
};