half-revert: remove autostart property from bridge ports
[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;