]> git.proxmox.com Git - pve-guest-common.git/blame - PVE/AbstractMigrate.pm
PVE/AbstractMigrate.pm: avoid locale specific time stamps
[pve-guest-common.git] / PVE / AbstractMigrate.pm
CommitLineData
58a3c91c
FG
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
9ed0fe88 17 my $tstr = strftime("%F %H:%M:%S", localtime);
58a3c91c
FG
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;
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
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
85sub get_remote_migration_ip {
86 my ($self) = @_;
87
88 my $ip;
89
90 my $cmd = [@{$self->{rem_ssh}}, 'pvecm', 'mtunnel', '--get_migration_ip'];
91
92 push @$cmd, '--migration_network', $self->{opts}->{migration_network}
93 if defined($self->{opts}->{migration_network});
94
95 PVE::Tools::run_command($cmd, outfunc => sub {
96 my $line = shift;
97
98 if ($line =~ m/^ip: '($PVE::Tools::IPRE)'$/) {
99 $ip = $1;
100 }
101 });
102
103 return $ip;
104}
105
106my $eval_int = sub {
107 my ($self, $func, @param) = @_;
108
109 eval {
110 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
111 $self->{delayed_interrupt} = 0;
112 die "interrupted by signal\n";
113 };
114 local $SIG{PIPE} = sub {
115 $self->{delayed_interrupt} = 0;
116 die "interrupted by signal\n";
117 };
118
119 my $di = $self->{delayed_interrupt};
120 $self->{delayed_interrupt} = 0;
121
122 die "interrupted by signal\n" if $di;
123
124 &$func($self, @param);
125 };
126};
127
128my @ssh_opts = ('-o', 'BatchMode=yes');
129my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
58a3c91c
FG
130
131sub migrate {
132 my ($class, $node, $nodeip, $vmid, $opts) = @_;
133
134 $class = ref($class) || $class;
135
136 my $self = {
137 delayed_interrupt => 0,
138 opts => $opts,
139 vmid => $vmid,
140 node => $node,
141 nodeip => $nodeip,
58a3c91c 142 rem_ssh => [ @ssh_cmd, "root\@$nodeip" ],
58a3c91c
FG
143 };
144
145 $self = bless $self, $class;
146
147 my $starttime = time();
148
58a3c91c
FG
149 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
150 $self->log('err', "received interrupt - delayed");
151 $self->{delayed_interrupt} = 1;
152 };
153
58a3c91c
FG
154 # lock container during migration
155 eval { $self->lock_vm($self->{vmid}, sub {
156
157 $self->{running} = 0;
158 &$eval_int($self, sub { $self->{running} = $self->prepare($self->{vmid}); });
159 die $@ if $@;
160
161 # get dedicated migration address from remote node, if set.
162 # as a side effect this checks also if the other node can be accessed
163 # through ssh and that it has quorum
164 my $remote_migration_ip = $self->get_remote_migration_ip();
165
166 if (defined($remote_migration_ip)) {
167 $nodeip = $remote_migration_ip;
168 $self->{nodeip} = $remote_migration_ip;
169 $self->{rem_ssh} = [ @ssh_cmd, "root\@$nodeip" ];
170
171 $self->log('info', "use dedicated network address for sending " .
172 "migration traffic ($self->{nodeip})");
173
174 # test if we can connect to new IP
175 my $cmd = [ @{$self->{rem_ssh}}, '/bin/true' ];
176 eval { $self->cmd_quiet($cmd); };
177 die "Can't connect to destination address ($self->{nodeip}) using " .
178 "public key authentication\n" if $@;
179 }
180
181 &$eval_int($self, sub { $self->phase1($self->{vmid}); });
182 my $err = $@;
183 if ($err) {
184 $self->log('err', $err);
185 eval { $self->phase1_cleanup($self->{vmid}, $err); };
186 if (my $tmperr = $@) {
187 $self->log('err', $tmperr);
188 }
189 eval { $self->final_cleanup($self->{vmid}); };
190 if (my $tmperr = $@) {
191 $self->log('err', $tmperr);
192 }
193 die $err;
194 }
195
196 # vm is now owned by other node
197 # Note: there is no VM config file on the local node anymore
198
199 if ($self->{running}) {
200
201 &$eval_int($self, sub { $self->phase2($self->{vmid}); });
202 my $phase2err = $@;
203 if ($phase2err) {
204 $self->{errors} = 1;
205 $self->log('err', "online migrate failure - $phase2err");
206 }
207 eval { $self->phase2_cleanup($self->{vmid}, $phase2err); };
208 if (my $err = $@) {
209 $self->log('err', $err);
210 $self->{errors} = 1;
211 }
212 }
213
214 # phase3 (finalize)
215 &$eval_int($self, sub { $self->phase3($self->{vmid}); });
216 my $phase3err = $@;
217 if ($phase3err) {
218 $self->log('err', $phase3err);
219 $self->{errors} = 1;
220 }
221 eval { $self->phase3_cleanup($self->{vmid}, $phase3err); };
222 if (my $err = $@) {
223 $self->log('err', $err);
224 $self->{errors} = 1;
225 }
226 eval { $self->final_cleanup($self->{vmid}); };
227 if (my $err = $@) {
228 $self->log('err', $err);
229 $self->{errors} = 1;
230 }
231 })};
232
233 my $err = $@;
234
235 my $delay = time() - $starttime;
236 my $mins = int($delay/60);
237 my $secs = $delay - $mins*60;
238 my $hours = int($mins/60);
239 $mins = $mins - $hours*60;
240
241 my $duration = sprintf "%02d:%02d:%02d", $hours, $mins, $secs;
242
243 if ($err) {
244 $self->log('err', "migration aborted (duration $duration): $err");
245 die "migration aborted\n";
246 }
247
248 if ($self->{errors}) {
249 $self->log('err', "migration finished with problems (duration $duration)");
250 die "migration problems\n"
251 }
252
253 $self->log('info', "migration finished successfully (duration $duration)");
254}
255
256sub lock_vm {
257 my ($self, $vmid, $code, @param) = @_;
258
259 die "abstract method - implement me";
260}
261
262sub prepare {
263 my ($self, $vmid) = @_;
264
265 die "abstract method - implement me";
266
267 # return $running;
268}
269
270# transfer all data and move VM config files
271sub phase1 {
272 my ($self, $vmid) = @_;
273 die "abstract method - implement me";
274}
275
276# only called if there are errors in phase1
277sub phase1_cleanup {
278 my ($self, $vmid, $err) = @_;
279 die "abstract method - implement me";
280}
281
282# only called when VM is running and phase1 was successful
283sub phase2 {
284 my ($self, $vmid) = @_;
285 die "abstract method - implement me";
286}
287
288# only called when VM is running and phase1 was successful
289sub phase2_cleanup {
290 my ($self, $vmid, $err) = @_;
291};
292
293# only called when phase1 was successful
294sub phase3 {
295 my ($self, $vmid) = @_;
296}
297
298# only called when phase1 was successful
299sub phase3_cleanup {
300 my ($self, $vmid, $err) = @_;
301}
302
303# final cleanup - always called
304sub final_cleanup {
305 my ($self, $vmid) = @_;
306 die "abstract method - implement me";
307}
308
3091;