Signed-off-by: Alexandre Derumier <aderum...@odiso.com>
---
 PVE/Makefile               |   1 +
 PVE/QemuMigrateExternal.pm | 340 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 341 insertions(+)
 create mode 100644 PVE/QemuMigrateExternal.pm

diff --git a/PVE/Makefile b/PVE/Makefile
index 2c800f6..0494cfb 100644
--- a/PVE/Makefile
+++ b/PVE/Makefile
@@ -1,6 +1,7 @@
 PERLSOURCE =                   \
        QemuServer.pm           \
        QemuMigrate.pm          \
+       QemuMigrateExternal.pm  \
        QMPClient.pm            \
        QemuConfig.pm
 
diff --git a/PVE/QemuMigrateExternal.pm b/PVE/QemuMigrateExternal.pm
new file mode 100644
index 0000000..013ee4b
--- /dev/null
+++ b/PVE/QemuMigrateExternal.pm
@@ -0,0 +1,340 @@
+package PVE::QemuMigrateExternal;
+
+use strict;
+use warnings;
+use IO::File;
+use IPC::Open2;
+use POSIX qw( WNOHANG );
+use PVE::INotify;
+use PVE::Tools;
+use PVE::Cluster;
+use PVE::Storage;
+use PVE::QemuServer;
+use Time::HiRes qw( usleep );
+use PVE::RPCEnvironment;
+use Storable qw(dclone);
+
+use base qw(PVE::QemuMigrate);
+
+sub prepare {
+    my ($self, $vmid) = @_;
+
+    my $online = $self->{opts}->{online};
+
+    $self->{storecfg} = PVE::Storage::config();
+
+    # test if VM exists
+    my $conf = $self->{vmconf} = PVE::QemuConfig->load_config($vmid);
+
+    PVE::QemuConfig->check_lock($conf);
+
+    my $running = 0;
+    if (my $pid = PVE::QemuServer::check_running($vmid)) {
+       die "can't migrate running VM without --online\n" if !$online;
+       $running = $pid;
+
+       $self->{forcemachine} = PVE::QemuServer::qemu_machine_pxe($vmid, $conf);
+
+    }
+
+    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");
+       }
+    }
+
+    # test ssh connection
+    push @{$self->{rem_ssh}}, '-i', $self->{opts}->{migration_external_sshkey};
+    my $cmd = [ @{$self->{rem_ssh}}, '/bin/true' ];
+
+    eval { $self->cmd_quiet($cmd); };
+    die "Can't connect to destination address using public key\n" if $@;
+
+    find_targetvmid($self);
+
+    generate_createvm_cmd($self, $conf);
+        
+    return 1;
+
+}
+
+sub phase1 {
+    my ($self, $vmid) = @_;
+
+    $self->log('info', "starting migration of VM $vmid to node '$self->{node}' 
($self->{nodeip})");
+
+    my $conf = $self->{vmconf};
+
+    # set migrate lock in config file
+    $conf->{lock} = 'migrate';
+    PVE::QemuConfig->write_config($vmid, $conf);
+
+    my $cmd = $self->{createcmd};
+    
+    # start create vm
+    eval{ PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {}) 
};
+    if (my $err = $@) {
+       $self->log('err', $err);
+       $self->{errors} = 1;
+       die $err;
+    }
+}
+
+sub phase1_cleanup {
+    my ($self, $vmid, $err) = @_;
+
+    $self->log('info', "aborting phase 1 - cleanup resources");
+
+       my $targetvmid = $self->{opts}->{targetvmid} ? 
$self->{opts}->{targetvmid} : $vmid;
+       
+    unlock_vm($self, $vmid);
+
+    cleanup_remote_vm($self, $targetvmid);
+}
+
+
+sub phase2 {
+    my ($self, $vmid) = @_;
+
+    my $targetvmid = $self->{opts}->{targetvmid} ? $self->{opts}->{targetvmid} 
: $vmid;
+
+    my $nodename = PVE::INotify::nodename();
+        
+    $self->log('info', "starting VM $vmid on remote node '$self->{node}'");
+
+    my $migration_type = 'secure';
+
+    my $cmd = generate_migrate_start_cmd($self, $targetvmid, $nodename, 
$migration_type);
+    
+    my ($raddr, $rport, $ruri, $spice_port, $spice_ticket) = 
PVE::QemuMigrate::find_remote_ports($self, $targetvmid, $cmd);
+
+    PVE::QemuMigrate::start_remote_tunnel($self, $nodename, $migration_type, 
$raddr, $rport, $ruri);
+
+    livemigrate_storage($self, $vmid);
+    
+    PVE::QemuMigrate::livemigrate($self, $vmid, $ruri, $spice_port);
+    
+}
+
+sub phase2_cleanup {
+    my ($self, $vmid, $err) = @_;
+
+    return if !$self->{errors};
+
+    my $targetvmid = $self->{opts}->{targetvmid} ? $self->{opts}->{targetvmid} 
: $vmid;
+
+    $self->{phase2errors} = 1;
+
+    $self->log('info', "aborting phase 2 - cleanup resources");
+
+    PVE::QemuMigrate::cancel_migrate($self, $vmid);
+
+    PVE::QemuMigrate::unlock_vm($self, $vmid);
+
+    cleanup_remote_vm($self, $targetvmid);
+
+    if ($self->{tunnel}) {
+       eval { PVE::QemuMigrate::finish_tunnel($self, $self->{tunnel});  };
+       if (my $err = $@) {
+           $self->log('err', $err);
+           $self->{errors} = 1;
+       }
+    }
+}
+
+
+sub phase3_cleanup {
+    my ($self, $vmid, $err) = @_;
+
+    return if $self->{phase2errors};
+        
+    my $targetvmid = $self->{opts}->{targetvmid} ? $self->{opts}->{targetvmid} 
: $vmid;
+
+    finish_block_jobs($self, $vmid);
+        
+    PVE::QemuMigrate::finish_livemigration($self, $targetvmid);
+    
+    PVE::QemuMigrate::finish_spice_migration($self, $vmid);
+
+    PVE::QemuMigrate::stop_local_vm($self, $vmid);
+
+    # clear migrate lock
+    my $cmd = [ @{$self->{rem_ssh}}, 'qm', 'unlock', $targetvmid ];
+    $self->cmd_logerr($cmd, errmsg => "failed to clear migrate lock");
+}
+
+sub find_targetvmid {
+    my ($self) = @_;
+    
+    if (!$self->{opts}->{targetvmid}) {
+       #get remote nextvmid
+       eval {
+           my $cmd = [@{$self->{rem_ssh}}, 'pvesh', 'get', '/cluster/nextid'];
+           PVE::Tools::run_command($cmd, outfunc => sub {
+               my $line = shift;
+               if ($line =~ m/^(\d+)/) {
+                   $self->{opts}->{targetvmid} = $line;
+               }
+           });
+       };
+       if (my $err = $@) {
+           $self->log('err', $err);
+           $self->{errors} = 1;
+           die $err;
+       }
+
+       die "can't find the next free vmid on remote cluster\n" if 
!$self->{opts}->{targetvmid};
+    }
+
+}
+
+sub generate_createvm_cmd {
+    my ($self, $conf) = @_;
+    
+    my $cmd = [@{$self->{rem_ssh}}, 'qm', 'create', 
$self->{opts}->{targetvmid}];
+
+    my $target_conf = dclone $conf;
+
+    foreach my $opt (keys %{$target_conf}) {
+       next if $opt =~ m/^(pending|snapshots|digest|parent)/;
+       next if $opt =~ m/^(ide|scsi|virtio)(\d+)/;
+
+       if ($opt =~ m/^(net)(\d+)/ && $self->{opts}->{$opt}) {
+           my $oldnet = PVE::QemuServer::parse_net($target_conf->{$opt});
+           my $newnet = PVE::QemuServer::parse_net($self->{opts}->{$opt});
+           foreach my $newnet_opt (keys %$newnet) {
+               next if $newnet_opt =~ m/^(model|macaddr|queues)$/;
+               $oldnet->{$newnet_opt} = $newnet->{$newnet_opt};
+           }
+           $target_conf->{$opt} = PVE::QemuServer::print_net($oldnet);
+       }
+
+       die "can't migrate unused disk. please remove it before migrate\n" if 
$opt =~ m/^(unused)(\d+)/;
+       push @$cmd , "-$opt", PVE::Tools::shellquote($target_conf->{$opt});
+    }
+
+    PVE::QemuServer::foreach_drive($target_conf, sub {
+       my ($ds, $drive) = @_;
+
+       if (PVE::QemuServer::drive_is_cdrom($drive, 1)) {
+           push @$cmd , "-$ds", PVE::Tools::shellquote($target_conf->{$ds});
+           return;
+       }
+
+       my $volid = $drive->{file};
+       return if !$volid;
+
+       my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+       return if !$sid;
+       my $size = PVE::Storage::volume_size_info($self->{storecfg}, $volid, 5);
+       die "can't get size\n" if !$size;
+       $size = $size/1024/1024/1024;
+       my $targetsid = $self->{opts}->{targetstorage} ? 
$self->{opts}->{targetstorage} : $sid;
+
+       my $data = { %$drive };
+       delete $data->{$_} for qw(index interface file size);
+       my $drive_conf = "$targetsid:$size";
+       foreach my $drive_opt (keys %{$data}) {
+           $drive_conf .= ",$drive_opt=$data->{$drive_opt}";
+       }
+
+       push @$cmd , "-$ds", PVE::Tools::shellquote($drive_conf);
+    });
+
+    push @$cmd , '-lock', 'migrate';
+
+    $self->{createcmd} = $cmd;
+}
+
+sub generate_migrate_start_cmd {
+    my ($self, $vmid, $nodename, $migration_type) = @_;
+
+    my $cmd = [@{$self->{rem_ssh}}];
+
+    push @$cmd , 'qm', 'start', $vmid, '--skiplock';
+
+    push @$cmd, '--external_migration';
+        
+    push @$cmd, '--migration_type', $migration_type;
+
+    push @$cmd, '--migration_network', $self->{opts}->{migration_network}
+      if $self->{opts}->{migration_network};
+
+    if ($migration_type eq 'insecure') {
+       push @$cmd, '--stateuri', 'tcp';
+    } else {
+       push @$cmd, '--stateuri', 'unix';
+    }
+
+    if ($self->{forcemachine}) {
+       push @$cmd, '--machine', $self->{forcemachine};
+    }
+
+    if ($self->{online_local_volumes}) {
+       push @$cmd, '--targetstorage', ($self->{opts}->{targetstorage} // '1');
+    }
+    
+    return $cmd;
+}
+
+sub livemigrate_storage {
+    my ($self, $vmid) = @_;
+
+    my $conf = $self->{vmconf};
+
+       $self->{storage_migration} = 1;
+       $self->{storage_migration_jobs} = {};
+       $self->log('info', "starting storage migration");
+       foreach my $drive (keys %{$self->{target_drive}}){
+           my $target = $self->{target_drive}->{$drive};
+           my $nbd_uri = $target->{nbd_uri};
+           #bandwith, source only
+           my $source_sid = 
PVE::Storage::Plugin::parse_volume_id($conf->{$drive});
+           my $bwlimit = PVE::Storage::get_bandwidth_limit('migrate', 
[$source_sid, undef], $self->{opts}->{bwlimit});
+           $self->log('info', "$drive: start migration to $nbd_uri");
+           PVE::QemuServer::qemu_drive_mirror($vmid, $drive, $nbd_uri, $vmid, 
undef, $self->{storage_migration_jobs}, 1, undef, $bwlimit);
+       }
+
+}
+
+sub finish_block_jobs {
+    my ($self, $vmid) = @_;
+
+    my $conf = $self->{vmconf};
+
+    if ($self->{storage_migration}) {
+       # finish block-job
+       eval { PVE::QemuServer::qemu_drive_mirror_monitor($vmid, undef, 
$self->{storage_migration_jobs}); };
+
+       if (my $err = $@) {
+           eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, 
$self->{storage_migration_jobs}) };
+           eval { PVE::QemuMigrate::cleanup_remotedisks($self) };
+           die "Failed to completed storage migration\n";
+       }
+    }    
+}
+
+
+sub cleanup_remote_vm {
+    my ($self, $vmid) = @_;
+    
+    my $cmd = [@{$self->{rem_ssh}}, 'qm', 'stop', $vmid, '--skiplock'];
+
+    eval{ PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {}) 
};
+    if (my $err = $@) {
+        $self->log('err', $err);
+        $self->{errors} = 1;
+    }
+
+    $cmd = [@{$self->{rem_ssh}}, 'qm', 'destroy', $vmid, '--skiplock'];
+
+    eval{ PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {}) 
};
+    if (my $err = $@) {
+       $self->log('err', $err);
+       $self->{errors} = 1;
+    }
+}
+
+1;
-- 
2.11.0

_______________________________________________
pve-devel mailing list
pve-devel@pve.proxmox.com
https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to