2011-09-14 12:02:08 +02:00
package PVE::QemuMigrate ;
2011-09-09 12:10:29 +02:00
2011-08-23 07:47:04 +02:00
use strict ;
2011-09-14 12:02:08 +02:00
use warnings ;
2011-12-07 06:36:20 +01:00
use PVE::AbstractMigrate ;
2011-09-14 12:02:08 +02:00
use IO::File ;
2011-08-23 07:47:04 +02:00
use IPC::Open2 ;
2011-09-14 12:02:08 +02:00
use PVE::INotify ;
2013-05-13 07:30:50 +02:00
use PVE::Tools ;
2011-09-14 12:02:08 +02:00
use PVE::Cluster ;
2011-08-23 07:47:04 +02:00
use PVE::Storage ;
2011-09-14 12:02:08 +02:00
use PVE::QemuServer ;
2012-08-21 12:21:54 +02:00
use Time::HiRes qw( usleep ) ;
2013-07-24 09:52:33 +02:00
use PVE::RPCEnvironment ;
2011-08-23 07:47:04 +02:00
2011-12-07 06:36:20 +01:00
use base qw( PVE::AbstractMigrate ) ;
2011-08-23 07:47:04 +02:00
2011-09-09 12:10:29 +02:00
sub fork_command_pipe {
2011-12-07 11:25:20 +01:00
my ( $ self , $ cmd ) = @ _ ;
2011-09-12 12:26:00 +02:00
2011-09-09 12:10:29 +02:00
my $ reader = IO::File - > new ( ) ;
my $ writer = IO::File - > new ( ) ;
my $ orig_pid = $$ ;
my $ cpid ;
eval { $ cpid = open2 ( $ reader , $ writer , @$ cmd ) ; } ;
my $ err = $@ ;
# catch exec errors
if ( $ orig_pid != $$ ) {
2011-12-07 11:25:20 +01:00
$ self - > log ( 'err' , "can't fork command pipe\n" ) ;
2011-09-12 12:26:00 +02:00
POSIX:: _exit ( 1 ) ;
kill ( 'KILL' , $$ ) ;
2011-09-09 12:10:29 +02:00
}
die $ err if $ err ;
return { writer = > $ writer , reader = > $ reader , pid = > $ cpid } ;
}
2011-09-12 12:26:00 +02:00
sub finish_command_pipe {
2012-01-17 11:25:44 +01:00
my ( $ self , $ cmdpipe , $ timeout ) = @ _ ;
2011-09-09 12:10:29 +02:00
my $ writer = $ cmdpipe - > { writer } ;
my $ reader = $ cmdpipe - > { reader } ;
$ writer - > close ( ) ;
$ reader - > close ( ) ;
my $ cpid = $ cmdpipe - > { pid } ;
2012-01-17 11:25:44 +01:00
if ( $ timeout ) {
for ( my $ i = 0 ; $ i < $ timeout ; $ i + + ) {
return if ! PVE::ProcFSTools:: check_process_running ( $ cpid ) ;
sleep ( 1 ) ;
}
}
$ self - > log ( 'info' , "ssh tunnel still running - terminating now with SIGTERM\n" ) ;
kill ( 15 , $ cpid ) ;
2011-09-09 12:10:29 +02:00
2012-01-17 11:25:44 +01:00
# wait again
for ( my $ i = 0 ; $ i < 10 ; $ i + + ) {
return if ! PVE::ProcFSTools:: check_process_running ( $ cpid ) ;
sleep ( 1 ) ;
}
$ self - > log ( 'info' , "ssh tunnel still running - terminating now with SIGKILL\n" ) ;
kill 9 , $ cpid ;
sleep 1 ;
2011-09-09 12:10:29 +02:00
}
2011-08-23 07:47:04 +02:00
sub fork_tunnel {
2011-12-07 06:36:20 +01:00
my ( $ self , $ nodeip , $ lport , $ rport ) = @ _ ;
2011-08-23 07:47:04 +02:00
2011-12-07 06:36:20 +01:00
my $ cmd = [ @ { $ self - > { rem_ssh } } , '-L' , "$lport:localhost:$rport" ,
2011-08-23 07:47:04 +02:00
'qm' , 'mtunnel' ] ;
2011-09-12 12:26:00 +02:00
2011-12-07 11:25:20 +01:00
my $ tunnel = $ self - > fork_command_pipe ( $ cmd ) ;
2011-08-23 07:47:04 +02:00
my $ reader = $ tunnel - > { reader } ;
my $ helo ;
2011-09-12 12:26:00 +02:00
eval {
2011-12-15 11:29:01 +01:00
PVE::Tools:: run_with_timeout ( 60 , sub { $ helo = <$reader> ; } ) ;
2011-08-23 07:47:04 +02:00
die "no reply\n" if ! $ helo ;
2011-09-09 12:10:29 +02:00
die "no quorum on target node\n" if $ helo =~ m/^no quorum$/ ;
2011-09-12 12:26:00 +02:00
die "got strange reply from mtunnel ('$helo')\n"
2011-08-23 07:47:04 +02:00
if $ helo !~ m/^tunnel online$/ ;
} ;
my $ err = $@ ;
if ( $ err ) {
2011-12-07 11:25:20 +01:00
$ self - > finish_command_pipe ( $ tunnel ) ;
2011-08-23 07:47:04 +02:00
die "can't open migration tunnel - $err" ;
}
return $ tunnel ;
}
2011-09-12 12:26:00 +02:00
sub finish_tunnel {
2011-12-07 06:36:20 +01:00
my ( $ self , $ tunnel ) = @ _ ;
2011-08-23 07:47:04 +02:00
my $ writer = $ tunnel - > { writer } ;
2011-09-12 12:26:00 +02:00
eval {
2011-12-15 11:29:01 +01:00
PVE::Tools:: run_with_timeout ( 30 , sub {
2011-08-23 07:47:04 +02:00
print $ writer "quit\n" ;
$ writer - > flush ( ) ;
2011-09-12 12:26:00 +02:00
} ) ;
2011-08-23 07:47:04 +02:00
} ;
my $ err = $@ ;
2011-09-12 12:26:00 +02:00
2012-01-17 11:25:44 +01:00
$ self - > finish_command_pipe ( $ tunnel , 30 ) ;
2011-09-12 12:26:00 +02:00
2011-08-23 07:47:04 +02:00
die $ err if $ err ;
}
2011-12-07 06:36:20 +01:00
sub lock_vm {
my ( $ self , $ vmid , $ code , @ param ) = @ _ ;
2012-08-23 07:36:48 +02:00
2011-12-07 06:36:20 +01:00
return PVE::QemuServer:: lock_config ( $ vmid , $ code , @ param ) ;
}
2011-11-25 08:05:36 +01:00
2011-12-07 06:36:20 +01:00
sub prepare {
my ( $ self , $ vmid ) = @ _ ;
2011-11-25 08:05:36 +01:00
2011-12-07 06:36:20 +01:00
my $ online = $ self - > { opts } - > { online } ;
2011-09-14 12:02:08 +02:00
2011-12-07 06:36:20 +01:00
$ self - > { storecfg } = PVE::Storage:: config ( ) ;
2011-09-14 12:02:08 +02:00
2011-12-07 06:36:20 +01:00
# test is VM exist
my $ conf = $ self - > { vmconf } = PVE::QemuServer:: load_config ( $ vmid ) ;
2011-09-14 12:02:08 +02:00
2011-12-07 06:36:20 +01:00
PVE::QemuServer:: check_lock ( $ conf ) ;
2011-09-14 12:02:08 +02:00
2011-12-07 06:36:20 +01:00
my $ running = 0 ;
if ( my $ pid = PVE::QemuServer:: check_running ( $ vmid ) ) {
die "cant migrate running VM without --online\n" if ! $ online ;
$ running = $ pid ;
2013-06-05 10:24:39 +02:00
$ self - > { forcemachine } = PVE::QemuServer:: get_current_qemu_machine ( $ vmid ) ;
2011-09-14 12:02:08 +02:00
}
2011-12-07 06:36:20 +01:00
if ( my $ loc_res = PVE::QemuServer:: check_local_resources ( $ conf , 1 ) ) {
if ( $ self - > { running } || ! $ self - > { opts } - > { force } ) {
die "can't migrate VM which uses local devices\n" ;
} else {
$ self - > log ( 'info' , "migrating VM which uses local devices" ) ;
}
2011-09-14 12:02:08 +02:00
}
2011-11-25 08:05:36 +01:00
# activate volumes
my $ vollist = PVE::QemuServer:: get_vm_volumes ( $ conf ) ;
2011-12-07 06:36:20 +01:00
PVE::Storage:: activate_volumes ( $ self - > { storecfg } , $ vollist ) ;
# fixme: check if storage is available on both nodes
2011-09-14 12:02:08 +02:00
# test ssh connection
2011-12-07 06:36:20 +01:00
my $ cmd = [ @ { $ self - > { rem_ssh } } , '/bin/true' ] ;
eval { $ self - > cmd_quiet ( $ cmd ) ; } ;
2011-09-14 12:02:08 +02:00
die "Can't connect to destination address using public key\n" if $@ ;
2011-11-25 08:05:36 +01:00
2011-12-07 06:36:20 +01:00
return $ running ;
2011-09-14 12:02:08 +02:00
}
sub sync_disks {
2011-12-07 06:36:20 +01:00
my ( $ self , $ vmid ) = @ _ ;
$ self - > log ( 'info' , "copying disk images" ) ;
2011-09-14 12:02:08 +02:00
2011-12-07 06:36:20 +01:00
my $ conf = $ self - > { vmconf } ;
$ self - > { volumes } = [] ;
2011-09-14 12:02:08 +02:00
my $ res = [] ;
eval {
my $ volhash = { } ;
my $ cdromhash = { } ;
2012-09-25 07:26:34 +02:00
my $ sharedvm = 1 ;
2012-07-16 06:59:53 +02:00
my @ sids = PVE::Storage:: storage_ids ( $ self - > { storecfg } ) ;
foreach my $ storeid ( @ sids ) {
my $ scfg = PVE::Storage:: storage_config ( $ self - > { storecfg } , $ storeid ) ;
next if $ scfg - > { shared } ;
2012-07-16 10:19:11 +02:00
next if ! PVE::Storage:: storage_check_enabled ( $ self - > { storecfg } , $ storeid , undef , 1 ) ;
2012-07-13 15:12:41 +02:00
# get list from PVE::Storage (for unused volumes)
my $ dl = PVE::Storage:: vdisk_list ( $ self - > { storecfg } , $ storeid , $ vmid ) ;
PVE::Storage:: foreach_volid ( $ dl , sub {
my ( $ volid , $ sid , $ volname ) = @ _ ;
2012-07-16 10:19:11 +02:00
# check if storage is available on target node
2012-07-13 15:12:41 +02:00
PVE::Storage:: storage_check_node ( $ self - > { storecfg } , $ sid , $ self - > { node } ) ;
$ volhash - > { $ volid } = 1 ;
2012-09-25 07:26:34 +02:00
$ sharedvm = 0 ; # there is a non-shared disk
2012-07-13 15:12:41 +02:00
} ) ;
}
2011-09-14 12:02:08 +02:00
2012-09-25 07:42:01 +02:00
# and add used, owned/non-shared disks (just to be sure we have all)
2011-09-14 12:02:08 +02:00
2012-09-25 07:42:01 +02:00
PVE::QemuServer:: foreach_volid ( $ conf , sub {
my ( $ volid , $ is_cdrom ) = @ _ ;
2011-09-14 12:02:08 +02:00
return if ! $ volid ;
die "cant migrate local file/device '$volid'\n" if $ volid =~ m | ^ / | ;
2012-09-25 07:42:01 +02:00
if ( $ is_cdrom ) {
2011-09-14 12:02:08 +02:00
die "cant migrate local cdrom drive\n" if $ volid eq 'cdrom' ;
return if $ volid eq 'none' ;
$ cdromhash - > { $ volid } = 1 ;
}
my ( $ sid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
2011-12-07 06:36:20 +01:00
# check if storage is available on both nodes
my $ scfg = PVE::Storage:: storage_check_node ( $ self - > { storecfg } , $ sid ) ;
PVE::Storage:: storage_check_node ( $ self - > { storecfg } , $ sid , $ self - > { node } ) ;
2011-09-14 12:02:08 +02:00
return if $ scfg - > { shared } ;
die "can't migrate local cdrom '$volid'\n" if $ cdromhash - > { $ volid } ;
$ sharedvm = 0 ;
2011-12-07 06:36:20 +01:00
my ( $ path , $ owner ) = PVE::Storage:: path ( $ self - > { storecfg } , $ volid ) ;
2011-09-14 12:02:08 +02:00
die "can't migrate volume '$volid' - owned by other VM (owner = VM $owner)\n"
2011-12-07 06:36:20 +01:00
if ! $ owner || ( $ owner != $ self - > { vmid } ) ;
2011-09-14 12:02:08 +02:00
$ volhash - > { $ volid } = 1 ;
} ) ;
2011-12-07 06:36:20 +01:00
if ( $ self - > { running } && ! $ sharedvm ) {
2011-09-14 12:02:08 +02:00
die "can't do online migration - VM uses local disks\n" ;
}
# do some checks first
foreach my $ volid ( keys %$ volhash ) {
my ( $ sid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
2011-12-07 06:36:20 +01:00
my $ scfg = PVE::Storage:: storage_config ( $ self - > { storecfg } , $ sid ) ;
2011-09-14 12:02:08 +02:00
die "can't migrate '$volid' - storagy type '$scfg->{type}' not supported\n"
if $ scfg - > { type } ne 'dir' ;
2013-02-14 11:58:54 +01:00
2013-02-28 06:35:29 +01:00
# if file, check if a backing file exist
if ( ( $ scfg - > { type } eq 'dir' ) && ( ! $ sharedvm ) ) {
2013-02-14 11:58:54 +01:00
my ( undef , undef , undef , $ parent ) = PVE::Storage:: volume_size_info ( $ self - > { storecfg } , $ volid , 1 ) ;
2013-02-28 06:35:29 +01:00
die "can't migrate '$volid' as it's a clone of '$parent'" if $ parent ;
2013-02-14 11:58:54 +01:00
}
2011-09-14 12:02:08 +02:00
}
foreach my $ volid ( keys %$ volhash ) {
my ( $ sid , $ volname ) = PVE::Storage:: parse_volume_id ( $ volid ) ;
2011-12-07 06:36:20 +01:00
push @ { $ self - > { volumes } } , $ volid ;
PVE::Storage:: storage_migrate ( $ self - > { storecfg } , $ volid , $ self - > { nodeip } , $ sid ) ;
2011-09-14 12:02:08 +02:00
}
} ;
die "Failed to sync data - $@" if $@ ;
}
2011-08-23 07:47:04 +02:00
sub phase1 {
2011-12-07 06:36:20 +01:00
my ( $ self , $ vmid ) = @ _ ;
2011-08-23 07:47:04 +02:00
2011-12-07 06:36:20 +01:00
$ self - > log ( 'info' , "starting migration of VM $vmid to node '$self->{node}' ($self->{nodeip})" ) ;
2011-08-23 07:47:04 +02:00
2011-12-07 06:36:20 +01:00
my $ conf = $ self - > { vmconf } ;
2011-08-23 07:47:04 +02:00
# set migrate lock in config file
2012-02-02 14:01:08 +01:00
$ conf - > { lock } = 'migrate' ;
PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) ;
2011-08-23 07:47:04 +02:00
2011-12-07 06:36:20 +01:00
sync_disks ( $ self , $ vmid ) ;
2011-09-09 12:10:29 +02:00
2011-08-23 07:47:04 +02:00
} ;
2011-12-07 06:36:20 +01:00
sub phase1_cleanup {
my ( $ self , $ vmid , $ err ) = @ _ ;
$ self - > log ( 'info' , "aborting phase 1 - cleanup resources" ) ;
2012-02-02 14:01:08 +01:00
my $ conf = $ self - > { vmconf } ;
delete $ conf - > { lock } ;
eval { PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) } ;
2011-12-07 06:36:20 +01:00
if ( my $ err = $@ ) {
$ self - > log ( 'err' , $ err ) ;
}
2012-08-23 07:36:48 +02:00
2011-12-07 06:36:20 +01:00
if ( $ self - > { volumes } ) {
foreach my $ volid ( @ { $ self - > { volumes } } ) {
$ self - > log ( 'err' , "found stale volume copy '$volid' on node '$self->{node}'" ) ;
# fixme: try to remove ?
}
}
}
2011-08-23 07:47:04 +02:00
sub phase2 {
2011-12-07 06:36:20 +01:00
my ( $ self , $ vmid ) = @ _ ;
2011-08-23 07:47:04 +02:00
2011-12-07 06:36:20 +01:00
my $ conf = $ self - > { vmconf } ;
2011-12-07 11:25:20 +01:00
$ self - > log ( 'info' , "starting VM $vmid on remote node '$self->{node}'" ) ;
2011-08-23 07:47:04 +02:00
my $ rport ;
2012-08-21 12:21:51 +02:00
my $ nodename = PVE::INotify:: nodename ( ) ;
2011-09-12 12:26:00 +02:00
## start on remote node
2013-07-24 09:52:33 +02:00
my $ cmd = [ @ { $ self - > { rem_ssh } } ] ;
2013-07-24 12:13:16 +02:00
my $ spice_ticket ;
2013-07-24 11:42:48 +02:00
if ( PVE::QemuServer:: vga_conf_has_spice ( $ conf - > { vga } ) ) {
2013-07-24 09:52:33 +02:00
my $ res = PVE::QemuServer:: vm_mon_cmd ( $ vmid , 'query-spice' ) ;
2013-07-24 12:13:16 +02:00
$ spice_ticket = $ res - > { ticket } ;
2013-07-24 09:52:33 +02:00
}
push @$ cmd , 'qm' , 'start' , $ vmid , '--stateuri' , 'tcp' , '--skiplock' , '--migratedfrom' , $ nodename ;
2013-06-05 10:24:39 +02:00
if ( $ self - > { forcemachine } ) {
push @$ cmd , '--machine' , $ self - > { forcemachine } ;
}
2013-07-24 11:42:48 +02:00
my $ spice_port ;
2013-07-24 12:13:16 +02:00
# Note: We try to keep $spice_ticket secret (do not pass via command line parameter)
# instead we pipe it through STDIN
PVE::Tools:: run_command ( $ cmd , input = > $ spice_ticket , outfunc = > sub {
2011-08-23 07:47:04 +02:00
my $ line = shift ;
if ( $ line =~ m/^migration listens on port (\d+)$/ ) {
2013-07-24 11:42:48 +02:00
$ rport = int ( $ 1 ) ;
2013-07-24 09:52:33 +02:00
} elsif ( $ line =~ m/^spice listens on port (\d+)$/ ) {
2013-07-24 11:42:48 +02:00
$ spice_port = int ( $ 1 ) ;
2011-08-23 07:47:04 +02:00
}
2012-12-27 09:18:15 +01:00
} , errfunc = > sub {
my $ line = shift ;
$ self - > log ( 'info' , $ line ) ;
} ) ;
2011-08-23 07:47:04 +02:00
die "unable to detect remote migration port\n" if ! $ rport ;
2011-12-07 06:36:20 +01:00
$ self - > log ( 'info' , "starting migration tunnel" ) ;
2011-09-09 12:10:29 +02:00
2011-08-23 07:47:04 +02:00
## create tunnel to remote port
2013-05-13 07:30:50 +02:00
my $ lport = PVE::Tools:: next_migrate_port ( ) ;
2011-12-07 06:36:20 +01:00
$ self - > { tunnel } = $ self - > fork_tunnel ( $ self - > { nodeip } , $ lport , $ rport ) ;
2011-08-23 07:47:04 +02:00
2011-12-08 10:07:19 +01:00
$ self - > log ( 'info' , "starting online/live migration on port $lport" ) ;
2011-08-23 07:47:04 +02:00
# start migration
my $ start = time ( ) ;
2012-08-28 12:46:08 +02:00
2012-12-27 09:18:14 +01:00
# load_defaults
my $ defaults = PVE::QemuServer:: load_defaults ( ) ;
# always set migrate speed (overwrite kvm default of 32m)
# we set a very hight default of 8192m which is basically unlimited
my $ migrate_speed = $ defaults - > { migrate_speed } || 8192 ;
$ migrate_speed = $ conf - > { migrate_speed } || $ migrate_speed ;
$ migrate_speed = $ migrate_speed * 1048576 ;
$ self - > log ( 'info' , "migrate_set_speed: $migrate_speed" ) ;
eval {
PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "migrate_set_speed" , value = > int ( $ migrate_speed ) ) ;
} ;
$ self - > log ( 'info' , "migrate_set_speed error: $@" ) if $@ ;
my $ migrate_downtime = $ defaults - > { migrate_downtime } ;
$ migrate_downtime = $ conf - > { migrate_downtime } if defined ( $ conf - > { migrate_downtime } ) ;
if ( defined ( $ migrate_downtime ) ) {
$ self - > log ( 'info' , "migrate_set_downtime: $migrate_downtime" ) ;
eval {
2012-12-30 19:02:59 +01:00
PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "migrate_set_downtime" , value = > int ( $ migrate_downtime * 100 ) / 100 ) ;
2012-12-27 09:18:14 +01:00
} ;
$ self - > log ( 'info' , "migrate_set_downtime error: $@" ) if $@ ;
}
2012-08-28 12:46:08 +02:00
my $ capabilities = { } ;
$ capabilities - > { capability } = "xbzrle" ;
2012-08-31 11:02:47 +02:00
$ capabilities - > { state } = JSON:: false ;
2012-08-28 12:46:08 +02:00
eval {
PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "migrate-set-capabilities" , capabilities = > [ $ capabilities ] ) ;
} ;
#set cachesize 10% of the total memory
my $ cachesize = int ( $ conf - > { memory } * 1048576 / 10 ) ;
eval {
PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "migrate-set-cache-size" , value = > $ cachesize ) ;
} ;
2013-07-24 11:42:48 +02:00
if ( PVE::QemuServer:: vga_conf_has_spice ( $ conf - > { vga } ) ) {
2013-07-24 09:52:33 +02:00
my $ rpcenv = PVE::RPCEnvironment:: get ( ) ;
my $ authuser = $ rpcenv - > get_user ( ) ;
2013-07-24 11:42:48 +02:00
my ( undef , $ proxyticket ) = PVE::AccessControl:: assemble_spice_ticket ( $ authuser , $ vmid , $ self - > { node } ) ;
2013-07-24 09:52:33 +02:00
2013-07-24 11:42:48 +02:00
my $ filename = "/etc/pve/nodes/$self->{node}/pve-ssl.pem" ;
2013-07-24 09:52:33 +02:00
my $ subject = PVE::QemuServer:: read_x509_subject_spice ( $ filename ) ;
$ self - > log ( 'info' , "spice client_migrate_info" ) ;
eval {
2013-07-24 11:42:48 +02:00
PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "client_migrate_info" , protocol = > 'spice' ,
hostname = > $ proxyticket , 'tls-port' = > $ spice_port ,
'cert-subject' = > $ subject ) ;
2013-07-24 09:52:33 +02:00
} ;
$ self - > log ( 'info' , "client_migrate_info error: $@" ) if $@ ;
}
2012-06-25 10:02:57 +02:00
eval {
PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "migrate" , uri = > "tcp:localhost:$lport" ) ;
} ;
my $ merr = $@ ;
2011-08-23 07:47:04 +02:00
2012-07-13 12:37:19 +02:00
my $ lstat = 0 ;
2012-08-21 12:21:54 +02:00
my $ usleep = 2000000 ;
my $ i = 0 ;
2012-08-30 09:28:24 +02:00
my $ err_count = 0 ;
2012-12-30 19:02:59 +01:00
my $ lastrem = undef ;
my $ downtimecounter = 0 ;
2011-08-23 07:47:04 +02:00
while ( 1 ) {
2012-08-21 12:21:54 +02:00
$ i + + ;
my $ avglstat = $ lstat / $ i if $ lstat ;
2012-08-30 09:28:24 +02:00
usleep ( $ usleep ) ;
my $ stat ;
eval {
$ stat = PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "query-migrate" ) ;
} ;
if ( my $ err = $@ ) {
$ err_count + + ;
warn "query migrate failed: $err\n" ;
if ( $ err_count <= 5 ) {
usleep ( 1000000 ) ;
next ;
}
die "too many query migrate failures - aborting\n" ;
}
2012-06-25 10:02:57 +02:00
if ( $ stat - > { status } =~ m/^(active|completed|failed|cancelled)$/im ) {
2011-12-08 10:07:19 +01:00
$ merr = undef ;
2012-08-30 09:28:24 +02:00
$ err_count = 0 ;
2012-06-25 10:02:57 +02:00
if ( $ stat - > { status } eq 'completed' ) {
2011-08-23 07:47:04 +02:00
my $ delay = time ( ) - $ start ;
if ( $ delay > 0 ) {
my $ mbps = sprintf "%.2f" , $ conf - > { memory } / $ delay ;
2012-12-27 09:18:16 +01:00
my $ downtime = $ stat - > { downtime } || 0 ;
$ self - > log ( 'info' , "migration speed: $mbps MB/s - downtime $downtime ms" ) ;
2011-08-23 07:47:04 +02:00
}
}
2012-08-23 07:36:48 +02:00
2012-06-25 10:02:57 +02:00
if ( $ stat - > { status } eq 'failed' || $ stat - > { status } eq 'cancelled' ) {
2011-08-23 07:47:04 +02:00
die "aborting\n"
}
2012-07-13 12:37:19 +02:00
if ( $ stat - > { status } ne 'active' ) {
$ self - > log ( 'info' , "migration status: $stat->{status}" ) ;
last ;
}
if ( $ stat - > { ram } - > { transferred } ne $ lstat ) {
my $ trans = $ stat - > { ram } - > { transferred } || 0 ;
my $ rem = $ stat - > { ram } - > { remaining } || 0 ;
my $ total = $ stat - > { ram } - > { total } || 0 ;
2012-08-28 12:46:08 +02:00
my $ xbzrlecachesize = $ stat - > { "xbzrle-cache" } - > { "cache-size" } || 0 ;
my $ xbzrlebytes = $ stat - > { "xbzrle-cache" } - > { "bytes" } || 0 ;
my $ xbzrlepages = $ stat - > { "xbzrle-cache" } - > { "pages" } || 0 ;
my $ xbzrlecachemiss = $ stat - > { "xbzrle-cache" } - > { "cache-miss" } || 0 ;
my $ xbzrleoverflow = $ stat - > { "xbzrle-cache" } - > { "overflow" } || 0 ;
2012-08-21 12:21:54 +02:00
#reduce sleep if remainig memory if lower than the everage transfert
2012-08-30 12:15:07 +02:00
$ usleep = 300000 if $ avglstat && $ rem < $ avglstat ;
2012-07-13 12:37:19 +02:00
$ self - > log ( 'info' , "migration status: $stat->{status} (transferred ${trans}, " .
2013-02-13 10:47:54 +01:00
"remaining ${rem}), total ${total})" ) ;
2012-08-28 12:46:08 +02:00
2012-08-31 11:02:47 +02:00
#$self->log('info', "migration xbzrle cachesize: ${xbzrlecachesize} transferred ${xbzrlebytes} pages ${xbzrlepages} cachemiss ${xbzrlecachemiss} overflow ${xbzrleoverflow}");
2012-12-30 19:02:59 +01:00
if ( ( $ lastrem && $ rem > $ lastrem ) || ( $ rem == 0 ) ) {
$ downtimecounter + + ;
}
$ lastrem = $ rem ;
if ( $ downtimecounter > 5 ) {
$ downtimecounter = 0 ;
$ migrate_downtime *= 2 ;
$ self - > log ( 'info' , "migrate_set_downtime: $migrate_downtime" ) ;
eval {
PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "migrate_set_downtime" , value = > int ( $ migrate_downtime * 100 ) / 100 ) ;
} ;
$ self - > log ( 'info' , "migrate_set_downtime error: $@" ) if $@ ;
}
2012-07-13 12:37:19 +02:00
}
2012-12-30 19:02:59 +01:00
2012-07-13 12:37:19 +02:00
$ lstat = $ stat - > { ram } - > { transferred } ;
2012-08-21 12:21:54 +02:00
2011-08-23 07:47:04 +02:00
} else {
2011-12-08 10:07:19 +01:00
die $ merr if $ merr ;
2012-06-25 10:02:57 +02:00
die "unable to parse migration status '$stat->{status}' - aborting\n" ;
2011-08-23 07:47:04 +02:00
}
2012-07-13 12:37:19 +02:00
}
2011-08-23 07:47:04 +02:00
}
2011-12-07 06:36:20 +01:00
2012-08-21 12:21:50 +02:00
sub phase2_cleanup {
my ( $ self , $ vmid , $ err ) = @ _ ;
2012-08-23 10:28:41 +02:00
return if ! $ self - > { errors } ;
$ self - > { phase2errors } = 1 ;
2012-08-21 12:21:50 +02:00
$ self - > log ( 'info' , "aborting phase 2 - cleanup resources" ) ;
2013-01-01 22:59:55 +01:00
$ self - > log ( 'info' , "migrate_cancel" ) ;
eval {
PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , "migrate_cancel" ) ;
} ;
$ self - > log ( 'info' , "migrate_cancel error: $@" ) if $@ ;
2012-08-21 12:21:50 +02:00
my $ conf = $ self - > { vmconf } ;
delete $ conf - > { lock } ;
eval { PVE::QemuServer:: update_config_nolock ( $ vmid , $ conf , 1 ) } ;
if ( my $ err = $@ ) {
$ self - > log ( 'err' , $ err ) ;
}
2012-08-23 10:28:41 +02:00
# cleanup ressources on target host
my $ nodename = PVE::INotify:: nodename ( ) ;
my $ cmd = [ @ { $ self - > { rem_ssh } } , 'qm' , 'stop' , $ vmid , '--skiplock' , '--migratedfrom' , $ nodename ] ;
eval { PVE::Tools:: run_command ( $ cmd , outfunc = > sub { } , errfunc = > sub { } ) } ;
if ( my $ err = $@ ) {
$ self - > log ( 'err' , $ err ) ;
$ self - > { errors } = 1 ;
}
2012-08-21 12:21:50 +02:00
}
2011-12-07 06:36:20 +01:00
sub phase3 {
my ( $ self , $ vmid ) = @ _ ;
2012-08-23 07:36:48 +02:00
2011-12-07 06:36:20 +01:00
my $ volids = $ self - > { volumes } ;
2012-08-23 10:28:41 +02:00
return if $ self - > { phase2errors } ;
2011-12-07 06:36:20 +01:00
# destroy local copies
foreach my $ volid ( @$ volids ) {
eval { PVE::Storage:: vdisk_free ( $ self - > { storecfg } , $ volid ) ; } ;
if ( my $ err = $@ ) {
$ self - > log ( 'err' , "removing local copy of '$volid' failed - $err" ) ;
$ self - > { errors } = 1 ;
last if $ err =~ /^interrupted by signal$/ ;
}
}
}
sub phase3_cleanup {
my ( $ self , $ vmid , $ err ) = @ _ ;
my $ conf = $ self - > { vmconf } ;
2012-08-23 10:28:41 +02:00
return if $ self - > { phase2errors } ;
2011-12-07 06:36:20 +01:00
2012-08-21 12:21:49 +02:00
# move config to remote node
my $ conffile = PVE::QemuServer:: config_file ( $ vmid ) ;
my $ newconffile = PVE::QemuServer:: config_file ( $ vmid , $ self - > { node } ) ;
die "Failed to move config to node '$self->{node}' - rename failed: $!\n"
if ! rename ( $ conffile , $ newconffile ) ;
2012-08-23 07:36:48 +02:00
# now that config file is move, we can resume vm on target if livemigrate
2012-08-21 12:21:53 +02:00
if ( $ self - > { tunnel } ) {
my $ cmd = [ @ { $ self - > { rem_ssh } } , 'qm' , 'resume' , $ vmid , '--skiplock' ] ;
2013-02-24 08:44:06 +01:00
eval { PVE::Tools:: run_command ( $ cmd , outfunc = > sub { } ,
errfunc = > sub {
my $ line = shift ;
$ self - > log ( 'err' , $ line ) ;
} ) ;
} ;
2012-08-23 07:36:48 +02:00
if ( my $ err = $@ ) {
2012-08-21 12:21:53 +02:00
$ self - > log ( 'err' , $ err ) ;
$ self - > { errors } = 1 ;
}
}
2013-07-24 09:52:33 +02:00
my $ timer = 0 ;
2013-07-24 11:42:48 +02:00
if ( PVE::QemuServer:: vga_conf_has_spice ( $ conf - > { vga } ) ) {
2013-07-24 09:52:33 +02:00
$ self - > log ( 'info' , "Waiting for spice server migration" ) ;
while ( 1 ) {
my $ res = PVE::QemuServer:: vm_mon_cmd_nocheck ( $ vmid , 'query-spice' ) ;
last if int ( $ res - > { 'migrated' } ) == 1 ;
last if $ timer > 50 ;
$ timer + + ;
usleep ( 200000 ) ;
}
}
2011-12-07 06:36:20 +01:00
# always stop local VM
eval { PVE::QemuServer:: vm_stop ( $ self - > { storecfg } , $ vmid , 1 , 1 ) ; } ;
if ( my $ err = $@ ) {
$ self - > log ( 'err' , "stopping vm failed - $err" ) ;
$ self - > { errors } = 1 ;
}
2012-01-17 11:25:44 +01:00
if ( $ self - > { tunnel } ) {
eval { finish_tunnel ( $ self , $ self - > { tunnel } ) ; } ;
if ( my $ err = $@ ) {
$ self - > log ( 'err' , $ err ) ;
$ self - > { errors } = 1 ;
}
}
2011-12-07 06:36:20 +01:00
# always deactivate volumes - avoid lvm LVs to be active on several nodes
eval {
my $ vollist = PVE::QemuServer:: get_vm_volumes ( $ conf ) ;
PVE::Storage:: deactivate_volumes ( $ self - > { storecfg } , $ vollist ) ;
} ;
if ( my $ err = $@ ) {
$ self - > log ( 'err' , $ err ) ;
$ self - > { errors } = 1 ;
}
# clear migrate lock
my $ cmd = [ @ { $ self - > { rem_ssh } } , 'qm' , 'unlock' , $ vmid ] ;
$ self - > cmd_logerr ( $ cmd , errmsg = > "failed to clear migrate lock" ) ;
}
sub final_cleanup {
my ( $ self , $ vmid ) = @ _ ;
# nothing to do
}
1 ;