X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPI2%2FQemu.pm;h=80bc141b5817bcc0339bb9f60dfc5633231559a5;hb=6d449202baab081ea334d0b18fd47d3ba9cd95ec;hp=733788797b4f0ebf4f14b6b9b338ef4289dc3dcb;hpb=84b31f488c9bc3e0c1d068ff8229f7291361d7a5;p=qemu-server.git diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 73378879..80bc141b 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -5,6 +5,8 @@ use warnings; use Cwd 'abs_path'; use Net::SSLeay; use UUID; +use POSIX; +use IO::Socket::IP; use PVE::Cluster qw (cfs_read_file cfs_write_file);; use PVE::SafeSyslog; @@ -22,8 +24,16 @@ use PVE::INotify; use PVE::Network; use PVE::Firewall; use PVE::API2::Firewall::VM; -use PVE::HA::Env::PVE2; -use PVE::HA::Config; +use PVE::ReplicationTools; + +BEGIN { + if (!$ENV{PVE_GENERATING_DOCS}) { + require PVE::HA::Env::PVE2; + import PVE::HA::Env::PVE2; + require PVE::HA::Config; + import PVE::HA::Config; + } +} use Data::Dumper; # fixme: remove @@ -60,7 +70,7 @@ my $check_storage_access = sub { die "no storage ID specified (and no default storage)\n" if !$storeid; $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']); } else { - $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid); + PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid); } }); }; @@ -123,16 +133,41 @@ my $create_disks = sub { die "no storage ID specified (and no default storage)\n" if !$storeid; my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid); my $fmt = $disk->{format} || $defformat; - my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, - $fmt, undef, $size*1024*1024); - $disk->{file} = $volid; - $disk->{size} = $size*1024*1024*1024; + + my $volid; + if ($ds eq 'efidisk0') { + # handle efidisk + my $ovmfvars = '/usr/share/kvm/OVMF_VARS-pure-efi.fd'; + die "uefi vars image not found\n" if ! -f $ovmfvars; + $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, + $fmt, undef, 128); + $disk->{file} = $volid; + $disk->{size} = 128*1024; + my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid); + my $scfg = PVE::Storage::storage_config($storecfg, $storeid); + my $qemufmt = PVE::QemuServer::qemu_img_format($scfg, $volname); + my $path = PVE::Storage::path($storecfg, $volid); + my $efidiskcmd = ['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $qemufmt]; + push @$efidiskcmd, $ovmfvars; + push @$efidiskcmd, $path; + + PVE::Storage::activate_volumes($storecfg, [$volid]); + + eval { PVE::Tools::run_command($efidiskcmd); }; + my $err = $@; + die "Copying of EFI Vars image failed: $err" if $err; + } else { + $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, + $fmt, undef, $size*1024*1024); + $disk->{file} = $volid; + $disk->{size} = $size*1024*1024*1024; + } push @$vollist, $volid; delete $disk->{format}; # no longer needed $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); } else { - $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid); + PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid); my $volid_is_new = 1; @@ -449,7 +484,7 @@ __PACKAGE__->register_method({ die "pipe requires cli environment\n" if $rpcenv->{type} ne 'cli'; } else { - $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $archive); + PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $archive); $archive = PVE::Storage::abs_filesystem_path($storecfg, $archive); } } @@ -468,6 +503,10 @@ __PACKAGE__->register_method({ die "unable to restore vm $vmid - vm is running\n" if PVE::QemuServer::check_running($vmid); + + die "unable to restore vm $vmid - vm is a template\n" + if PVE::QemuConfig->is_template($conf); + } else { die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n"; } @@ -585,6 +624,7 @@ __PACKAGE__->register_method({ { subdir => 'rrd' }, { subdir => 'rrddata' }, { subdir => 'monitor' }, + { subdir => 'agent' }, { subdir => 'snapshot' }, { subdir => 'spiceproxy' }, { subdir => 'sendkey' }, @@ -914,6 +954,7 @@ my $update_vm_api = sub { if (PVE::QemuServer::is_valid_drivename($opt)) { # cleanup drive path my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}); + 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); } elsif ($opt =~ m/^net(\d+)$/) { @@ -967,6 +1008,12 @@ my $update_vm_api = sub { foreach my $opt (@delete) { $modified->{$opt} = 1; $conf = PVE::QemuConfig->load_config($vmid); # update/reload + if (!defined($conf->{$opt})) { + warn "cannot delete '$opt' - not set in current configuration!\n"; + $modified->{$opt} = 0; + next; + } + if ($opt =~ m/^unused/) { my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt}); PVE::QemuConfig->check_protection($conf, "can't remove unused disk '$drive->{file}'"); @@ -982,6 +1029,16 @@ my $update_vm_api = sub { if defined($conf->{pending}->{$opt}); PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); + } elsif ($opt eq "replica" || $opt eq "replica_target") { + delete $conf->{$opt}; + delete $conf->{replica} if $opt eq "replica_target"; + + PVE::ReplicationTools::job_remove($vmid); + PVE::QemuConfig->write_config($vmid, $conf); + } elsif ($opt eq "replica_interval" || $opt eq "replica_rate_limit") { + delete $conf->{$opt}; + PVE::ReplicationTools::update_conf($vmid, $opt, $param->{$opt}); + PVE::QemuConfig->write_config($vmid, $conf); } else { PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); @@ -1004,6 +1061,27 @@ my $update_vm_api = sub { if defined($conf->{pending}->{$opt}); &$create_disks($rpcenv, $authuser, $conf->{pending}, $storecfg, $vmid, undef, {$opt => $param->{$opt}}); + } elsif ($opt eq "replica") { + die "Not all volumes are syncable, please check your config\n" + if !PVE::ReplicationTools::check_guest_volumes_syncable($conf, 'qemu'); + die "replica_target is required\n" + if !$conf->{replica_target} && !$param->{replica_target}; + my $value = $param->{$opt}; + if ($value) { + PVE::ReplicationTools::job_enable($vmid); + } else { + PVE::ReplicationTools::job_disable($vmid); + } + $conf->{$opt} = $param->{$opt}; + } elsif ($opt eq "replica_interval" || $opt eq "replica_rate_limit") { + $conf->{$opt} = $param->{$opt}; + PVE::ReplicationTools::update_conf($vmid, $opt, $param->{$opt}); + } elsif ($opt eq "replica_target" ) { + die "Node: $param->{$opt} does not exists in Cluster.\n" + if !PVE::Cluster::check_node_exists($param->{$opt}); + PVE::ReplicationTools::update_conf($vmid, $opt, $param->{$opt}) + if defined($conf->{$opt}); + $conf->{$opt} = $param->{$opt}; } else { $conf->{pending}->{$opt} = $param->{$opt}; } @@ -1239,6 +1317,9 @@ __PACKAGE__->register_method({ syslog('info', "destroy VM $vmid: $upid\n"); + # return without error if vm has no replica job + PVE::ReplicationTools::destroy_replica($vmid); + PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock); PVE::AccessControl::remove_vm_access($vmid); @@ -1370,20 +1451,36 @@ __PACKAGE__->register_method({ $cmd = ['/usr/bin/vncterm', '-rfbport', $port, '-timeout', $timeout, '-authpath', $authpath, '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd]; + PVE::Tools::run_command($cmd); } else { $ENV{LC_PVE_TICKET} = $ticket if $websocket; # set ticket with "qm vncproxy" - my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid]; - - my $qmstr = join(' ', @$qmcmd); - - # also redirect stderr (else we get RFB protocol errors) - $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"]; + $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid]; + + my $sock = IO::Socket::IP->new( + Listen => 1, + LocalPort => $port, + Proto => 'tcp', + GetAddrInfoFlags => 0, + ) or die "failed to create socket: $!\n"; + # Inside the worker we shouldn't have any previous alarms + # running anyway...: + alarm(0); + local $SIG{ALRM} = sub { die "connection timed out\n" }; + alarm $timeout; + accept(my $cli, $sock) or die "connection failed: $!\n"; + alarm(0); + close($sock); + if (PVE::Tools::run_command($cmd, + output => '>&'.fileno($cli), + input => '<&'.fileno($cli), + noerr => 1) != 0) + { + die "Failed to run vncproxy.\n"; + } } - PVE::Tools::run_command($cmd); - return; }; @@ -1597,7 +1694,25 @@ __PACKAGE__->register_method({ skiplock => get_standard_option('skiplock'), stateuri => get_standard_option('pve-qm-stateuri'), migratedfrom => get_standard_option('pve-node',{ optional => 1 }), + migration_type => { + type => 'string', + enum => ['secure', 'insecure'], + description => "Migration traffic is encrypted using an SSH " . + "tunnel by default. On secure, completely private networks " . + "this can be disabled to increase performance.", + optional => 1, + }, + migration_network => { + type => 'string', format => 'CIDR', + description => "CIDR of the (sub) network that is used for migration.", + optional => 1, + }, machine => get_standard_option('pve-qm-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 + } }, }, returns => { @@ -1628,6 +1743,21 @@ __PACKAGE__->register_method({ raise_param_exc({ migratedfrom => "Only root may use this option." }) if $migratedfrom && $authuser ne 'root@pam'; + my $migration_type = extract_param($param, 'migration_type'); + raise_param_exc({ migration_type => "Only root may use this option." }) + if $migration_type && $authuser ne 'root@pam'; + + my $migration_network = extract_param($param, 'migration_network'); + raise_param_exc({ migration_network => "Only root may use this option." }) + if $migration_network && $authuser ne 'root@pam'; + + my $targetstorage = extract_param($param, 'targetstorage'); + raise_param_exc({ targetstorage => "Only root may use this option." }) + if $targetstorage && $authuser ne 'root@pam'; + + raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." }) + if $targetstorage && !$migratedfrom; + # read spice ticket from STDIN my $spice_ticket; if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type} eq 'cli')) { @@ -1649,7 +1779,7 @@ __PACKAGE__->register_method({ my $service = "vm:$vmid"; - my $cmd = ['ha-manager', 'enable', $service]; + my $cmd = ['ha-manager', 'set', $service, '--state', 'started']; print "Executing HA start for VM $vmid\n"; @@ -1668,7 +1798,7 @@ __PACKAGE__->register_method({ syslog('info', "start VM $vmid: $upid\n"); PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, - $machine, $spice_ticket); + $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage); return; }; @@ -1703,7 +1833,7 @@ __PACKAGE__->register_method({ optional => 1, }, keepActive => { - description => "Do not decativate storage volumes.", + description => "Do not deactivate storage volumes.", type => 'boolean', optional => 1, default => 0, @@ -1746,7 +1876,7 @@ __PACKAGE__->register_method({ my $service = "vm:$vmid"; - my $cmd = ['ha-manager', 'disable', $service]; + my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped']; print "Executing HA stop for VM $vmid\n"; @@ -1854,7 +1984,7 @@ __PACKAGE__->register_method({ default => 0, }, keepActive => { - description => "Do not decativate storage volumes.", + description => "Do not deactivate storage volumes.", type => 'boolean', optional => 1, default => 0, @@ -1908,18 +2038,40 @@ __PACKAGE__->register_method({ } } - my $realcmd = sub { - my $upid = shift; + if (PVE::HA::Config::vm_is_ha_managed($vmid) && + ($rpcenv->{type} ne 'ha')) { + + my $hacmd = sub { + my $upid = shift; - syslog('info', "shutdown VM $vmid: $upid\n"); + my $service = "vm:$vmid"; - PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout}, - $shutdown, $param->{forceStop}, $keepActive); + my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped']; - return; - }; + print "Executing HA stop for VM $vmid\n"; + + PVE::Tools::run_command($cmd); + + return; + }; + + return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd); - return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd); + } else { + + my $realcmd = sub { + my $upid = shift; + + syslog('info', "shutdown VM $vmid: $upid\n"); + + PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout}, + $shutdown, $param->{forceStop}, $keepActive); + + return; + }; + + return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd); + } }}); __PACKAGE__->register_method({ @@ -2311,7 +2463,8 @@ __PACKAGE__->register_method({ # always change MAC! address if ($opt =~ m/^net(\d+)$/) { my $net = PVE::QemuServer::parse_net($value); - $net->{macaddr} = PVE::Tools::random_ether_addr(); + my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg'); + $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix}); $newconf->{$opt} = PVE::QemuServer::print_net($net); } elsif (PVE::QemuServer::is_valid_drivename($opt)) { my $drive = PVE::QemuServer::parse_drive($opt, $value); @@ -2368,21 +2521,28 @@ __PACKAGE__->register_method({ my $upid = shift; my $newvollist = []; + my $jobs = {}; eval { local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; }; PVE::Storage::activate_volumes($storecfg, $vollist, $snapname); + my $total_jobs = scalar(keys %{$drives}); + my $i = 1; + foreach my $opt (keys %$drives) { my $drive = $drives->{$opt}; + my $skipcomplete = ($total_jobs != $i); # finish after last drive my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname, - $newid, $storage, $format, $fullclone->{$opt}, $newvollist); + $newid, $storage, $format, $fullclone->{$opt}, $newvollist, + $jobs, $skipcomplete, $oldconf->{agent}); $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive); PVE::QemuConfig->write_config($newid, $newconf); + $i++; } delete $newconf->{lock}; @@ -2391,6 +2551,7 @@ __PACKAGE__->register_method({ if ($target) { # always deactivate volumes - avoid lvm LVs to be active on several nodes PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running; + PVE::Storage::deactivate_volumes($storecfg, $newvollist); my $newconffile = PVE::QemuConfig->config_file($newid, $target); die "Failed to move config to node '$target' - rename failed: $!\n" @@ -2402,6 +2563,8 @@ __PACKAGE__->register_method({ 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) { @@ -2434,17 +2597,15 @@ __PACKAGE__->register_method({ proxyto => 'node', description => "Move volume to different storage.", permissions => { - description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " . - "and 'Datastore.AllocateSpace' permissions on the storage.", - check => - [ 'and', - ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]], - ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]], - ], + description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.", + check => [ 'and', + ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]], + ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]], + ], }, parameters => { additionalProperties => 0, - properties => { + properties => { node => get_standard_option('pve-node'), vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), disk => { @@ -2505,6 +2666,8 @@ __PACKAGE__->register_method({ my $conf = PVE::QemuConfig->load_config($vmid); + PVE::QemuConfig->check_lock($conf); + die "checksum missmatch (file change by other user?)\n" if $digest && $digest ne $conf->{digest}; @@ -2553,6 +2716,10 @@ __PACKAGE__->register_method({ PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete}; + # convert moved disk to base if part of template + PVE::QemuServer::template_create($vmid, $conf, $disk) + if PVE::QemuConfig->is_template($conf); + PVE::QemuConfig->write_config($vmid, $conf); eval { @@ -2615,6 +2782,27 @@ __PACKAGE__->register_method({ description => "Allow to migrate VMs which use local devices. Only root may use this option.", optional => 1, }, + migration_type => { + type => 'string', + enum => ['secure', 'insecure'], + description => "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.", + optional => 1, + }, + migration_network => { + type => 'string', format => 'CIDR', + description => "CIDR of the (sub) network that is used for migration.", + optional => 1, + }, + "with-local-disks" => { + type => 'boolean', + description => "Enable live storage migration for local disk", + optional => 1, + }, + targetstorage => get_standard_option('pve-storage-id', { + description => "Default target storage.", + optional => 1, + completion => \&PVE::QemuServer::complete_storage, + }), }, }, returns => { @@ -2641,9 +2829,19 @@ __PACKAGE__->register_method({ my $vmid = extract_param($param, 'vmid'); + raise_param_exc({ targetstorage => "Live storage migration can only be done online." }) + if !$param->{online} && $param->{targetstorage}; + raise_param_exc({ force => "Only root may use this option." }) if $param->{force} && $authuser ne 'root@pam'; + raise_param_exc({ migration_type => "Only root may use this option." }) + if $param->{migration_type} && $authuser ne 'root@pam'; + + # allow root only until better network permissions are available + raise_param_exc({ migration_network => "Only root may use this option." }) + if $param->{migration_network} && $authuser ne 'root@pam'; + # test if VM exists my $conf = PVE::QemuConfig->load_config($vmid); @@ -2657,7 +2855,12 @@ __PACKAGE__->register_method({ } my $storecfg = PVE::Storage::config(); - PVE::QemuServer::check_storage_availability($storecfg, $conf, $target); + + if( $param->{targetstorage}) { + PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target); + } else { + PVE::QemuServer::check_storage_availability($storecfg, $conf, $target); + } if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { @@ -2698,7 +2901,8 @@ __PACKAGE__->register_method({ proxyto => 'node', description => "Execute Qemu monitor commands.", permissions => { - check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]], + description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')", + check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]], }, parameters => { additionalProperties => 0, @@ -2715,6 +2919,18 @@ __PACKAGE__->register_method({ code => sub { my ($param) = @_; + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $is_ro = sub { + my $command = shift; + return $command =~ m/^\s*info(\s+|$)/ + || $command =~ m/^\s*help\s*$/; + }; + + $rpcenv->check_full($authuser, "/", ['Sys.Modify']) + if !&$is_ro($param->{command}); + my $vmid = $param->{vmid}; my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists @@ -2728,6 +2944,70 @@ __PACKAGE__->register_method({ return $res; }}); +my $guest_agent_commands = [ + 'ping', + 'get-time', + 'info', + 'fsfreeze-status', + 'fsfreeze-freeze', + 'fsfreeze-thaw', + 'fstrim', + 'network-get-interfaces', + 'get-vcpus', + 'get-fsinfo', + 'get-memory-blocks', + 'get-memory-block-info', + 'suspend-hybrid', + 'suspend-ram', + 'suspend-disk', + 'shutdown', + ]; + +__PACKAGE__->register_method({ + name => 'agent', + path => '{vmid}/agent', + method => 'POST', + protected => 1, + proxyto => 'node', + description => "Execute Qemu Guest Agent commands.", + permissions => { + check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]], + }, + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + vmid => get_standard_option('pve-vmid', { + completion => \&PVE::QemuServer::complete_vmid_running }), + command => { + type => 'string', + description => "The QGA command.", + enum => $guest_agent_commands, + }, + }, + }, + returns => { + type => 'object', + description => "Returns an object with a single `result` property. The type of that +property depends on the executed command.", + }, + code => sub { + my ($param) = @_; + + my $vmid = $param->{vmid}; + + my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists + + die "No Qemu Guest Agent\n" if !defined($conf->{agent}); + die "VM $vmid is not running\n" if !PVE::QemuServer::check_running($vmid); + + my $cmd = $param->{command}; + + my $res = PVE::QemuServer::vm_mon_cmd($vmid, "guest-$cmd"); + + return { result => $res }; + }}); + __PACKAGE__->register_method({ name => 'resize_vm', path => '{vmid}/resize', @@ -2752,7 +3032,7 @@ __PACKAGE__->register_method({ size => { type => 'string', pattern => '\+?\d+(\.\d+)?[KMGT]?', - description => "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.", + description => "The new size. With the `+` sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.", }, digest => { type => 'string', @@ -2833,7 +3113,7 @@ __PACKAGE__->register_method({ $newsize += $size if $ext; $newsize = int($newsize); - die "unable to skrink disk size\n" if $newsize < $size; + die "shrinking disks is not supported\n" if $newsize < $size; return if $size == $newsize; @@ -2864,7 +3144,7 @@ __PACKAGE__->register_method({ parameters => { additionalProperties => 0, properties => { - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), node => get_standard_option('pve-node'), }, },