1 package PVE
::QemuMigrate
;
5 use PVE
::AbstractMigrate
;
13 use base
qw(PVE::AbstractMigrate);
15 sub fork_command_pipe
{
16 my ($self, $cmd) = @_;
18 my $reader = IO
::File-
>new();
19 my $writer = IO
::File-
>new();
25 eval { $cpid = open2
($reader, $writer, @$cmd); };
30 if ($orig_pid != $$) {
31 $self->log('err', "can't fork command pipe\n");
38 return { writer
=> $writer, reader
=> $reader, pid
=> $cpid };
41 sub finish_command_pipe
{
42 my ($self, $cmdpipe, $timeout) = @_;
44 my $writer = $cmdpipe->{writer
};
45 my $reader = $cmdpipe->{reader
};
50 my $cpid = $cmdpipe->{pid
};
53 for (my $i = 0; $i < $timeout; $i++) {
54 return if !PVE
::ProcFSTools
::check_process_running
($cpid);
59 $self->log('info', "ssh tunnel still running - terminating now with SIGTERM\n");
63 for (my $i = 0; $i < 10; $i++) {
64 return if !PVE
::ProcFSTools
::check_process_running
($cpid);
68 $self->log('info', "ssh tunnel still running - terminating now with SIGKILL\n");
74 my ($self, $nodeip, $lport, $rport) = @_;
76 my $cmd = [@{$self->{rem_ssh
}}, '-L', "$lport:localhost:$rport",
79 my $tunnel = $self->fork_command_pipe($cmd);
81 my $reader = $tunnel->{reader
};
85 PVE
::Tools
::run_with_timeout
(60, sub { $helo = <$reader>; });
86 die "no reply\n" if !$helo;
87 die "no quorum on target node\n" if $helo =~ m/^no quorum$/;
88 die "got strange reply from mtunnel ('$helo')\n"
89 if $helo !~ m/^tunnel online$/;
94 $self->finish_command_pipe($tunnel);
95 die "can't open migration tunnel - $err";
101 my ($self, $tunnel) = @_;
103 my $writer = $tunnel->{writer
};
106 PVE
::Tools
::run_with_timeout
(30, sub {
107 print $writer "quit\n";
113 $self->finish_command_pipe($tunnel, 30);
119 my ($self, $vmid, $code, @param) = @_;
121 return PVE
::QemuServer
::lock_config
($vmid, $code, @param);
125 my ($self, $vmid) = @_;
127 my $online = $self->{opts
}->{online
};
129 $self->{storecfg
} = PVE
::Storage
::config
();
132 my $conf = $self->{vmconf
} = PVE
::QemuServer
::load_config
($vmid);
134 PVE
::QemuServer
::check_lock
($conf);
137 if (my $pid = PVE
::QemuServer
::check_running
($vmid)) {
138 die "cant migrate running VM without --online\n" if !$online;
142 if (my $loc_res = PVE
::QemuServer
::check_local_resources
($conf, 1)) {
143 if ($self->{running
} || !$self->{opts
}->{force
}) {
144 die "can't migrate VM which uses local devices\n";
146 $self->log('info', "migrating VM which uses local devices");
151 my $vollist = PVE
::QemuServer
::get_vm_volumes
($conf);
152 PVE
::Storage
::activate_volumes
($self->{storecfg
}, $vollist);
154 # fixme: check if storage is available on both nodes
156 # test ssh connection
157 my $cmd = [ @{$self->{rem_ssh
}}, '/bin/true' ];
158 eval { $self->cmd_quiet($cmd); };
159 die "Can't connect to destination address using public key\n" if $@;
165 my ($self, $vmid) = @_;
167 $self->log('info', "copying disk images");
169 my $conf = $self->{vmconf
};
171 $self->{volumes
} = [];
180 # get list from PVE::Storage (for unused volumes)
181 my $dl = PVE
::Storage
::vdisk_list
($self->{storecfg
}, undef, $vmid);
182 PVE
::Storage
::foreach_volid
($dl, sub {
183 my ($volid, $sid, $volname) = @_;
185 # check if storage is available on both nodes
186 my $scfg = PVE
::Storage
::storage_check_node
($self->{storecfg
}, $sid);
187 PVE
::Storage
::storage_check_node
($self->{storecfg
}, $sid, $self->{node
});
189 return if $scfg->{shared
};
191 $volhash->{$volid} = 1;
194 # and add used,owned/non-shared disks (just to be sure we have all)
197 PVE
::QemuServer
::foreach_drive
($conf, sub {
198 my ($ds, $drive) = @_;
200 my $volid = $drive->{file
};
203 die "cant migrate local file/device '$volid'\n" if $volid =~ m
|^/|;
205 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) {
206 die "cant migrate local cdrom drive\n" if $volid eq 'cdrom';
207 return if $volid eq 'none';
208 $cdromhash->{$volid} = 1;
211 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
213 # check if storage is available on both nodes
214 my $scfg = PVE
::Storage
::storage_check_node
($self->{storecfg
}, $sid);
215 PVE
::Storage
::storage_check_node
($self->{storecfg
}, $sid, $self->{node
});
217 return if $scfg->{shared
};
219 die "can't migrate local cdrom '$volid'\n" if $cdromhash->{$volid};
223 my ($path, $owner) = PVE
::Storage
::path
($self->{storecfg
}, $volid);
225 die "can't migrate volume '$volid' - owned by other VM (owner = VM $owner)\n"
226 if !$owner || ($owner != $self->{vmid
});
228 $volhash->{$volid} = 1;
231 if ($self->{running
} && !$sharedvm) {
232 die "can't do online migration - VM uses local disks\n";
235 # do some checks first
236 foreach my $volid (keys %$volhash) {
237 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
238 my $scfg = PVE
::Storage
::storage_config
($self->{storecfg
}, $sid);
240 die "can't migrate '$volid' - storagy type '$scfg->{type}' not supported\n"
241 if $scfg->{type
} ne 'dir';
244 foreach my $volid (keys %$volhash) {
245 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
246 push @{$self->{volumes
}}, $volid;
247 PVE
::Storage
::storage_migrate
($self->{storecfg
}, $volid, $self->{nodeip
}, $sid);
250 die "Failed to sync data - $@" if $@;
254 my ($self, $vmid) = @_;
256 $self->log('info', "starting migration of VM $vmid to node '$self->{node}' ($self->{nodeip})");
258 my $conf = $self->{vmconf
};
260 # set migrate lock in config file
261 $conf->{lock} = 'migrate';
262 PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1);
264 sync_disks
($self, $vmid);
266 # move config to remote node
267 my $conffile = PVE
::QemuServer
::config_file
($vmid);
268 my $newconffile = PVE
::QemuServer
::config_file
($vmid, $self->{node
});
270 die "Failed to move config to node '$self->{node}' - rename failed: $!\n"
271 if !rename($conffile, $newconffile);
275 my ($self, $vmid, $err) = @_;
277 $self->log('info', "aborting phase 1 - cleanup resources");
279 my $conf = $self->{vmconf
};
280 delete $conf->{lock};
281 eval { PVE
::QemuServer
::update_config_nolock
($vmid, $conf, 1) };
283 $self->log('err', $err);
286 if ($self->{volumes
}) {
287 foreach my $volid (@{$self->{volumes
}}) {
288 $self->log('err', "found stale volume copy '$volid' on node '$self->{node}'");
289 # fixme: try to remove ?
295 my ($self, $vmid) = @_;
297 my $conf = $self->{vmconf
};
299 $self->log('info', "starting VM $vmid on remote node '$self->{node}'");
303 ## start on remote node
304 my $cmd = [@{$self->{rem_ssh
}}, 'qm', 'start',
305 $vmid, '--stateuri', 'tcp', '--skiplock'];
307 PVE
::Tools
::run_command
($cmd, outfunc
=> sub {
310 if ($line =~ m/^migration listens on port (\d+)$/) {
313 }, errfunc
=> sub {});
315 die "unable to detect remote migration port\n" if !$rport;
317 $self->log('info', "starting migration tunnel");
319 ## create tunnel to remote port
320 my $lport = PVE
::QemuServer
::next_migrate_port
();
321 $self->{tunnel
} = $self->fork_tunnel($self->{nodeip
}, $lport, $rport);
323 $self->log('info', "starting online/live migration on port $lport");
328 my $merr = PVE
::QemuServer
::vm_monitor_command
($vmid, "migrate -d \"tcp:localhost:$lport\"", 1);
333 my $stat = PVE::QemuServer::vm_monitor_command($vmid, "info migrate
", 1);
334 if ($stat =~ m/^Migration status: (active|completed|failed|cancelled)$/im) {
338 if ($stat ne $lstat) {
339 if ($ms eq 'active') {
340 my ($trans, $rem, $total) = (0, 0, 0);
341 $trans = $1 if $stat =~ m/^transferred ram: (\d+) kbytes$/im;
342 $rem = $1 if $stat =~ m/^remaining ram: (\d+) kbytes$/im;
343 $total = $1 if $stat =~ m/^total ram: (\d+) kbytes$/im;
345 $self->log('info', "migration status
: $ms (transferred
${trans
}KB
, " .
346 "remaining
${rem
}KB
), total
${total
}KB
)");
348 $self->log('info', "migration status
: $ms");
352 if ($ms eq 'completed') {
353 my $delay = time() - $start;
355 my $mbps = sprintf "%.2f", $conf->{memory}/$delay;
356 $self->log('info', "migration speed
: $mbps MB
/s
");
360 if ($ms eq 'failed' || $ms eq 'cancelled') {
364 last if $ms ne 'active';
367 die "unable to parse migration status
'$stat' - aborting
\n";
374 my ($self, $vmid) = @_;
376 my $volids = $self->{volumes};
378 # destroy local copies
379 foreach my $volid (@$volids) {
380 eval { PVE::Storage::vdisk_free($self->{storecfg}, $volid); };
382 $self->log('err', "removing
local copy of
'$volid' failed
- $err");
384 last if $err =~ /^interrupted by signal$/;
390 my ($self, $vmid, $err) = @_;
392 my $conf = $self->{vmconf};
394 # always stop local VM
395 eval { PVE::QemuServer::vm_stop($self->{storecfg}, $vmid, 1, 1); };
397 $self->log('err', "stopping vm failed
- $err");
401 if ($self->{tunnel}) {
402 eval { finish_tunnel($self, $self->{tunnel}); };
404 $self->log('err', $err);
409 # always deactivate volumes - avoid lvm LVs to be active on several nodes
411 my $vollist = PVE::QemuServer::get_vm_volumes($conf);
412 PVE::Storage::deactivate_volumes($self->{storecfg}, $vollist);
415 $self->log('err', $err);
420 my $cmd = [ @{$self->{rem_ssh}}, 'qm', 'unlock', $vmid ];
421 $self->cmd_logerr($cmd, errmsg => "failed to clear migrate
lock");
425 my ($self, $vmid) = @_;