use correct log function
[pve-common.git] / data / PVE / AbstractMigrate.pm
1 package PVE::AbstractMigrate;
2
3 use strict;
4 use warnings;
5 use POSIX qw(strftime);
6 use PVE::Tools;
7
8 my $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
30 sub log {
31 my ($self, $level, $msg) = @_;
32
33 chomp $msg;
34
35 return if !$msg;
36
37 print &$msg2text($level, $msg);
38 }
39
40 sub cmd {
41 my ($self, $cmd, %param) = @_;
42
43 my $logfunc = sub {
44 my $line = shift;
45 $self->log('info', $line);
46 };
47
48 $self->log('info', "# " . PVE::Tools::cmd2string($cmd));
49
50 PVE::Tools::run_command($cmd, %param, outfunc => $logfunc, errfunc => $logfunc);
51 }
52
53 my $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
75 sub cmd_quiet {
76 my ($self, $cmd, %param) = @_;
77 return &$run_command_quiet_full($self, $cmd, 0, %param);
78 }
79
80 sub cmd_logerr {
81 my ($self, $cmd, %param) = @_;
82 return &$run_command_quiet_full($self, $cmd, 1, %param);
83 }
84
85 my $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
107 # blowfish is a fast block cipher, much faster then 3des
108 my @ssh_opts = ('-c', 'blowfish', '-o', 'BatchMode=yes');
109 my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
110 my @scp_cmd = ('/usr/bin/scp', @ssh_opts);
111 my @rsync_opts = ('-aH', '--delete', '--numeric-ids');
112 my @rsync_cmd = ('/usr/bin/rsync', @rsync_opts);
113
114 sub migrate {
115 my ($class, $node, $nodeip, $vmid, $opts) = @_;
116
117 $class = ref($class) || $class;
118
119 my $self = {
120 delayed_interrupt => 0,
121 opts => $opts,
122 vmid => $vmid,
123 node => $node,
124 nodeip => $nodeip,
125 rsync_cmd => [ @rsync_cmd ],
126 rem_ssh => [ @ssh_cmd, "root\@$nodeip" ],
127 scp_cmd => [ @scp_cmd ],
128 };
129
130 $self = bless $self, $class;
131
132 my $starttime = time();
133
134 local $ENV{RSYNC_RSH} = join(' ', @ssh_cmd);
135
136 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
137 $self->log('err', "received interrupt - delayed");
138 $self->{delayed_interrupt} = 1;
139 };
140
141 local $ENV{RSYNC_RSH} = join(' ', @ssh_cmd);
142
143 # lock container during migration
144 eval { $self->lock_vm($self->{vmid}, sub {
145
146 $self->{running} = 0;
147 &$eval_int($self, sub { $self->{running} = $self->prepare($self->{vmid}); });
148 die $@ if $@;
149
150 &$eval_int($self, sub { $self->phase1($self->{vmid}); });
151 my $err = $@;
152 if ($err) {
153 $self->log('err', $err);
154 eval { $self->phase1_cleanup($self->{vmid}, $err); };
155 if (my $tmperr = $@) {
156 $self->log('err', $tmperr);
157 }
158 eval { $self->final_cleanup($self->{vmid}); };
159 if (my $tmperr = $@) {
160 $self->log('err', $tmperr);
161 }
162 die $err;
163 }
164
165 # vm is now owned by other node
166 # Note: there is no VM config file on the local node anymore
167
168 if ($self->{running}) {
169
170 &$eval_int($self, sub { $self->phase2($self->{vmid}); });
171 my $phase2err = $@;
172 if ($phase2err) {
173 $self->{errors} = 1;
174 $self->log('err', "online migrate failure - $phase2err");
175 }
176 eval { $self->phase2_cleanup($self->{vmid}, $phase2err); };
177 if (my $err = $@) {
178 $self->log('err', $err);
179 $self->{errors} = 1;
180 }
181 }
182
183 # phase3 (finalize)
184 &$eval_int($self, sub { $self->phase3($self->{vmid}); });
185 my $phase3err = $@;
186 if ($phase3err) {
187 $self->log('err', $phase3err);
188 $self->{errors} = 1;
189 }
190 eval { $self->phase3_cleanup($self->{vmid}, $phase3err); };
191 if (my $err = $@) {
192 $self->log('err', $err);
193 $self->{errors} = 1;
194 }
195 eval { $self->final_cleanup($self->{vmid}); };
196 if (my $err = $@) {
197 $self->log('err', $err);
198 $self->{errors} = 1;
199 }
200 })};
201
202 my $err = $@;
203
204 my $delay = time() - $starttime;
205 my $mins = int($delay/60);
206 my $secs = $delay - $mins*60;
207 my $hours = int($mins/60);
208 $mins = $mins - $hours*60;
209
210 my $duration = sprintf "%02d:%02d:%02d", $hours, $mins, $secs;
211
212 if ($err) {
213 $self->log('err', "migration aborted (duration $duration): $err");
214 die "migration aborted\n";
215 }
216
217 if ($self->{errors}) {
218 $self->log('err', "migration finished with problems (duration $duration)");
219 die "migration problems\n"
220 }
221
222 $self->log('info', "migration finished successfuly (duration $duration)");
223 }
224
225 sub lock_vm {
226 my ($self, $vmid, $code, @param) = @_;
227
228 die "abstract method - implement me";
229 }
230
231 sub prepare {
232 my ($self, $vmid) = @_;
233
234 die "abstract method - implement me";
235
236 # return $running;
237 }
238
239 # transfer all data and move VM config files
240 sub phase1 {
241 my ($self, $vmid) = @_;
242 die "abstract method - implement me";
243 }
244
245 # only called if there are errors in phase1
246 sub phase1_cleanup {
247 my ($self, $vmid, $err) = @_;
248 die "abstract method - implement me";
249 }
250
251 # only called when VM is running and phase1 was successful
252 sub phase2 {
253 my ($self, $vmid) = @_;
254 die "abstract method - implement me";
255 }
256
257 # only called when VM is running and phase1 was successful
258 sub phase2_cleanup {
259 my ($self, $vmid, $err) = @_;
260 };
261
262 # only called when phase1 was successful
263 sub phase3 {
264 my ($self, $vmid) = @_;
265 }
266
267 # only called when phase1 was successful
268 sub phase3_cleanup {
269 my ($self, $vmid, $err) = @_;
270 }
271
272 # final cleanup - always called
273 sub final_cleanup {
274 my ($self, $vmid) = @_;
275 die "abstract method - implement me";
276 }
277
278 1;