[pve-devel] [PATCH v2 qemu-server 2/2] add migration hooks to VM migration process

Stefan Hanreich s.hanreich at proxmox.com
Thu Oct 6 14:44:47 CEST 2022


This calls the newly added commands in the previous commit to run the
migrate-hooks during the migration process. When a tunnel already
exists, the tunnel gets reused otherwise it creates a new ad-hoc tunnel
that is used for running the migration-hook.

Additionally I added some mock methods to the QemuMigrateMock class, so
the test class supports the newly added commands as well.

Signed-off-by: Stefan Hanreich <s.hanreich at proxmox.com>
---
 PVE/QemuMigrate.pm                    | 108 ++++++++++++++++++++++++++
 test/MigrationTest/QemuMigrateMock.pm |  11 ++-
 2 files changed, 118 insertions(+), 1 deletion(-)

diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm
index d52dc8d..42cf7d0 100644
--- a/PVE/QemuMigrate.pm
+++ b/PVE/QemuMigrate.pm
@@ -203,6 +203,8 @@ sub prepare {
     eval { $self->cmd_quiet($cmd); };
     die "Can't connect to destination address using public key\n" if $@;
 
+    $self->migration_hook($vmid, 'pre');
+
     return $running;
 }
 
@@ -1216,6 +1218,10 @@ sub phase3_cleanup {
 	}
     }
 
+    if (!$self->{errors}) {
+	$self->migration_hook($vmid, 'post');
+    }
+
     # close tunnel on successful migration, on error phase2_cleanup closed it
     if ($tunnel) {
 	eval { PVE::Tunnel::finish_tunnel($tunnel); };
@@ -1284,4 +1290,106 @@ sub round_powerof2 {
     return 2 << int(log($_[0]-1)/log(2));
 }
 
+sub migration_hook {
+    my ($self, $vmid, $phase) = @_;
+
+    if (!$self->{vmconf}->{hookscript}) {
+	return;
+    }
+
+    my $stop_on_error = $phase eq 'pre';
+
+    PVE::GuestHelpers::exec_hookscript(
+	$self->{vmconf},
+	$vmid,
+	"$phase-migrate",
+	$stop_on_error,
+    );
+
+    my $tunnel;
+
+    eval {
+	$tunnel = $self->{tunnel} // $self->fork_tunnel();
+    };
+    die $@ if $@;
+
+    my $close_tunnel = sub {
+	if (!$self->{tunnel}) {
+	    $self->log('info', "closing tunnel for migration hook");
+	    PVE::Tunnel::finish_tunnel($tunnel);
+	}
+    };
+
+    my $source = PVE::INotify::nodename();
+    my $target = $self->{node};
+
+    eval {
+	$self->log('info', "running hook $phase-migrate on target");
+	PVE::Tunnel::write_tunnel($tunnel, 30, "migrate-hook $vmid $phase $source $target");
+    };
+    my $err = $@;
+
+    if ($err =~ /no reply to command/) {
+	eval {
+	    $close_tunnel->();
+	};
+	if ($@) {
+	    die $err;
+	} else {
+	    $self->log('warn', 'Got timeout when trying to run migrate-hook. Target doesn\'t support migrate hooks (old version?). Still continuing with migration.');
+	    return;
+	}
+    } elsif ($err) {
+	$close_tunnel->();
+	die $err;
+    }
+
+    $self->log('info', "successfully started hook $phase-migrate on target");
+
+    my $running = 1;
+
+    while ($running) {
+	eval {
+	    PVE::Tunnel::write_tunnel($tunnel, 30, "query-migrate-hook");
+	    my $status = PVE::Tunnel::read_tunnel($tunnel, 30);
+
+	    if ($status eq 'running') {
+		sleep(5);
+	    } elsif ($status eq 'finished') {
+		my $output = MIME::Base64::decode(
+		    PVE::Tunnel::read_tunnel($tunnel, 30)
+		);
+
+		$self->log('info', "$phase-migrate hook ran successfully on target:\n" . $output);
+	    } elsif ($status eq 'error') {
+		my $output = MIME::Base64::decode(
+		    PVE::Tunnel::read_tunnel($tunnel, 30)
+		);
+
+		my $msg = "An error occured during running the hookscript:\n" . $output;
+
+		if ($stop_on_error) {
+		    die $msg;
+		} else {
+		    $self->log('warn', $msg)
+		}
+	    } else {
+		die "Invalid response!"
+	    }
+
+	    $running = $status eq 'running';
+	};
+	if ($@) {
+	    $err = $@;
+	    last;
+	}
+    }
+
+    eval {
+	$close_tunnel->();
+    };
+    die $err if $err; # use the initial error if it exists
+    die $@ if $@;
+}
+
 1;
diff --git a/test/MigrationTest/QemuMigrateMock.pm b/test/MigrationTest/QemuMigrateMock.pm
index f2c0281..e33a284 100644
--- a/test/MigrationTest/QemuMigrateMock.pm
+++ b/test/MigrationTest/QemuMigrateMock.pm
@@ -64,6 +64,8 @@ $tunnel_module->mock(
 	    my $vmid = $1;
 	    die "resuming wrong VM '$vmid'\n" if $vmid ne $test_vmid;
 	    return;
+	} elsif ($command =~ /^migrate-hook.*/) {
+	    return;
 	}
 	die "write_tunnel (mocked) - implement me: $command\n";
     },
@@ -72,7 +74,12 @@ $tunnel_module->mock(
 my $qemu_migrate_module = Test::MockModule->new("PVE::QemuMigrate");
 $qemu_migrate_module->mock(
     fork_tunnel => sub {
-	die "fork_tunnel (mocked) - implement me\n"; # currently no call should lead here
+	return {
+	    writer => "mocked",
+	    reader => "mocked",
+	    pid => 123456,
+	    version => 1,
+	};
     },
     read_tunnel => sub {
 	die "read_tunnel (mocked) - implement me\n"; # currently no call should lead here
@@ -298,6 +305,8 @@ $MigrationTest::Shared::tools_module->mock(
 			return 0;
 		    } elsif ($cmd eq 'stop') {
 			return 0;
+		    } elsif ($cmd eq 'mtunnel') {
+			return 0;
 		    }
 		    die "run_command (mocked) ssh qm command - implement me: ${cmd_msg}";
 		} elsif ($cmd eq 'pvesm') {
-- 
2.30.2





More information about the pve-devel mailing list