]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
migrate: aquire guest_migration_lock during migration
[qemu-server.git] / PVE / API2 / Qemu.pm
index 0bae42451341f7530e3845f967f6c85370c70dca..eb61cb8e231f08e98415a43cb71252d7e0b6a0ee 100644 (file)
@@ -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})) {
+                   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;
@@ -2464,15 +2505,14 @@ __PACKAGE__->register_method({
 
                    my $total_jobs = scalar(keys %{$drives});
                    my $i = 1;
-                   my $skipcomplete = 1;
 
                    foreach my $opt (keys %$drives) {
-
                        my $drive = $drives->{$opt};
-                       $skipcomplete = undef if $total_jobs == $i; #finish after last drive
+                       my $skipcomplete = ($total_jobs != $i); # finish after last drive
 
                        my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
-                                                                  $newid, $storage, $format, $fullclone->{$opt}, $newvollist, $jobs, $skipcomplete, $oldconf->{agent});
+                                                                  $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
+                                                                  $jobs, $skipcomplete, $oldconf->{agent});
 
                        $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
 
@@ -2651,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 {
@@ -2724,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 => {
@@ -2754,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." })
@@ -2780,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') {
 
@@ -2802,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);
        }
 
     }});
@@ -2952,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',
@@ -3033,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;