X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPI2%2FQemu.pm;h=d8d3f3e6b312b6fa7e4e6a357c167736c9473147;hb=7c4351f7d4334dca942c2a1b489480cfc84c48ae;hp=74104dfb0150383f03a3c1556a7052cf3af7125e;hpb=0a13e08ec2549d9f05377b53635c1f973b2afac2;p=qemu-server.git diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 74104df..d8d3f3e 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -20,6 +20,7 @@ use PVE::ReplicationConfig; 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; @@ -70,7 +71,7 @@ my $check_storage_access = sub { my $volid = $drive->{file}; my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1); - if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || $volname eq 'cloudinit')) { + if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) { # nothing to check } elsif ($isCDROM && ($volid eq 'cdrom')) { $rpcenv->check($authuser, "/", ['Sys.Console']); @@ -147,8 +148,8 @@ my $create_disks = sub { if (!$volid || $volid eq 'none' || $volid eq 'cdrom') { delete $disk->{size}; - $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); - } elsif ($volname eq 'cloudinit') { + $res->{$ds} = PVE::QemuServer::print_drive($disk); + } elsif (defined($volname) && $volname eq 'cloudinit') { $storeid = $storeid // $default_storage; die "no storage ID specified (and no default storage)\n" if !$storeid; my $scfg = PVE::Storage::storage_config($storecfg, $storeid); @@ -169,7 +170,7 @@ my $create_disks = sub { $disk->{media} = 'cdrom'; push @$vollist, $volid; delete $disk->{format}; # no longer needed - $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); + $res->{$ds} = PVE::QemuServer::print_drive($disk); } elsif ($volid =~ $NEW_DISK_RE) { my ($storeid, $size) = ($2 || $default_storage, $3); die "no storage ID specified (and no default storage)\n" if !$storeid; @@ -188,7 +189,7 @@ my $create_disks = sub { $disk->{file} = $volid; $disk->{size} = PVE::Tools::convert_size($size, 'kb' => 'b'); delete $disk->{format}; # no longer needed - $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); + $res->{$ds} = PVE::QemuServer::print_drive($disk); } else { PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid); @@ -206,12 +207,12 @@ my $create_disks = sub { my $size = PVE::Storage::volume_size_info($storecfg, $volid); - die "volume $volid does not exists\n" if !$size; + die "volume $volid does not exist\n" if !$size; $disk->{size} = $size; } - $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); + $res->{$ds} = PVE::QemuServer::print_drive($disk); } }; @@ -283,6 +284,7 @@ my $generaloptions = { 'startup' => 1, 'tdf' => 1, 'template' => 1, + 'tags' => 1, }; my $vmpoweroptions = { @@ -336,6 +338,10 @@ my $check_vm_modify_config_perm = sub { $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']); } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) { $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']); + } elsif ($opt eq 'vmstate') { + # the user needs Disk and PowerMgmt privileges to change the vmstate + # also needs privileges on the storage, that will be checked later + $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]); } else { # catches hostpci\d+, args, lock, etc. # new options will be checked here @@ -395,6 +401,26 @@ __PACKAGE__->register_method({ return $res; }}); +my $parse_restore_archive = sub { + my ($storecfg, $archive) = @_; + + my ($archive_storeid, $archive_volname) = PVE::Storage::parse_volume_id($archive); + + 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({ @@ -417,7 +443,7 @@ __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, @@ -523,7 +549,7 @@ __PACKAGE__->register_method({ raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive; PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); - $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive); + $param->{$opt} = PVE::QemuServer::print_drive($drive); } } @@ -535,9 +561,11 @@ __PACKAGE__->register_method({ 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); } } @@ -554,12 +582,19 @@ __PACKAGE__->register_method({ 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)) { @@ -569,17 +604,18 @@ __PACKAGE__->register_method({ } PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool; - - if ($start_after_create) { - eval { PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node }) }; - warn $@ if $@; - } }; # ensure no old replication state are exists PVE::ReplicationState::delete_guest_states($vmid); - return PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd); + PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd); + + if ($start_after_create) { + print "Execute autostart\n"; + eval { PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node }) }; + warn $@ if $@; + } }; my $createfn = sub { @@ -587,19 +623,15 @@ __PACKAGE__->register_method({ PVE::ReplicationState::delete_guest_states($vmid); my $realcmd = sub { - - my $vollist = []; - my $conf = $param; + my $arch = PVE::QemuServer::get_vm_arch($conf); - my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf); - + my $vollist = []; eval { - $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; } @@ -821,7 +853,8 @@ __PACKAGE__->register_method({ 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' ]], }, @@ -847,7 +880,7 @@ __PACKAGE__->register_method({ }, }, returns => { - description => "The current VM configuration.", + description => "The VM configuration.", type => "object", properties => PVE::QemuServer::json_config_properties({ digest => { @@ -879,7 +912,7 @@ __PACKAGE__->register_method({ 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' ]], }, @@ -1061,7 +1094,7 @@ my $update_vm_api = sub { raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive; PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); $check_replication->($drive); - $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive); + $param->{$opt} = PVE::QemuServer::print_drive($drive); } elsif ($opt =~ m/^net(\d+)$/) { # add macaddr my $net = PVE::QemuServer::parse_net($param->{$opt}); @@ -1089,6 +1122,15 @@ my $update_vm_api = sub { die "checksum missmatch (file change by other user?)\n" if $digest && $digest ne $conf->{digest}; + # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?! + if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) { + if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') { + delete $conf->{lock}; # for check lock check, not written out + push @delete, 'lock'; # this is the real deal to write it out + } + push @delete, 'runningmachine' if $conf->{runningmachine}; + } + PVE::QemuConfig->check_lock($conf) if !$skiplock; foreach my $opt (keys %$revert) { @@ -1139,6 +1181,12 @@ my $update_vm_api = sub { delete $conf->{$opt}; PVE::QemuConfig->write_config($vmid, $conf); } + } elsif ($opt eq 'vmstate') { + PVE::QemuConfig->check_protection($conf, "can't remove vmstate '$val'"); + if (PVE::QemuServer::try_deallocate_drive($storecfg, $vmid, $conf, $opt, { file => $val }, $rpcenv, $authuser, 1)) { + delete $conf->{$opt}; + PVE::QemuConfig->write_config($vmid, $conf); + } } elsif (PVE::QemuServer::is_valid_drivename($opt)) { PVE::QemuConfig->check_protection($conf, "can't remove drive '$opt'"); $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']); @@ -1173,7 +1221,7 @@ my $update_vm_api = sub { $conf = PVE::QemuConfig->load_config($vmid); # update/reload next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed - my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf); + my $arch = PVE::QemuServer::get_vm_arch($conf); if (PVE::QemuServer::is_valid_drivename($opt)) { my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}); @@ -1221,13 +1269,13 @@ my $update_vm_api = sub { $conf = PVE::QemuConfig->load_config($vmid); # update/reload + my $errors = {}; if ($running) { - my $errors = {}; PVE::QemuServer::vmconfig_hotplug_pending($vmid, $conf, $storecfg, $modified, $errors); - raise_param_exc($errors) if scalar(keys %$errors); } else { - PVE::QemuServer::vmconfig_apply_pending($vmid, $conf, $storecfg, $running); + PVE::QemuServer::vmconfig_apply_pending($vmid, $conf, $storecfg, $running, $errors); } + raise_param_exc($errors) if scalar(keys %$errors); return; }; @@ -1423,10 +1471,12 @@ __PACKAGE__->register_method({ 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); @@ -1449,8 +1499,14 @@ __PACKAGE__->register_method({ 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 @@ -1967,12 +2023,19 @@ __PACKAGE__->register_method({ description => "CIDR of the (sub) network that is used for migration.", optional => 1, }, - machine => get_standard_option('pve-qm-machine'), + 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 - } + }, + timeout => { + description => "Wait maximal timeout seconds.", + type => 'integer', + minimum => 0, + default => 'max(30, vm memory in GiB)', + optional => 1, + }, }, }, returns => { @@ -1986,6 +2049,7 @@ __PACKAGE__->register_method({ my $node = extract_param($param, 'node'); my $vmid = extract_param($param, 'vmid'); + my $timeout = extract_param($param, 'timeout'); my $machine = extract_param($param, 'machine'); @@ -2039,8 +2103,8 @@ __PACKAGE__->register_method({ 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); + PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine, + $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout); return; }; @@ -2368,6 +2432,9 @@ __PACKAGE__->register_method({ proxyto => 'node', description => "Suspend virtual machine.", permissions => { + description => "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',". + " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'". + " on the storage for the vmstate.", check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], }, parameters => { @@ -2416,6 +2483,20 @@ __PACKAGE__->register_method({ die "Cannot suspend HA managed VM to disk\n" if $todisk && PVE::HA::Config::vm_is_ha_managed($vmid); + # early check for storage permission, for better user feedback + if ($todisk) { + $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']); + + if (!$statestorage) { + # get statestorage from config if none is given + my $conf = PVE::QemuConfig->load_config($vmid); + my $storecfg = PVE::Storage::config(); + $statestorage = PVE::QemuServer::find_vmstate_storage($conf, $storecfg); + } + + $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']); + } + my $realcmd = sub { my $upid = shift; @@ -2692,32 +2773,24 @@ __PACKAGE__->register_method({ my ($param) = @_; my $rpcenv = PVE::RPCEnvironment::get(); - - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); my $node = extract_param($param, 'node'); - my $vmid = extract_param($param, 'vmid'); - my $newid = extract_param($param, 'newid'); - my $pool = extract_param($param, 'pool'); - - if (defined($pool)) { - $rpcenv->check_pool_exist($pool); - } + $rpcenv->check_pool_exist($pool) if defined($pool); my $snapname = extract_param($param, 'snapname'); - my $storage = extract_param($param, 'storage'); - my $format = extract_param($param, 'format'); - my $target = extract_param($param, 'target'); my $localnode = PVE::INotify::nodename(); - undef $target if $target && ($target eq $localnode || $target eq 'localhost'); + if ($target && ($target eq $localnode || $target eq 'localhost')) { + undef $target; + } PVE::Cluster::check_node_exists($target) if $target; @@ -2735,7 +2808,7 @@ __PACKAGE__->register_method({ } } - PVE::Cluster::check_cfs_quorum(); + PVE::Cluster::check_cfs_quorum(); my $running = PVE::QemuServer::check_running($vmid) || 0; @@ -2743,25 +2816,18 @@ __PACKAGE__->register_method({ my $shared_lock = $running ? 0 : 1; my $clonefn = sub { - - # do all tests after lock - # we also try to do all tests before we fork the worker + # do all tests after lock but before forking worker - if possible my $conf = PVE::QemuConfig->load_config($vmid); - PVE::QemuConfig->check_lock($conf); my $verify_running = PVE::QemuServer::check_running($vmid) || 0; - die "unexpected state change\n" if $verify_running != $running; die "snapshot '$snapname' does not exist\n" if $snapname && !defined( $conf->{snapshots}->{$snapname}); - my $full = extract_param($param, 'full'); - if (!defined($full)) { - $full = !PVE::QemuConfig->is_template($conf); - } + my $full = extract_param($param, 'full') // !PVE::QemuConfig->is_template($conf); die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full; @@ -2773,10 +2839,10 @@ __PACKAGE__->register_method({ my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage); - die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm; + die "can't clone VM to node '$target' (VM uses local storage)\n" + if $target && !$sharedvm; my $conffile = PVE::QemuConfig->config_file($newid); - die "unable to create VM $newid: config file already exists\n" if -f $conffile; @@ -2829,8 +2895,7 @@ __PACKAGE__->register_method({ my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || ''); $smbios1->{uuid} = PVE::QemuServer::generate_uuid(); $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1); - - # auto generate a new vmgenid if the option was set + # auto generate a new vmgenid only if the option was set for template if ($newconf->{vmgenid}) { $newconf->{vmgenid} = PVE::QemuServer::generate_uuid(); } @@ -2840,11 +2905,7 @@ __PACKAGE__->register_method({ if ($param->{name}) { $newconf->{name} = $param->{name}; } else { - if ($oldconf->{name}) { - $newconf->{name} = "Copy-of-$oldconf->{name}"; - } else { - $newconf->{name} = "Copy-of-VM-$vmid"; - } + $newconf->{name} = "Copy-of-VM-" . ($oldconf->{name} // $vmid); } if ($param->{description}) { @@ -2852,6 +2913,7 @@ __PACKAGE__->register_method({ } # create empty/temp config - this fails if VM already exists on other node + # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n"); my $realcmd = sub { @@ -2886,7 +2948,7 @@ __PACKAGE__->register_method({ $newid, $storage, $format, $fullclone->{$opt}, $newvollist, $jobs, $skipcomplete, $oldconf->{agent}, $clonelimit); - $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive); + $newconf->{$opt} = PVE::QemuServer::print_drive($newdrive); PVE::QemuConfig->write_config($newid, $newconf); $i++; @@ -2916,16 +2978,18 @@ __PACKAGE__->register_method({ 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? foreach my $volid (@$newvollist) { eval { PVE::Storage::vdisk_free($storecfg, $volid); }; warn $@ if $@; } + + PVE::Firewall::remove_vmfw_conf($newid); + + unlink $conffile; # avoid races -> last thing before die + die "clone failed: $err"; } @@ -2966,7 +3030,7 @@ __PACKAGE__->register_method({ 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.", @@ -3007,51 +3071,43 @@ __PACKAGE__->register_method({ my ($param) = @_; my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); my $node = extract_param($param, 'node'); - my $vmid = extract_param($param, 'vmid'); - my $digest = extract_param($param, 'digest'); - my $disk = extract_param($param, 'disk'); - my $storeid = extract_param($param, 'storage'); - my $format = extract_param($param, 'format'); my $storecfg = PVE::Storage::config(); my $updatefn = sub { - my $conf = PVE::QemuConfig->load_config($vmid); - PVE::QemuConfig->check_lock($conf); - die "checksum missmatch (file change by other user?)\n" + die "VM config checksum missmatch (file change by other user?)\n" if $digest && $digest ne $conf->{digest}; die "disk '$disk' does not exist\n" if !$conf->{$disk}; my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk}); - my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n"; - + die "disk '$disk' has no associated volume\n" if !$drive->{file}; die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive, 1); + my $old_volid = $drive->{file}; my $oldfmt; my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid); if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){ $oldfmt = $1; } - die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid && + die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid && (!$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}; @@ -3062,7 +3118,6 @@ __PACKAGE__->register_method({ PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]); my $realcmd = sub { - my $newvollist = []; eval { @@ -3080,7 +3135,7 @@ __PACKAGE__->register_method({ my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef, $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit); - $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive); + $conf->{$disk} = PVE::QemuServer::print_drive($newdrive); PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete}; @@ -3090,8 +3145,9 @@ __PACKAGE__->register_method({ PVE::QemuConfig->write_config($vmid, $conf); - if ($running && PVE::QemuServer::parse_guest_agent($conf)->{fstrim_cloned_disks} && PVE::QemuServer::qga_check_running($vmid)) { - eval { mon_cmd($vmid, "guest-fstrim"); }; + my $do_trim = PVE::QemuServer::parse_guest_agent($conf)->{fstrim_cloned_disks}; + if ($running && $do_trim && PVE::QemuServer::qga_check_running($vmid)) { + eval { mon_cmd($vmid, "guest-fstrim") }; } eval { @@ -3102,11 +3158,10 @@ __PACKAGE__->register_method({ warn $@ if $@; }; if (my $err = $@) { - - foreach my $volid (@$newvollist) { - eval { PVE::Storage::vdisk_free($storecfg, $volid); }; - warn $@ if $@; - } + foreach my $volid (@$newvollist) { + eval { PVE::Storage::vdisk_free($storecfg, $volid) }; + warn $@ if $@; + } die "storage migration failed: $err"; } @@ -3359,7 +3414,7 @@ __PACKAGE__->register_method({ if (PVE::QemuServer::check_running($vmid)) { die "can't migrate running VM without --online\n" if !$param->{online}; } else { - warn "VM isn't running. Doing offline migration instead\n." if $param->{online}; + warn "VM isn't running. Doing offline migration instead.\n" if $param->{online}; $param->{online} = 0; } @@ -3473,7 +3528,7 @@ __PACKAGE__->register_method({ 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', @@ -3569,8 +3624,9 @@ __PACKAGE__->register_method({ PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize); - $drive->{size} = $newsize; - $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive); + 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); }; @@ -3714,6 +3770,9 @@ __PACKAGE__->register_method({ die "unable to use snapshot name 'current' (reserved name)\n" if $snapname eq 'current'; + die "unable to use snapshot name 'pending' (reserved name)\n" + if lc($snapname) eq 'pending'; + my $realcmd = sub { PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname"); PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate}, @@ -3822,7 +3881,7 @@ __PACKAGE__->register_method({ 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, @@ -3969,7 +4028,7 @@ __PACKAGE__->register_method({ 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()], }, },