From 643adc823549aff4023a35610a406bc68c7fff64 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Mon, 5 Dec 2011 07:21:53 +0100 Subject: [PATCH] implement AbstractMigrate.pm --- data/Makefile | 1 + data/PVE/AbstractMigrate.pm | 278 ++++++++++++++++++++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 data/PVE/AbstractMigrate.pm diff --git a/data/Makefile b/data/Makefile index ad3f38f..286473a 100644 --- a/data/Makefile +++ b/data/Makefile @@ -16,6 +16,7 @@ LIB_SOURCES= \ AtomicFile.pm \ INotify.pm \ Tools.pm \ + AbstractMigrate.pm \ Exception.pm all: diff --git a/data/PVE/AbstractMigrate.pm b/data/PVE/AbstractMigrate.pm new file mode 100644 index 0000000..6c6061d --- /dev/null +++ b/data/PVE/AbstractMigrate.pm @@ -0,0 +1,278 @@ +package PVE::AbstractMigrate; + +use strict; +use warnings; +use POSIX qw(strftime); +use PVE::Tools; + +my $msg2text = sub { + my ($level, $msg) = @_; + + chomp $msg; + + return '' if !$msg; + + my $res = ''; + + my $tstr = strftime("%b %d %H:%M:%S", localtime); + + foreach my $line (split (/\n/, $msg)) { + if ($level eq 'err') { + $res .= "$tstr ERROR: $line\n"; + } else { + $res .= "$tstr $line\n"; + } + } + + return $res; +}; + +sub log { + my ($self, $level, $msg) = @_; + + chomp $msg; + + return if !$msg; + + print &$msg2text($level, $msg); +} + +sub cmd { + my ($self, $cmd, %param) = @_; + + my $logfunc = sub { + my $line = shift; + logmsg('info', $line); + }; + + $self->log('info', "# " . PVE::Tools::cmd2string($cmd)); + + PVE::Tools::run_command($cmd, %param, outfunc => $logfunc, errfunc => $logfunc); +} + +my $run_command_quiet_full = sub { + my ($self, $cmd, $logerr, %param) = @_; + + my $log = ''; + my $logfunc = sub { + my $line = shift; + $log .= &$msg2text('info', $line);; + }; + + eval { PVE::Tools::run_command($cmd, %param, outfunc => $logfunc, errfunc => $logfunc); }; + if (my $err = $@) { + $self->log('info', "# " . PVE::Tools::cmd2string($cmd)); + print $log; + if ($logerr) { + $self->{errors} = 1; + $self->log('err', $err); + } else { + die $err; + } + } +}; + +sub cmd_quiet { + my ($self, $cmd, %param) = @_; + return &$run_command_quiet_full($self, $cmd, 0, %param); +} + +sub cmd_logerr { + my ($self, $cmd, %param) = @_; + return &$run_command_quiet_full($self, $cmd, 1, %param); +} + +my $eval_int = sub { + my ($self, $func, @param) = @_; + + eval { + local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { + $self->{delayed_interrupt} = 0; + die "interrupted by signal\n"; + }; + local $SIG{PIPE} = sub { + $self->{delayed_interrupt} = 0; + die "interrupted by signal\n"; + }; + + my $di = $self->{delayed_interrupt}; + $self->{delayed_interrupt} = 0; + + die "interrupted by signal\n" if $di; + + &$func($self, @param); + }; +}; + +# blowfish is a fast block cipher, much faster then 3des +my @ssh_opts = ('-c', 'blowfish', '-o', 'BatchMode=yes'); +my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts); +my @scp_cmd = ('/usr/bin/scp', @ssh_opts); +my @rsync_opts = ('-aH', '--delete', '--numeric-ids'); +my @rsync_cmd = ('/usr/bin/rsync', @rsync_opts); + +sub migrate { + my ($class, $node, $nodeip, $vmid, $opts) = @_; + + $class = ref($class) || $class; + + my $self = { + delayed_interrupt => 0, + opts => $opts, + vmid => $vmid, + node => $node, + nodeip => $nodeip, + rsync_cmd => [ @rsync_cmd ], + rem_ssh => [ @ssh_cmd, "root\@$nodeip" ], + scp_cmd => [ @scp_cmd ], + }; + + $self = bless $self, $class; + + my $starttime = time(); + + local $ENV{RSYNC_RSH} = join(' ', @ssh_cmd); + + local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub { + logmsg('err', "received interrupt - delayed"); + $self->{delayed_interrupt} = 1; + }; + + local $ENV{RSYNC_RSH} = join(' ', @ssh_cmd); + + # lock container during migration + eval { $self->lock_vm($self->{vmid}, sub { + + $self->{running} = 0; + &$eval_int($self, sub { $self->{running} = $self->prepare($self->{vmid}); }); + die $@ if $@; + + &$eval_int($self, sub { $self->phase1($self->{vmid}); }); + my $err = $@; + if ($err) { + $self->log('err', $err); + eval { $self->phase1_cleanup($self->{vmid}, $err); }; + if (my $tmperr = $@) { + $self->log('err', $tmperr); + } + eval { $self->final_cleanup($self->{vmid}); }; + if (my $tmperr = $@) { + $self->log('err', $tmperr); + } + die $err; + } + + # vm is now owned by other node + # Note: there is no VM config file on the local node anymore + + if ($self->{running}) { + + &$eval_int($self, sub { $self->phase2($self->{vmid}); }); + my $phase2err = $@; + if ($phase2err) { + $self->{errors} = 1; + $self->log('err', "online migrate failure - $phase2err"); + } + eval { $self->phase2_cleanup($self->{vmid}, $phase2err); }; + if (my $err = $@) { + $self->log('err', $err); + $self->{errors} = 1; + } + } + + # phase3 (finalize) + &$eval_int($self, sub { $self->phase3($self->{vmid}); }); + my $phase3err = $@; + if ($phase3err) { + $self->log('err', $phase3err); + $self->{errors} = 1; + } + eval { $self->phase3_cleanup($self->{vmid}, $phase3err); }; + if (my $err = $@) { + $self->log('err', $err); + $self->{errors} = 1; + } + eval { $self->final_cleanup($self->{vmid}); }; + if (my $err = $@) { + $self->log('err', $err); + $self->{errors} = 1; + } + })}; + + my $err = $@; + + my $delay = time() - $starttime; + my $mins = int($delay/60); + my $secs = $delay - $mins*60; + my $hours = int($mins/60); + $mins = $mins - $hours*60; + + my $duration = sprintf "%02d:%02d:%02d", $hours, $mins, $secs; + + if ($err) { + $self->log('err', "migration aborted (duration $duration): $err"); + die "migration aborted\n"; + } + + if ($self->{errors}) { + $self->log('err', "migration finished with problems (duration $duration)"); + die "migration problems\n" + } + + $self->log('info', "migration finished successfuly (duration $duration)"); +} + +sub lock_vm { + my ($self, $vmid, $code, @param) = @_; + + die "abstract method - implement me"; +} + +sub prepare { + my ($self, $vmid) = @_; + + die "abstract method - implement me"; + + # return $running; +} + +# transfer all data and move VM config files +sub phase1 { + my ($self, $vmid) = @_; + die "abstract method - implement me"; +} + +# only called if there are errors in phase1 +sub phase1_cleanup { + my ($self, $vmid, $err) = @_; + die "abstract method - implement me"; +} + +# only called when VM is running and phase1 was successful +sub phase2 { + my ($self, $vmid) = @_; + die "abstract method - implement me"; +} + +# only called when VM is running and phase1 was successful +sub phase2_cleanup { + my ($self, $vmid, $err) = @_; +}; + +# only called when phase1 was successful +sub phase3 { + my ($self, $vmid) = @_; +} + +# only called when phase1 was successful +sub phase3_cleanup { + my ($self, $vmid, $err) = @_; +} + +# final cleanup - always called +sub final_cleanup { + my ($self, $vmid) = @_; + die "abstract method - implement me"; +} + +1; -- 2.39.2