bump version to 1.0-39
[pve-common.git] / data / PVE / AbstractMigrate.pm
CommitLineData
643adc82
DM
1package PVE::AbstractMigrate;
2
3use strict;
4use warnings;
5use POSIX qw(strftime);
6use PVE::Tools;
7
8my $msg2text = sub {
9 my ($level, $msg) = @_;
10
11 chomp $msg;
12
13 return '' if !$msg;
14
15 my $res = '';
16
17 my $tstr = strftime("%b %d %H:%M:%S", localtime);
18
19 foreach my $line (split (/\n/, $msg)) {
20 if ($level eq 'err') {
21 $res .= "$tstr ERROR: $line\n";
22 } else {
23 $res .= "$tstr $line\n";
24 }
25 }
26
27 return $res;
28};
29
30sub log {
31 my ($self, $level, $msg) = @_;
32
33 chomp $msg;
34
35 return if !$msg;
36
37 print &$msg2text($level, $msg);
38}
39
40sub cmd {
41 my ($self, $cmd, %param) = @_;
42
43 my $logfunc = sub {
44 my $line = shift;
92ea5df8 45 $self->log('info', $line);
643adc82
DM
46 };
47
48 $self->log('info', "# " . PVE::Tools::cmd2string($cmd));
49
50 PVE::Tools::run_command($cmd, %param, outfunc => $logfunc, errfunc => $logfunc);
51}
52
53my $run_command_quiet_full = sub {
54 my ($self, $cmd, $logerr, %param) = @_;
55
56 my $log = '';
57 my $logfunc = sub {
58 my $line = shift;
59 $log .= &$msg2text('info', $line);;
60 };
61
62 eval { PVE::Tools::run_command($cmd, %param, outfunc => $logfunc, errfunc => $logfunc); };
63 if (my $err = $@) {
64 $self->log('info', "# " . PVE::Tools::cmd2string($cmd));
65 print $log;
66 if ($logerr) {
67 $self->{errors} = 1;
68 $self->log('err', $err);
69 } else {
70 die $err;
71 }
72 }
73};
74
75sub cmd_quiet {
76 my ($self, $cmd, %param) = @_;
77 return &$run_command_quiet_full($self, $cmd, 0, %param);
78}
79
80sub cmd_logerr {
81 my ($self, $cmd, %param) = @_;
82 return &$run_command_quiet_full($self, $cmd, 1, %param);
83}
84
85my $eval_int = sub {
86 my ($self, $func, @param) = @_;
87
88 eval {
89 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
90 $self->{delayed_interrupt} = 0;
91 die "interrupted by signal\n";
92 };
93 local $SIG{PIPE} = sub {
94 $self->{delayed_interrupt} = 0;
95 die "interrupted by signal\n";
96 };
97
98 my $di = $self->{delayed_interrupt};
99 $self->{delayed_interrupt} = 0;
100
101 die "interrupted by signal\n" if $di;
102
103 &$func($self, @param);
104 };
105};
106
6606fa12 107my @ssh_opts = ('-o', 'BatchMode=yes');
643adc82
DM
108my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
109my @scp_cmd = ('/usr/bin/scp', @ssh_opts);
36ac0df3 110my @rsync_opts = ('-aHAX', '--delete', '--numeric-ids');
643adc82
DM
111my @rsync_cmd = ('/usr/bin/rsync', @rsync_opts);
112
113sub migrate {
114 my ($class, $node, $nodeip, $vmid, $opts) = @_;
115
116 $class = ref($class) || $class;
117
118 my $self = {
119 delayed_interrupt => 0,
120 opts => $opts,
121 vmid => $vmid,
122 node => $node,
123 nodeip => $nodeip,
124 rsync_cmd => [ @rsync_cmd ],
125 rem_ssh => [ @ssh_cmd, "root\@$nodeip" ],
126 scp_cmd => [ @scp_cmd ],
127 };
128
129 $self = bless $self, $class;
130
131 my $starttime = time();
132
133 local $ENV{RSYNC_RSH} = join(' ', @ssh_cmd);
134
135 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
92ea5df8 136 $self->log('err', "received interrupt - delayed");
643adc82
DM
137 $self->{delayed_interrupt} = 1;
138 };
139
140 local $ENV{RSYNC_RSH} = join(' ', @ssh_cmd);
141
142 # lock container during migration
143 eval { $self->lock_vm($self->{vmid}, sub {
144
145 $self->{running} = 0;
146 &$eval_int($self, sub { $self->{running} = $self->prepare($self->{vmid}); });
147 die $@ if $@;
148
149 &$eval_int($self, sub { $self->phase1($self->{vmid}); });
150 my $err = $@;
151 if ($err) {
152 $self->log('err', $err);
153 eval { $self->phase1_cleanup($self->{vmid}, $err); };
154 if (my $tmperr = $@) {
155 $self->log('err', $tmperr);
156 }
157 eval { $self->final_cleanup($self->{vmid}); };
158 if (my $tmperr = $@) {
159 $self->log('err', $tmperr);
160 }
161 die $err;
162 }
163
164 # vm is now owned by other node
165 # Note: there is no VM config file on the local node anymore
166
167 if ($self->{running}) {
168
169 &$eval_int($self, sub { $self->phase2($self->{vmid}); });
170 my $phase2err = $@;
171 if ($phase2err) {
172 $self->{errors} = 1;
173 $self->log('err', "online migrate failure - $phase2err");
174 }
175 eval { $self->phase2_cleanup($self->{vmid}, $phase2err); };
176 if (my $err = $@) {
177 $self->log('err', $err);
178 $self->{errors} = 1;
179 }
180 }
181
182 # phase3 (finalize)
183 &$eval_int($self, sub { $self->phase3($self->{vmid}); });
184 my $phase3err = $@;
185 if ($phase3err) {
186 $self->log('err', $phase3err);
187 $self->{errors} = 1;
188 }
189 eval { $self->phase3_cleanup($self->{vmid}, $phase3err); };
190 if (my $err = $@) {
191 $self->log('err', $err);
192 $self->{errors} = 1;
193 }
194 eval { $self->final_cleanup($self->{vmid}); };
195 if (my $err = $@) {
196 $self->log('err', $err);
197 $self->{errors} = 1;
198 }
199 })};
200
201 my $err = $@;
202
203 my $delay = time() - $starttime;
204 my $mins = int($delay/60);
205 my $secs = $delay - $mins*60;
206 my $hours = int($mins/60);
207 $mins = $mins - $hours*60;
208
209 my $duration = sprintf "%02d:%02d:%02d", $hours, $mins, $secs;
210
211 if ($err) {
212 $self->log('err', "migration aborted (duration $duration): $err");
213 die "migration aborted\n";
214 }
215
216 if ($self->{errors}) {
217 $self->log('err', "migration finished with problems (duration $duration)");
218 die "migration problems\n"
219 }
220
221 $self->log('info', "migration finished successfuly (duration $duration)");
222}
223
224sub lock_vm {
225 my ($self, $vmid, $code, @param) = @_;
226
227 die "abstract method - implement me";
228}
229
230sub prepare {
231 my ($self, $vmid) = @_;
232
233 die "abstract method - implement me";
234
235 # return $running;
236}
237
238# transfer all data and move VM config files
239sub phase1 {
240 my ($self, $vmid) = @_;
241 die "abstract method - implement me";
242}
243
244# only called if there are errors in phase1
245sub phase1_cleanup {
246 my ($self, $vmid, $err) = @_;
247 die "abstract method - implement me";
248}
249
250# only called when VM is running and phase1 was successful
251sub phase2 {
252 my ($self, $vmid) = @_;
253 die "abstract method - implement me";
254}
255
256# only called when VM is running and phase1 was successful
257sub phase2_cleanup {
258 my ($self, $vmid, $err) = @_;
259};
260
261# only called when phase1 was successful
262sub phase3 {
263 my ($self, $vmid) = @_;
264}
265
266# only called when phase1 was successful
267sub phase3_cleanup {
268 my ($self, $vmid, $err) = @_;
269}
270
271# final cleanup - always called
272sub final_cleanup {
273 my ($self, $vmid) = @_;
274 die "abstract method - implement me";
275}
276
2771;