[pve-devel] [PATCH v2 qemu-server 1/2] add migrate-hook and query-migrate-hook commands to CLI

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


migrate-hook runs the migrate hook by forking a new process and
attaching to its STDOUT. This helps us deal with long running
hookscripts, that could otherwise block. Additionally it safeguards us
from hookscripts doing weird stuff with output.

query-migrate-hook can be used to query the current state of the running
hookscript. After the migrate-hook has finished it also transfers the
output back to the source node. When an error occurs, the
query-migrate-hook command informs the caller and returns the respective
output as well.

Signed-off-by: Stefan Hanreich <s.hanreich at proxmox.com>
---
 PVE/CLI/qm.pm | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 109 insertions(+)

diff --git a/PVE/CLI/qm.pm b/PVE/CLI/qm.pm
index ca5d25f..5ac091b 100755
--- a/PVE/CLI/qm.pm
+++ b/PVE/CLI/qm.pm
@@ -306,6 +306,8 @@ __PACKAGE__->register_method ({
 	$tunnel_write->("tunnel online");
 	$tunnel_write->("ver 1");
 
+	my $state = {};
+
 	while (my $line = <STDIN>) {
 	    chomp $line;
 	    if ($line =~ /^quit$/) {
@@ -323,6 +325,113 @@ __PACKAGE__->register_method ({
 		} else {
 		    $tunnel_write->("ERR: resume failed - VM $vmid not running");
 		}
+	    } elsif ($line =~ /^migrate-hook (\d+) (pre|post) ([\S]+) ([\S]+)$/) {
+		if ($state->{migrate_hook}) {
+		    $tunnel_write->("ERR: migrate-hook is already running!");
+		    next;
+		}
+
+		my $vmid = $1;
+		my $phase = $2;
+		my $source = $3;
+		my $target = $4;
+
+		if (!PVE::JSONSchema::pve_verify_node_name($source, 1)) {
+		    $tunnel_write->("ERR: Invalid name for source node");
+		    next;
+		}
+
+		if (!PVE::JSONSchema::pve_verify_node_name($target, 1)) {
+		    $tunnel_write->("ERR: Invalid name for target node");
+		    next;
+		}
+
+		my $config_node = ($phase eq 'pre')
+		    ? $source
+		    : $target;
+
+		eval {
+		    my $conf = PVE::QemuConfig->load_config($vmid, $config_node);
+
+		    pipe(my $reader, my $writer);
+
+		    my $pid = fork();
+		    die "Could not fork new process!" if !defined $pid;
+
+		    if ($pid == 0) {
+			# child
+			close $reader;
+
+			$ENV{PVE_MIGRATED_FROM} = $source;
+
+			eval {
+			    PVE::GuestHelpers::exec_hookscript(
+				$conf,
+				$vmid,
+				"$phase-migrate",
+				1,
+				{
+				    output  => ">&" . fileno($writer),
+				    errfunc => sub {
+					my $error_line = shift;
+					print $writer "STDERR: " . $error_line;
+				    },
+				}
+			    );
+			};
+			my $err = $@;
+
+			close $writer;
+
+			if ($err) {
+			    POSIX::_exit(1);
+			}
+
+			POSIX::_exit(0);
+		    }
+
+		    close $writer;
+
+		    $state->{migrate_hook} = {
+			output => $reader,
+			pid    => $pid,
+		    };
+		};
+		if ($@) {
+		    chomp $@;
+		    $tunnel_write->("ERR: $phase-migrate hook failed - $@");
+		} else {
+		    $tunnel_write->("OK");
+		}
+	    } elsif ($line =~ /^query-migrate-hook$/) {
+		if (!$state->{migrate_hook}) {
+		    $tunnel_write->("ERR: No migration hook running!");
+		    next;
+		}
+
+		if (!waitpid($state->{migrate_hook}->{pid}, POSIX::WNOHANG)) {
+		    $tunnel_write->("OK");
+		    $tunnel_write->("running");
+		    next;
+		}
+
+		my $reader = $state->{migrate_hook}->{output};
+		my $output = "";
+
+		while (my $line = <$reader>) {
+		    $output .= $line;
+		}
+
+		close $state->{migrate_hook}->{output};
+		delete $state->{migrate_hook};
+
+		my $status = ($? == 0)
+		    ? 'finished'
+		    : 'error';
+
+		$tunnel_write->("OK");
+		$tunnel_write->("$status");
+		$tunnel_write->(MIME::Base64::encode($output, ''));
 	    }
 	}
 
-- 
2.30.2





More information about the pve-devel mailing list