X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPI2%2FQemu.pm;h=b5b183f40a97b464ce63f561f6a4b13d69899423;hb=c068c1c3a918d2a112646254cf19ac4c1b133f81;hp=e48bf6ddd6bff50d314f6fba498c8b168c424df7;hpb=3b4cf0f0fc6bfff27b6a430a8d3aa3c837a0324d;p=qemu-server.git diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index e48bf6d..b5b183f 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -5,6 +5,8 @@ use warnings; use Cwd 'abs_path'; use Net::SSLeay; use UUID; +use POSIX; +use IO::Socket::IP; use PVE::Cluster qw (cfs_read_file cfs_write_file);; use PVE::SafeSyslog; @@ -13,6 +15,8 @@ use PVE::Exception qw(raise raise_param_exc raise_perm_exc); use PVE::Storage; use PVE::JSONSchema qw(get_standard_option); use PVE::RESTHandler; +use PVE::ReplicationConfig; +use PVE::GuestHelpers; use PVE::QemuConfig; use PVE::QemuServer; use PVE::QemuMigrate; @@ -22,8 +26,15 @@ use PVE::INotify; use PVE::Network; use PVE::Firewall; use PVE::API2::Firewall::VM; -use PVE::HA::Env::PVE2; -use PVE::HA::Config; + +BEGIN { + if (!$ENV{PVE_GENERATING_DOCS}) { + require PVE::HA::Env::PVE2; + import PVE::HA::Env::PVE2; + require PVE::HA::Config; + import PVE::HA::Config; + } +} use Data::Dumper; # fixme: remove @@ -60,7 +71,7 @@ my $check_storage_access = sub { die "no storage ID specified (and no default storage)\n" if !$storeid; $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']); } else { - $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid); + PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid); } }); }; @@ -110,7 +121,8 @@ my $create_disks = sub { my $vollist = []; my $res = {}; - PVE::QemuServer::foreach_drive($settings, sub { + + my $code = sub { my ($ds, $disk) = @_; my $volid = $disk->{file}; @@ -118,7 +130,7 @@ my $create_disks = sub { if (!$volid || $volid eq 'none' || $volid eq 'cdrom') { delete $disk->{size}; $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); - } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) { + } elsif ($volid =~ m!^(([^/:\s]+):)?(\d+(\.\d+)?)$!) { my ($storeid, $size) = ($2 || $default_storage, $3); die "no storage ID specified (and no default storage)\n" if !$storeid; my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid); @@ -157,7 +169,7 @@ my $create_disks = sub { $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); } else { - $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid); + PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid); my $volid_is_new = 1; @@ -181,7 +193,9 @@ my $create_disks = sub { $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); } - }); + }; + + eval { PVE::QemuServer::foreach_drive($settings, $code); }; # free allocated images on error if (my $err = $@) { @@ -474,7 +488,7 @@ __PACKAGE__->register_method({ die "pipe requires cli environment\n" if $rpcenv->{type} ne 'cli'; } else { - $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive); + PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $archive); $archive = PVE::Storage::abs_filesystem_path($storecfg, $archive); } } @@ -998,6 +1012,12 @@ my $update_vm_api = sub { foreach my $opt (@delete) { $modified->{$opt} = 1; $conf = PVE::QemuConfig->load_config($vmid); # update/reload + if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) { + warn "cannot delete '$opt' - not set in current configuration!\n"; + $modified->{$opt} = 0; + next; + } + if ($opt =~ m/^unused/) { my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt}); PVE::QemuConfig->check_protection($conf, "can't remove unused disk '$drive->{file}'"); @@ -1261,6 +1281,10 @@ __PACKAGE__->register_method({ die "unable to remove VM $vmid - used in HA resources\n" if PVE::HA::Config::vm_is_ha_managed($vmid); + # do not allow destroy if there are replication jobs + my $repl_conf = PVE::ReplicationConfig->new(); + $repl_conf->check_for_existing_jobs($vmid); + # early tests (repeat after locking) die "VM $vmid is running - destroy failed\n" if PVE::QemuServer::check_running($vmid); @@ -1401,24 +1425,41 @@ __PACKAGE__->register_method({ $cmd = ['/usr/bin/vncterm', '-rfbport', $port, '-timeout', $timeout, '-authpath', $authpath, '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd]; + PVE::Tools::run_command($cmd); } else { $ENV{LC_PVE_TICKET} = $ticket if $websocket; # set ticket with "qm vncproxy" - my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid]; - - my $qmstr = join(' ', @$qmcmd); - - # also redirect stderr (else we get RFB protocol errors) - $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"]; + $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid]; + + my $sock = IO::Socket::IP->new( + ReuseAddr => 1, + Listen => 1, + LocalPort => $port, + Proto => 'tcp', + GetAddrInfoFlags => 0, + ) or die "failed to create socket: $!\n"; + # Inside the worker we shouldn't have any previous alarms + # running anyway...: + alarm(0); + local $SIG{ALRM} = sub { die "connection timed out\n" }; + alarm $timeout; + accept(my $cli, $sock) or die "connection failed: $!\n"; + alarm(0); + close($sock); + if (PVE::Tools::run_command($cmd, + output => '>&'.fileno($cli), + input => '<&'.fileno($cli), + noerr => 1) != 0) + { + die "Failed to run vncproxy.\n"; + } } - PVE::Tools::run_command($cmd); - return; }; - my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd); + my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1); PVE::Tools::wait_for_vnc_port($port); @@ -1643,7 +1684,7 @@ __PACKAGE__->register_method({ }, machine => get_standard_option('pve-qm-machine'), targetstorage => { - description => "Target migration storage . (1 = same storeid than original)", + description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)", type => 'string', optional => 1 } @@ -2407,12 +2448,12 @@ __PACKAGE__->register_method({ $newconf->{$opt} = $value; # simply copy configuration } else { if ($param->{full}) { - die "Full clone feature is not available" + die "Full clone feature is not supported for drive '$opt'\n" if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running); $fullclone->{$opt} = 1; } else { # not full means clone instead of copy - die "Linked clone feature is not available" + die "Linked clone feature is not supported for drive '$opt'\n" if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running); } $drives->{$opt} = $drive; @@ -2650,6 +2691,10 @@ __PACKAGE__->register_method({ PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete}; + # convert moved disk to base if part of template + PVE::QemuServer::template_create($vmid, $conf, $disk) + if PVE::QemuConfig->is_template($conf); + PVE::QemuConfig->write_config($vmid, $conf); eval { @@ -2723,10 +2768,16 @@ __PACKAGE__->register_method({ description => "CIDR of the (sub) network that is used for migration.", optional => 1, }, - targetstorage => get_standard_option('pve-storage-id', { - description => "Target storage.", + "with-local-disks" => { + type => 'boolean', + description => "Enable live storage migration for local disk", optional => 1, - }), + }, + targetstorage => get_standard_option('pve-storage-id', { + description => "Default target storage.", + optional => 1, + completion => \&PVE::QemuServer::complete_storage, + }), }, }, returns => { @@ -2753,7 +2804,7 @@ __PACKAGE__->register_method({ my $vmid = extract_param($param, 'vmid'); - raise_param_exc({ targetstorage => "Live Storage migration can only be done online" }) + raise_param_exc({ targetstorage => "Live storage migration can only be done online." }) if !$param->{online} && $param->{targetstorage}; raise_param_exc({ force => "Only root may use this option." }) @@ -2779,7 +2830,12 @@ __PACKAGE__->register_method({ } my $storecfg = PVE::Storage::config(); - PVE::QemuServer::check_storage_availability($storecfg, $conf, $target); + + if( $param->{targetstorage}) { + PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target); + } else { + PVE::QemuServer::check_storage_availability($storecfg, $conf, $target); + } if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { @@ -2801,13 +2857,17 @@ __PACKAGE__->register_method({ } else { - my $realcmd = sub { - my $upid = shift; + my $code = sub { + my $realcmd = sub { + my $upid = shift; + + PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param); + }; - PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param); + return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd); }; - return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd); + return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $code); } }}); @@ -2951,7 +3011,7 @@ __PACKAGE__->register_method({ size => { type => 'string', pattern => '\+?\d+(\.\d+)?[KMGT]?', - description => "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.", + description => "The new size. With the `+` sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.", }, digest => { type => 'string', @@ -3032,7 +3092,7 @@ __PACKAGE__->register_method({ $newsize += $size if $ext; $newsize = int($newsize); - die "unable to skrink disk size\n" if $newsize < $size; + die "shrinking disks is not supported\n" if $newsize < $size; return if $size == $newsize; @@ -3334,7 +3394,12 @@ __PACKAGE__->register_method({ PVE::QemuConfig->snapshot_rollback($vmid, $snapname); }; - return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd); + my $worker = sub { + # hold migration lock, this makes sure that nobody create replication snapshots + return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd); + }; + + return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker); }}); __PACKAGE__->register_method({