my $parse_restore_archive = sub {
my ($storecfg, $archive) = @_;
- my ($archive_storeid, $archive_volname) = PVE::Storage::parse_volume_id($archive);
+ my ($archive_storeid, $archive_volname) = PVE::Storage::parse_volume_id($archive, 1);
if (defined($archive_storeid)) {
my $scfg = PVE::Storage::storage_config($storecfg, $archive_storeid);
optional => 1,
},
machine => get_standard_option('pve-qemu-machine'),
- targetstorage => {
- description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
- type => 'string',
- optional => 1
- },
+ targetstorage => get_standard_option('pve-targetstorage'),
timeout => {
description => "Wait maximal timeout seconds.",
type => 'integer',
my $migration_network = $get_root_param->('migration_network');
my $targetstorage = $get_root_param->('targetstorage');
- raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
- if $targetstorage && !$migratedfrom;
+ my $storagemap;
+
+ if ($targetstorage) {
+ raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
+ if !$migratedfrom;
+ $storagemap = eval { PVE::JSONSchema::parse_idmap($targetstorage, 'pve-storage-id') };
+ raise_param_exc({ targetstorage => "failed to parse targetstorage map: $@" })
+ if $@;
+ }
# read spice ticket from STDIN
my $spice_ticket;
+ my $nbd_protocol_version = 0;
+ my $replicated_volumes = {};
if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
- if (defined(my $line = <STDIN>)) {
+ while (defined(my $line = <STDIN>)) {
chomp $line;
- $spice_ticket = $line;
+ if ($line =~ m/^spice_ticket: (.+)$/) {
+ $spice_ticket = $1;
+ } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
+ $nbd_protocol_version = $1;
+ } elsif ($line =~ m/^replicated_volume: (.*)$/) {
+ $replicated_volumes->{$1} = 1;
+ } else {
+ # fallback for old source node
+ $spice_ticket = $line;
+ }
}
}
syslog('info', "start VM $vmid: $upid\n");
- PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine,
- $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout);
+ my $migrate_opts = {
+ migratedfrom => $migratedfrom,
+ spice_ticket => $spice_ticket,
+ network => $migration_network,
+ type => $migration_type,
+ storagemap => $storagemap,
+ nbd_proto_version => $nbd_protocol_version,
+ replicated_volumes => $replicated_volumes,
+ };
+
+ my $params = {
+ statefile => $stateuri,
+ skiplock => $skiplock,
+ forcemachine => $machine,
+ timeout => $timeout,
+ };
+
+ PVE::QemuServer::vm_start($storecfg, $vmid, $params, $migrate_opts);
return;
};
PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
} else {
my $storecfg = PVE::Storage::config();
- PVE::QemuServer::vm_start($storecfg, $vmid, undef, $skiplock);
+ PVE::QemuServer::vm_start($storecfg, $vmid, { skiplock => $skiplock });
}
return;
foreach my $opt (keys %$drives) {
my $drive = $drives->{$opt};
my $skipcomplete = ($total_jobs != $i); # finish after last drive
+ my $completion = $skipcomplete ? 'skip' : 'complete';
my $src_sid = PVE::Storage::parse_volume_id($drive->{file});
my $storage_list = [ $src_sid ];
my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
$newid, $storage, $format, $fullclone->{$opt}, $newvollist,
- $jobs, $skipcomplete, $oldconf->{agent}, $clonelimit);
+ $jobs, $completion, $oldconf->{agent}, $clonelimit, $oldconf);
$newconf->{$opt} = PVE::QemuServer::print_drive($newdrive);
my $movelimit = PVE::Storage::get_bandwidth_limit('move', [$oldstoreid, $storeid], $bwlimit);
my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
- $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
+ $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
$conf->{$disk} = PVE::QemuServer::print_drive($newdrive);
description => "Enable live storage migration for local disk",
optional => 1,
},
- targetstorage => get_standard_option('pve-storage-id', {
- description => "Default target storage.",
- optional => 1,
+ targetstorage => get_standard_option('pve-targetstorage', {
completion => \&PVE::QemuServer::complete_migration_storage,
}),
bwlimit => {
my $storecfg = PVE::Storage::config();
- if( $param->{targetstorage}) {
- PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
+ if (my $targetstorage = $param->{targetstorage}) {
+ my $check_storage = sub {
+ my ($target_sid) = @_;
+ PVE::Storage::storage_check_node($storecfg, $target_sid, $target);
+ $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
+ my $scfg = PVE::Storage::storage_config($storecfg, $target_sid);
+ raise_param_exc({ targetstorage => "storage '$target_sid' does not support vm images"})
+ if !$scfg->{content}->{images};
+ };
+
+ my $storagemap = eval { PVE::JSONSchema::parse_idmap($targetstorage, 'pve-storage-id') };
+ raise_param_exc({ targetstorage => "failed to parse targetstorage map: $@" })
+ if $@;
+
+ $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
+ if !defined($storagemap->{identity});
+
+ foreach my $source (values %{$storagemap->{entries}}) {
+ $check_storage->($source);
+ }
+
+ $check_storage->($storagemap->{default})
+ if $storagemap->{default};
+
+ PVE::QemuServer::check_storage_availability($storecfg, $conf, $target)
+ if $storagemap->{identity};
+
+ $param->{storagemap} = $storagemap;
} else {
PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
}