use PVE::GuestHelpers;
use PVE::QemuConfig;
use PVE::QemuServer;
+use PVE::QemuServer::Drive;
use PVE::QemuServer::Monitor qw(mon_cmd);
use PVE::QemuMigrate;
use PVE::RPCEnvironment;
return $res;
}});
+my $parse_restore_archive = sub {
+ my ($storecfg, $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);
+ if ($scfg->{type} eq 'pbs') {
+ return {
+ type => 'pbs',
+ volid => $archive,
+ };
+ }
+ }
+ my $path = PVE::Storage::abs_filesystem_path($storecfg, $archive);
+ return {
+ type => 'file',
+ path => $path,
+ };
+};
__PACKAGE__->register_method({
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }),
archive => {
- description => "The backup file.",
+ description => "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
type => 'string',
optional => 1,
maxLength => 255,
if ($archive eq '-') {
die "pipe requires cli environment\n"
if $rpcenv->{type} ne 'cli';
+ $archive = { type => 'pipe' };
} else {
PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $archive);
- $archive = PVE::Storage::abs_filesystem_path($storecfg, $archive);
+
+ $archive = $parse_restore_archive->($storecfg, $archive);
}
}
die "$emsg vm is running\n" if PVE::QemuServer::check_running($vmid);
my $realcmd = sub {
- PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
+ my $restore_options = {
storage => $storage,
pool => $pool,
unique => $unique,
bwlimit => $bwlimit,
- });
+ };
+ if ($archive->{type} eq 'file' || $archive->{type} eq 'pipe') {
+ PVE::QemuServer::restore_file_archive($archive->{path} // '-', $vmid, $authuser, $restore_options);
+ } elsif ($archive->{type} eq 'pbs') {
+ PVE::QemuServer::restore_proxmox_backup_archive($archive->{volid}, $vmid, $authuser, $restore_options);
+ } else {
+ die "unknown backup archive type\n";
+ }
my $restored_conf = PVE::QemuConfig->load_config($vmid);
# Convert restored VM to template if backup was VM template
if (PVE::QemuConfig->is_template($restored_conf)) {
$vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
if (!$conf->{bootdisk}) {
- my $firstdisk = PVE::QemuServer::resolve_first_disk($conf);
+ my $firstdisk = PVE::QemuServer::Drive::resolve_first_disk($conf);
$conf->{bootdisk} = $firstdisk if $firstdisk;
}
path => '{vmid}/config',
method => 'GET',
proxyto => 'node',
- description => "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
+ description => "Get the virtual machine configuration with pending configuration " .
+ "changes applied. Set the 'current' parameter to get the current configuration instead.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
},
},
},
returns => {
- description => "The current VM configuration.",
+ description => "The VM configuration.",
type => "object",
properties => PVE::QemuServer::json_config_properties({
digest => {
path => '{vmid}/pending',
method => 'GET',
proxyto => 'node',
- description => "Get virtual machine configuration, including pending changes.",
+ description => "Get the virtual machine configuration with both current and pending values.",
permissions => {
check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
},
my $conf = PVE::QemuConfig->load_config($vmid);
my $storecfg = PVE::Storage::config();
PVE::QemuConfig->check_protection($conf, "can't remove VM $vmid");
- die "unable to remove VM $vmid - used in HA resources\n"
- if PVE::HA::Config::vm_is_ha_managed($vmid);
+
+ my $ha_managed = PVE::HA::Config::service_is_configured("vm:$vmid");
if (!$param->{purge}) {
+ die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
+ if $ha_managed;
# don't allow destroy if with replication jobs but no purge param
my $repl_conf = PVE::ReplicationConfig->new();
$repl_conf->check_for_existing_jobs($vmid);
PVE::AccessControl::remove_vm_access($vmid);
PVE::Firewall::remove_vmfw_conf($vmid);
if ($param->{purge}) {
+ print "purging VM $vmid from related configurations..\n";
PVE::ReplicationConfig::remove_vmid_jobs($vmid);
PVE::VZDump::Plugin::remove_vmid_from_backup_jobs($vmid);
+
+ if ($ha_managed) {
+ PVE::HA::Config::delete_service_from_config("vm:$vmid");
+ print "NOTE: removed VM $vmid from HA resource configuration.\n";
+ }
}
# only now remove the zombie config, else we can have reuse race
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',
+ minimum => 0,
+ default => 'max(30, vm memory in GiB)',
+ optional => 1,
+ },
},
},
returns => {
my $node = extract_param($param, 'node');
my $vmid = extract_param($param, 'vmid');
+ my $timeout = extract_param($param, 'timeout');
my $machine = extract_param($param, 'machine');
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);
+ 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;
my $localnode = PVE::INotify::nodename();
- if ($target && ($target eq $localnode || $target eq 'localhost')) {
+ if ($target && ($target eq $localnode || $target eq 'localhost')) {
undef $target;
- } else {
- PVE::Cluster::check_node_exists($target);
}
+ PVE::Cluster::check_node_exists($target) if $target;
+
my $storecfg = PVE::Storage::config();
if ($storage) {
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);
PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
};
if (my $err = $@) {
- unlink $conffile;
-
eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
sleep 1; # some storage like rbd need to wait before release volume - really?
PVE::Firewall::remove_vmfw_conf($newid);
+ unlink $conffile; # avoid races -> last thing before die
+
die "clone failed: $err";
}
disk => {
type => 'string',
description => "The disk you want to move.",
- enum => [ PVE::QemuServer::valid_drive_names() ],
+ enum => [PVE::QemuServer::Drive::valid_drive_names()],
},
storage => get_standard_option('pve-storage-id', {
description => "Target storage.",
(!$format || !$oldfmt || $oldfmt eq $format);
# this only checks snapshots because $disk is passed!
- my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
+ my $snapshotted = PVE::QemuServer::Drive::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
die "you can't move a disk with snapshots and delete the source\n"
if $snapshotted && $param->{delete};
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);
}
disk => {
type => 'string',
description => "The disk you want to resize.",
- enum => [PVE::QemuServer::valid_drive_names()],
+ enum => [PVE::QemuServer::Drive::valid_drive_names()],
},
size => {
type => 'string',
PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
- $drive->{size} = $newsize;
+ my $effective_size = eval { PVE::Storage::volume_size_info($storecfg, $volid, 3); };
+ $drive->{size} = $effective_size // $newsize;
$conf->{$disk} = PVE::QemuServer::print_drive($drive);
PVE::QemuConfig->write_config($vmid, $conf);
proxyto => 'node',
description => "Get snapshot configuration",
permissions => {
- check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
+ check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any => 1],
},
parameters => {
additionalProperties => 0,
optional => 1,
type => 'string',
description => "If you want to convert only 1 disk to base image.",
- enum => [PVE::QemuServer::valid_drive_names()],
+ enum => [PVE::QemuServer::Drive::valid_drive_names()],
},
},