]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuMigrate.pm
migrate: tolerate query-migrate errors
[qemu-server.git] / PVE / QemuMigrate.pm
index 85566c3b1ac3a10a8553c2bfe40178adf47db063..0ad0eab4a26a5067712865831ccc84ce578858fe 100644 (file)
@@ -9,6 +9,7 @@ use PVE::INotify;
 use PVE::Cluster;
 use PVE::Storage;
 use PVE::QemuServer;
+use Time::HiRes qw( usleep );
 
 use base qw(PVE::AbstractMigrate);
 
@@ -117,7 +118,7 @@ sub finish_tunnel {
 
 sub lock_vm {
     my ($self, $vmid, $code, @param) = @_;
-    
+
     return PVE::QemuServer::lock_config($vmid, $code, @param);
 }
 
@@ -177,19 +178,23 @@ sub sync_disks {
        my $volhash = {};
        my $cdromhash = {};
 
-       # get list from PVE::Storage (for unused volumes)
-       my $dl = PVE::Storage::vdisk_list($self->{storecfg}, undef, $vmid);
-       PVE::Storage::foreach_volid($dl, sub {
-           my ($volid, $sid, $volname) = @_;
+       my @sids = PVE::Storage::storage_ids($self->{storecfg});
+        foreach my $storeid (@sids) {
+           my $scfg = PVE::Storage::storage_config($self->{storecfg}, $storeid);
+            next if $scfg->{shared};
+           next if !PVE::Storage::storage_check_enabled($self->{storecfg}, $storeid, undef, 1);
 
-           # check if storage is available on both nodes
-           my $scfg = PVE::Storage::storage_check_node($self->{storecfg}, $sid);
-           PVE::Storage::storage_check_node($self->{storecfg}, $sid, $self->{node});
+            # get list from PVE::Storage (for unused volumes)
+            my $dl = PVE::Storage::vdisk_list($self->{storecfg}, $storeid, $vmid);
+            PVE::Storage::foreach_volid($dl, sub {
+                my ($volid, $sid, $volname) = @_;
 
-           return if $scfg->{shared};
+                # check if storage is available on target node
+                PVE::Storage::storage_check_node($self->{storecfg}, $sid, $self->{node});
 
-           $volhash->{$volid} = 1;
-       });
+                $volhash->{$volid} = 1;
+            });
+        }
 
        # and add used,owned/non-shared disks (just to be sure we have all)
 
@@ -263,12 +268,6 @@ sub phase1 {
 
     sync_disks($self, $vmid);
 
-    # move config to remote node
-    my $conffile = PVE::QemuServer::config_file($vmid);
-    my $newconffile = PVE::QemuServer::config_file($vmid, $self->{node});
-
-    die "Failed to move config to node '$self->{node}' - rename failed: $!\n"
-       if !rename($conffile, $newconffile);
 };
 
 sub phase1_cleanup {
@@ -282,7 +281,7 @@ sub phase1_cleanup {
     if (my $err = $@) {
        $self->log('err', $err);
     }
-  
+
     if ($self->{volumes}) {
        foreach my $volid (@{$self->{volumes}}) {
            $self->log('err', "found stale volume copy '$volid' on node '$self->{node}'");
@@ -300,9 +299,11 @@ sub phase2 {
 
     my $rport;
 
+    my $nodename = PVE::INotify::nodename();
+
     ## start on remote node
-    my $cmd = [@{$self->{rem_ssh}}, 'qm', 'start', 
-              $vmid, '--stateuri', 'tcp', '--skiplock'];
+    my $cmd = [@{$self->{rem_ssh}}, 'qm', 'start',
+               $vmid, '--stateuri', 'tcp', '--skiplock', '--migratedfrom', $nodename];
 
     PVE::Tools::run_command($cmd, outfunc => sub {
        my $line = shift;
@@ -324,33 +325,51 @@ sub phase2 {
     # start migration
 
     my $start = time();
+
+    my $capabilities = {};
+    $capabilities->{capability} =  "xbzrle";
+    $capabilities->{state} = JSON::true;
+
+    eval {
+       PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]);
+    };
+
+    #set cachesize 10% of the total memory
+    my $cachesize = int($conf->{memory}*1048576/10);
+    eval {
+       PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "migrate-set-cache-size", value => $cachesize);
+    };
+
     eval {
         PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "migrate", uri => "tcp:localhost:$lport");
     };
     my $merr = $@;
 
-    my $lstat = undef;
+    my $lstat = 0;
+    my $usleep = 2000000;
+    my $i = 0;
+    my $err_count = 0;
     while (1) {
-       sleep (2);
-       my $stat = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "query-migrate");
-       
+       $i++;
+       my $avglstat = $lstat/$i if $lstat;
+
+       usleep($usleep);
+       my $stat;
+       eval {
+           $stat = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "query-migrate");
+       };
+       if (my $err = $@) {
+           $err_count++;
+           warn "query migrate failed: $err\n";
+           if ($err_count <= 5) {
+               usleep(1000000);
+               next;
+           }
+           die "too many query migrate failures - aborting\n";
+       }
        if ($stat->{status} =~ m/^(active|completed|failed|cancelled)$/im) {
            $merr = undef;
-
-           if ($stat->{ram}->{transferred} ne $lstat) {
-               if ($stat->{status} eq 'active') {
-                   my ($trans, $rem, $total) = (0, 0, 0);
-                   $trans = sprintf "%.2f", $stat->{ram}->{transferred}/1024 if $stat->{ram}->{transferred};
-                   $rem = sprintf "%.2f", $stat->{ram}->{remaining}/1024 if $stat->{ram}->{remaining};
-                   $total = sprintf "%.2f", $stat->{ram}->{total}/1024 if $stat->{ram}->{total};
-
-                   $self->log('info', "migration status: $stat->{status} (transferred ${trans}KB, " .
-                              "remaining ${rem}KB), total ${total}KB)");
-               } else {
-                   $self->log('info', "migration status: $stat->{status}");
-               }
-           }
-
+           $err_count = 0;
            if ($stat->{status} eq 'completed') {
                my $delay = time() - $start;
                if ($delay > 0) {
@@ -358,24 +377,74 @@ sub phase2 {
                    $self->log('info', "migration speed: $mbps MB/s");
                }
            }
-           
+
            if ($stat->{status} eq 'failed' || $stat->{status} eq 'cancelled') {
                die "aborting\n"
            }
 
-           last if $stat->{status} ne 'active';
+           if ($stat->{status} ne 'active') {
+               $self->log('info', "migration status: $stat->{status}");
+               last;
+           }
+
+           if ($stat->{ram}->{transferred} ne $lstat) {
+               my $trans = $stat->{ram}->{transferred} || 0;
+               my $rem = $stat->{ram}->{remaining} || 0;
+               my $total = $stat->{ram}->{total} || 0;
+               my $xbzrlecachesize = $stat->{"xbzrle-cache"}->{"cache-size"} || 0;
+               my $xbzrlebytes = $stat->{"xbzrle-cache"}->{"bytes"} || 0;
+               my $xbzrlepages = $stat->{"xbzrle-cache"}->{"pages"} || 0;
+               my $xbzrlecachemiss = $stat->{"xbzrle-cache"}->{"cache-miss"} || 0;
+               my $xbzrleoverflow = $stat->{"xbzrle-cache"}->{"overflow"} || 0;
+               #reduce sleep if remainig memory if lower than the everage transfert 
+               $usleep = 300000 if $rem < $avglstat;
+
+               $self->log('info', "migration status: $stat->{status} (transferred ${trans}, " .
+                          "remaining ${rem}), total ${total})");
+
+               $self->log('info', "migration xbzrle cachesize: ${xbzrlecachesize} transferred ${xbzrlebytes} pages ${xbzrlepages} cachemiss ${xbzrlecachemiss} overflow ${xbzrleoverflow}");
+           }
+
+           $lstat = $stat->{ram}->{transferred};
+           
        } else {
            die $merr if $merr;
            die "unable to parse migration status '$stat->{status}' - aborting\n";
        }
-       $lstat = $lstat->{ram}->{transferred};
-    };
+    }
+}
+
+sub phase2_cleanup {
+    my ($self, $vmid, $err) = @_;
+
+    return if !$self->{errors};
+    $self->{phase2errors} = 1;
+
+    $self->log('info', "aborting phase 2 - cleanup resources");
+
+    my $conf = $self->{vmconf};
+    delete $conf->{lock};
+    eval { PVE::QemuServer::update_config_nolock($vmid, $conf, 1) };
+    if (my $err = $@) {
+        $self->log('err', $err);
+    }
+
+    # cleanup ressources on target host
+    my $nodename = PVE::INotify::nodename();
+    my $cmd = [@{$self->{rem_ssh}}, 'qm', 'stop', $vmid, '--skiplock', '--migratedfrom', $nodename];
+    eval{ PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {}) };
+    if (my $err = $@) {
+        $self->log('err', $err);
+        $self->{errors} = 1;
+    }
 }
 
 sub phase3 {
     my ($self, $vmid) = @_;
-    
+
     my $volids = $self->{volumes};
+    return if $self->{phase2errors};
 
     # destroy local copies
     foreach my $volid (@$volids) {
@@ -392,6 +461,24 @@ sub phase3_cleanup {
     my ($self, $vmid, $err) = @_;
 
     my $conf = $self->{vmconf};
+    return if $self->{phase2errors};
+
+    # move config to remote node
+    my $conffile = PVE::QemuServer::config_file($vmid);
+    my $newconffile = PVE::QemuServer::config_file($vmid, $self->{node});
+
+    die "Failed to move config to node '$self->{node}' - rename failed: $!\n"
+        if !rename($conffile, $newconffile);
+
+    # now that config file is move, we can resume vm on target if livemigrate
+    if ($self->{tunnel}) {
+       my $cmd = [@{$self->{rem_ssh}}, 'qm', 'resume', $vmid, '--skiplock'];
+       eval{ PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {}) };
+       if (my $err = $@) {
+           $self->log('err', $err);
+           $self->{errors} = 1;
+       }
+    }
 
     # always stop local VM
     eval { PVE::QemuServer::vm_stop($self->{storecfg}, $vmid, 1, 1); };