remove + from getopt's prefix_pattern
[pve-common.git] / src / 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 my @ssh_opts = ('-o', 'BatchMode=yes');
108 my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
109 my @scp_cmd = ('/usr/bin/scp', @ssh_opts);
110 my @rsync_opts = ('-aHAX', '--delete', '--numeric-ids');
111 my @rsync_cmd = ('/usr/bin/rsync', @rsync_opts);
112
113 sub 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 {
136 $self->log('err', "received interrupt - delayed");
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 successfully (duration $duration)");
222 }
223
224 sub lock_vm {
225 my ($self, $vmid, $code, @param) = @_;
226
227 die "abstract method - implement me";
228 }
229
230 sub 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
239 sub phase1 {
240 my ($self, $vmid) = @_;
241 die "abstract method - implement me";
242 }
243
244 # only called if there are errors in phase1
245 sub 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
251 sub phase2 {
252 my ($self, $vmid) = @_;
253 die "abstract method - implement me";
254 }
255
256 # only called when VM is running and phase1 was successful
257 sub phase2_cleanup {
258 my ($self, $vmid, $err) = @_;
259 };
260
261 # only called when phase1 was successful
262 sub phase3 {
263 my ($self, $vmid) = @_;
264 }
265
266 # only called when phase1 was successful
267 sub phase3_cleanup {
268 my ($self, $vmid, $err) = @_;
269 }
270
271 # final cleanup - always called
272 sub final_cleanup {
273 my ($self, $vmid) = @_;
274 die "abstract method - implement me";
275 }
276
277 1;