X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPI2%2FQemu.pm;h=c0a72c0316c44f071ffb79d8890b22c066f664d3;hb=63d269d79ec51f65cf37facac52160af4c8ed80c;hp=68899d8c8f55f42dad79268537a77b27db21974f;hpb=df2a2dbbe1b3320e885d8a9996ec2fb6b17190df;p=qemu-server.git diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 68899d8..c0a72c0 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -13,13 +13,17 @@ use PVE::Exception qw(raise raise_param_exc raise_perm_exc); use PVE::Storage; use PVE::JSONSchema qw(get_standard_option); use PVE::RESTHandler; +use PVE::QemuConfig; use PVE::QemuServer; use PVE::QemuMigrate; use PVE::RPCEnvironment; use PVE::AccessControl; use PVE::INotify; use PVE::Network; +use PVE::Firewall; use PVE::API2::Firewall::VM; +use PVE::HA::Env::PVE2; +use PVE::HA::Config; use Data::Dumper; # fixme: remove @@ -37,22 +41,6 @@ my $resolve_cdrom_alias = sub { } }; -my $test_deallocate_drive = sub { - my ($storecfg, $vmid, $key, $drive, $force) = @_; - - if (!PVE::QemuServer::drive_is_cdrom($drive)) { - my $volid = $drive->{file}; - if ( PVE::QemuServer::vm_is_volid_owner($storecfg, $vmid, $volid)) { - if ($force || $key =~ m/^unused/) { - my $sid = PVE::Storage::parse_volume_id($volid); - return $sid; - } - } - } - - return undef; -}; - my $check_storage_access = sub { my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_; @@ -188,32 +176,62 @@ my $create_disks = sub { return $vollist; }; -my $delete_drive = sub { - my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_; +my $cpuoptions = { + 'cores' => 1, + 'cpu' => 1, + 'cpulimit' => 1, + 'cpuunits' => 1, + 'numa' => 1, + 'smp' => 1, + 'sockets' => 1, + 'vpcus' => 1, +}; - if (!PVE::QemuServer::drive_is_cdrom($drive)) { - my $volid = $drive->{file}; +my $memoryoptions = { + 'memory' => 1, + 'balloon' => 1, + 'shares' => 1, +}; - if (PVE::QemuServer::vm_is_volid_owner($storecfg, $vmid, $volid)) { - if ($force || $key =~ m/^unused/) { - eval { - # check if the disk is really unused - my $used_paths = PVE::QemuServer::get_used_paths($vmid, $storecfg, $conf, 1, $key); - my $path = PVE::Storage::path($storecfg, $volid); +my $hwtypeoptions = { + 'acpi' => 1, + 'hotplug' => 1, + 'kvm' => 1, + 'machine' => 1, + 'scsihw' => 1, + 'smbios1' => 1, + 'tablet' => 1, + 'vga' => 1, + 'watchdog' => 1, +}; - die "unable to delete '$volid' - volume is still in use (snapshot?)\n" - if $used_paths->{$path}; +my $generaloptions = { + 'agent' => 1, + 'autostart' => 1, + 'bios' => 1, + 'description' => 1, + 'keyboard' => 1, + 'localtime' => 1, + 'migrate_downtime' => 1, + 'migrate_speed' => 1, + 'name' => 1, + 'onboot' => 1, + 'ostype' => 1, + 'protection' => 1, + 'reboot' => 1, + 'startdate' => 1, + 'startup' => 1, + 'tdf' => 1, + 'template' => 1, +}; - PVE::Storage::vdisk_free($storecfg, $volid); - }; - die $@ if $@; - } else { - PVE::QemuServer::add_unused_volume($conf, $volid, $vmid); - } - } - } +my $vmpoweroptions = { + 'freeze' => 1, +}; - delete $conf->{$key}; +my $diskoptions = { + 'boot' => 1, + 'bootdisk' => 1, }; my $check_vm_modify_config_perm = sub { @@ -223,25 +241,32 @@ my $check_vm_modify_config_perm = sub { foreach my $opt (@$key_list) { # disk checks need to be done somewhere else - next if PVE::QemuServer::valid_drivename($opt); + next if PVE::QemuServer::is_valid_drivename($opt); + next if $opt eq 'cdrom'; + next if $opt =~ m/^unused\d+$/; - if ($opt eq 'sockets' || $opt eq 'cores' || - $opt eq 'cpu' || $opt eq 'smp' || - $opt eq 'cpulimit' || $opt eq 'cpuunits') { + if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) { $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']); - } elsif ($opt eq 'boot' || $opt eq 'bootdisk') { - $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']); - } elsif ($opt eq 'memory' || $opt eq 'balloon' || $opt eq 'shares') { + } elsif ($memoryoptions->{$opt}) { $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']); - } elsif ($opt eq 'args' || $opt eq 'lock') { - die "only root can set '$opt' config\n"; - } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' || - $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') { + } elsif ($hwtypeoptions->{$opt}) { $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']); + } elsif ($generaloptions->{$opt}) { + $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']); + # special case for startup since it changes host behaviour + if ($opt eq 'startup') { + $rpcenv->check_full($authuser, "/", ['Sys.Modify']); + } + } elsif ($vmpoweroptions->{$opt}) { + $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']); + } elsif ($diskoptions->{$opt}) { + $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']); } elsif ($opt =~ m/^net\d+$/) { $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']); } else { - $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']); + # catches usb\d+, hostpci\d+, args, lock, etc. + # new options will be checked here + die "only root can set '$opt' config\n"; } } @@ -263,6 +288,11 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), + full => { + type => 'boolean', + optional => 1, + description => "Determine the full status of active VMs.", + }, }, }, returns => { @@ -279,7 +309,7 @@ __PACKAGE__->register_method({ my $rpcenv = PVE::RPCEnvironment::get(); my $authuser = $rpcenv->get_user(); - my $vmstatus = PVE::QemuServer::vmstatus(); + my $vmstatus = PVE::QemuServer::vmstatus(undef, $param->{full}); my $res = []; foreach my $vmid (keys %$vmstatus) { @@ -313,16 +343,18 @@ __PACKAGE__->register_method({ properties => PVE::QemuServer::json_config_properties( { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }), archive => { description => "The backup file.", type => 'string', optional => 1, maxLength => 255, + completion => \&PVE::QemuServer::complete_backup_archives, }, storage => get_standard_option('pve-storage-id', { description => "Default storage.", optional => 1, + completion => \&PVE::QemuServer::complete_storage, }), force => { optional => 1, @@ -367,7 +399,7 @@ __PACKAGE__->register_method({ my $pool = extract_param($param, 'pool'); - my $filename = PVE::QemuServer::config_file($vmid); + my $filename = PVE::QemuConfig->config_file($vmid); my $storecfg = PVE::Storage::config(); @@ -399,7 +431,7 @@ __PACKAGE__->register_method({ &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]); foreach my $opt (keys %$param) { - if (PVE::QemuServer::valid_drivename($opt)) { + if (PVE::QemuServer::is_valid_drivename($opt)) { my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}); raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive; @@ -423,14 +455,22 @@ __PACKAGE__->register_method({ } my $restorefn = sub { + my $vmlist = PVE::Cluster::get_vmlist(); + if ($vmlist->{ids}->{$vmid}) { + my $current_node = $vmlist->{ids}->{$vmid}->{node}; + if ($current_node eq $node) { + my $conf = PVE::QemuConfig->load_config($vmid); + + PVE::QemuConfig->check_protection($conf, "unable to restore VM $vmid"); - # fixme: this test does not work if VM exists on other node! - if (-f $filename) { - die "unable to restore vm $vmid: config file already exists\n" - if !$force; + die "unable to restore vm $vmid - config file already exists\n" + if !$force; - die "unable to restore vm $vmid: vm is running\n" - if PVE::QemuServer::check_running($vmid); + die "unable to restore vm $vmid - vm is running\n" + if PVE::QemuServer::check_running($vmid); + } else { + die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n"; + } } my $realcmd = sub { @@ -448,8 +488,7 @@ __PACKAGE__->register_method({ my $createfn = sub { # test after locking - die "unable to create vm $vmid: config file already exists\n" - if -f $filename; + PVE::Cluster::check_vmid_unused($vmid); my $realcmd = sub { @@ -462,7 +501,7 @@ __PACKAGE__->register_method({ $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage); # try to be smart about bootdisk - my @disks = PVE::QemuServer::disknames(); + my @disks = PVE::QemuServer::valid_drive_names(); my $firstdisk; foreach my $ds (reverse @disks) { next if !$conf->{$ds}; @@ -483,7 +522,7 @@ __PACKAGE__->register_method({ $conf->{smbios1} = "uuid=$uuid_str"; } - PVE::QemuServer::update_config_nolock($vmid, $conf); + PVE::QemuConfig->write_config($vmid, $conf); }; my $err = $@; @@ -502,7 +541,7 @@ __PACKAGE__->register_method({ return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd); }; - return PVE::QemuServer::lock_config_full($vmid, 1, $archive ? $restorefn : $createfn); + return PVE::QemuConfig->lock_config_full($vmid, 1, $archive ? $restorefn : $createfn); }}); __PACKAGE__->register_method({ @@ -661,7 +700,13 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), + current => { + description => "Get current values (instead of pending values).", + optional => 1, + default => 0, + type => 'boolean', + }, }, }, returns => { @@ -676,9 +721,23 @@ __PACKAGE__->register_method({ code => sub { my ($param) = @_; - my $conf = PVE::QemuServer::load_config($param->{vmid}); + my $conf = PVE::QemuConfig->load_config($param->{vmid}); delete $conf->{snapshots}; + + if (!$param->{current}) { + foreach my $opt (keys %{$conf->{pending}}) { + next if $opt eq 'delete'; + my $value = $conf->{pending}->{$opt}; + next if ref($value); # just to be sure + $conf->{$opt} = $value; + } + my $pending_delete_hash = PVE::QemuServer::split_flagged_list($conf->{pending}->{delete}); + foreach my $opt (keys %$pending_delete_hash) { + delete $conf->{$opt} if $conf->{$opt}; + } + } + delete $conf->{pending}; return $conf; @@ -697,7 +756,7 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), }, }, returns => { @@ -720,8 +779,11 @@ __PACKAGE__->register_method({ optional => 1, }, delete => { - description => "Indicated a pending delete request.", - type => 'boolean', + description => "Indicates a pending delete request if present and not 0. " . + "The value 2 indicates a force-delete request.", + type => 'integer', + minimum => 0, + maximum => 2, optional => 1, }, }, @@ -730,37 +792,34 @@ __PACKAGE__->register_method({ code => sub { my ($param) = @_; - my $conf = PVE::QemuServer::load_config($param->{vmid}); + my $conf = PVE::QemuConfig->load_config($param->{vmid}); - my $pending_delete_hash = {}; - foreach my $opt (PVE::Tools::split_list($conf->{pending}->{delete})) { - $pending_delete_hash->{$opt} = 1; - } + my $pending_delete_hash = PVE::QemuServer::split_flagged_list($conf->{pending}->{delete}); my $res = []; - foreach my $opt (keys $conf) { + foreach my $opt (keys %$conf) { next if ref($conf->{$opt}); my $item = { key => $opt }; $item->{value} = $conf->{$opt} if defined($conf->{$opt}); $item->{pending} = $conf->{pending}->{$opt} if defined($conf->{pending}->{$opt}); - $item->{delete} = 1 if $pending_delete_hash->{$opt}; + $item->{delete} = ($pending_delete_hash->{$opt} ? 2 : 1) if exists $pending_delete_hash->{$opt}; push @$res, $item; } - foreach my $opt (keys $conf->{pending}) { + foreach my $opt (keys %{$conf->{pending}}) { next if $opt eq 'delete'; next if ref($conf->{pending}->{$opt}); # just to be sure - next if $conf->{$opt}; + next if defined($conf->{$opt}); my $item = { key => $opt }; $item->{pending} = $conf->{pending}->{$opt}; push @$res, $item; } - foreach my $opt (PVE::Tools::split_list($conf->{pending}->{delete})) { + while (my ($opt, $force) = each %$pending_delete_hash) { next if $conf->{pending}->{$opt}; # just to be sure next if $conf->{$opt}; - my $item = { key => $opt, delete => 1}; + my $item = { key => $opt, delete => ($force ? 2 : 1)}; push @$res, $item; } @@ -805,9 +864,11 @@ my $update_vm_api = sub { my $delete_str = extract_param($param, 'delete'); + my $revert_str = extract_param($param, 'revert'); + my $force = extract_param($param, 'force'); - die "no options specified\n" if !$delete_str && !scalar(keys %$param); + die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param); my $storecfg = PVE::Storage::config(); @@ -817,13 +878,31 @@ my $update_vm_api = sub { # now try to verify all parameters + my $revert = {}; + foreach my $opt (PVE::Tools::split_list($revert_str)) { + if (!PVE::QemuServer::option_exists($opt)) { + raise_param_exc({ revert => "unknown option '$opt'" }); + } + + raise_param_exc({ delete => "you can't use '-$opt' and " . + "-revert $opt' at the same time" }) + if defined($param->{$opt}); + + $revert->{$opt} = 1; + } + my @delete = (); foreach my $opt (PVE::Tools::split_list($delete_str)) { $opt = 'ide2' if $opt eq 'cdrom'; + raise_param_exc({ delete => "you can't use '-$opt' and " . "-delete $opt' at the same time" }) if defined($param->{$opt}); + raise_param_exc({ revert => "you can't use '-delete $opt' and " . + "-revert $opt' at the same time" }) + if $revert->{$opt}; + if (!PVE::QemuServer::option_exists($opt)) { raise_param_exc({ delete => "unknown option '$opt'" }); } @@ -832,7 +911,7 @@ my $update_vm_api = sub { } foreach my $opt (keys %$param) { - if (PVE::QemuServer::valid_drivename($opt)) { + if (PVE::QemuServer::is_valid_drivename($opt)) { # cleanup drive path my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}); PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); @@ -852,12 +931,20 @@ my $update_vm_api = sub { my $updatefn = sub { - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); die "checksum missmatch (file change by other user?)\n" if $digest && $digest ne $conf->{digest}; - PVE::QemuServer::check_lock($conf) if !$skiplock; + PVE::QemuConfig->check_lock($conf) if !$skiplock; + + foreach my $opt (keys %$revert) { + if (defined($conf->{$opt})) { + $param->{$opt} = $conf->{$opt}; + } elsif (defined($conf->{pending}->{$opt})) { + push @delete, $opt; + } + } if ($param->{memory} || defined($param->{balloon})) { my $maxmem = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory} || $defaults->{memory}; @@ -879,33 +966,34 @@ my $update_vm_api = sub { foreach my $opt (@delete) { $modified->{$opt} = 1; - $conf = PVE::QemuServer::load_config($vmid); # update/reload + $conf = PVE::QemuConfig->load_config($vmid); # update/reload if ($opt =~ m/^unused/) { - $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']); my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt}); - if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) { - $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']); - &$delete_drive($conf, $storecfg, $vmid, $opt, $drive); - PVE::QemuServer::update_config_nolock($vmid, $conf, 1); + PVE::QemuConfig->check_protection($conf, "can't remove unused disk '$drive->{file}'"); + $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']); + if (PVE::QemuServer::try_deallocate_drive($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) { + delete $conf->{$opt}; + PVE::QemuConfig->write_config($vmid, $conf); } - } elsif (PVE::QemuServer::valid_drivename($opt)) { + } 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']); PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt})) if defined($conf->{pending}->{$opt}); - PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt); - PVE::QemuServer::update_config_nolock($vmid, $conf, 1); + PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force); + PVE::QemuConfig->write_config($vmid, $conf); } else { - PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt); - PVE::QemuServer::update_config_nolock($vmid, $conf, 1); + PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force); + PVE::QemuConfig->write_config($vmid, $conf); } } foreach my $opt (keys %$param) { # add/change $modified->{$opt} = 1; - $conf = PVE::QemuServer::load_config($vmid); # update/reload + $conf = PVE::QemuConfig->load_config($vmid); # update/reload next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed - if (PVE::QemuServer::valid_drivename($opt)) { + if (PVE::QemuServer::is_valid_drivename($opt)) { my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}); if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']); @@ -920,13 +1008,13 @@ my $update_vm_api = sub { $conf->{pending}->{$opt} = $param->{$opt}; } PVE::QemuServer::vmconfig_undelete_pending_option($conf, $opt); - PVE::QemuServer::update_config_nolock($vmid, $conf, 1); + PVE::QemuConfig->write_config($vmid, $conf); } # remove pending changes when nothing changed - $conf = PVE::QemuServer::load_config($vmid); # update/reload + $conf = PVE::QemuConfig->load_config($vmid); # update/reload my $changes = PVE::QemuServer::vmconfig_cleanup_pending($conf); - PVE::QemuServer::update_config_nolock($vmid, $conf, 1) if $changes; + PVE::QemuConfig->write_config($vmid, $conf) if $changes; return if !scalar(keys %{$conf->{pending}}); @@ -934,7 +1022,7 @@ my $update_vm_api = sub { # apply pending changes - $conf = PVE::QemuServer::load_config($vmid); # update/reload + $conf = PVE::QemuConfig->load_config($vmid); # update/reload if ($running) { my $errors = {}; @@ -980,7 +1068,7 @@ my $update_vm_api = sub { } }; - return PVE::QemuServer::lock_config($vmid, $updatefn); + return PVE::QemuConfig->lock_config($vmid, $updatefn); }; my $vm_config_perm_list = [ @@ -1015,6 +1103,11 @@ __PACKAGE__->register_method({ description => "A list of settings you want to delete.", optional => 1, }, + revert => { + type => 'string', format => 'pve-configid-list', + description => "Revert a pending change.", + optional => 1, + }, force => { type => 'boolean', description => $opt_force_description, @@ -1058,13 +1151,18 @@ __PACKAGE__->register_method({ properties => PVE::QemuServer::json_config_properties( { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), skiplock => get_standard_option('skiplock'), delete => { type => 'string', format => 'pve-configid-list', description => "A list of settings you want to delete.", optional => 1, }, + revert => { + type => 'string', format => 'pve-configid-list', + description => "Revert a pending change.", + optional => 1, + }, force => { type => 'boolean', description => $opt_force_description, @@ -1102,7 +1200,7 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }), skiplock => get_standard_option('skiplock'), }, }, @@ -1123,20 +1221,18 @@ __PACKAGE__->register_method({ if $skiplock && $authuser ne 'root@pam'; # test if VM exists - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); my $storecfg = PVE::Storage::config(); - my $delVMfromPoolFn = sub { - my $usercfg = cfs_read_file("user.cfg"); - if (my $pool = $usercfg->{vms}->{$vmid}) { - if (my $data = $usercfg->{pools}->{$pool}) { - delete $data->{vms}->{$vmid}; - delete $usercfg->{vms}->{$vmid}; - cfs_write_file("user.cfg", $usercfg); - } - } - }; + 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); + + # early tests (repeat after locking) + die "VM $vmid is running - destroy failed\n" + if PVE::QemuServer::check_running($vmid); my $realcmd = sub { my $upid = shift; @@ -1145,7 +1241,9 @@ __PACKAGE__->register_method({ PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock); - PVE::AccessControl::remove_vm_from_pool($vmid); + PVE::AccessControl::remove_vm_access($vmid); + + PVE::Firewall::remove_vmfw_conf($vmid); }; return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd); @@ -1165,7 +1263,7 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), idlist => { type => 'string', format => 'pve-configid-list', description => "A list of disk IDs you want to delete.", @@ -1232,7 +1330,7 @@ __PACKAGE__->register_method({ my $node = $param->{node}; my $websocket = $param->{websocket}; - my $conf = PVE::QemuServer::load_config($vmid, $node); # check if VM exists + my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists my $authpath = "/vms/$vmid"; @@ -1241,17 +1339,19 @@ __PACKAGE__->register_method({ $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192) if !$sslcert; - my $port = PVE::Tools::next_vnc_port(); - - my $remip; + my ($remip, $family); my $remcmd = []; if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) { - $remip = PVE::Cluster::remote_node_ip($node); + ($remip, $family) = PVE::Cluster::remote_node_ip($node); # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip]; + } else { + $family = PVE::Tools::get_host_address_family($node); } + my $port = PVE::Tools::next_vnc_port($family); + my $timeout = 10; my $realcmd = sub { @@ -1279,7 +1379,7 @@ __PACKAGE__->register_method({ my $qmstr = join(' ', @$qmcmd); # also redirect stderr (else we get RFB protocol errors) - $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"]; + $cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 2>/dev/null"]; } PVE::Tools::run_command($cmd); @@ -1347,7 +1447,7 @@ __PACKAGE__->register_method({ PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath); - my $conf = PVE::QemuServer::load_config($vmid, $node); # VM exists ? + my $conf = PVE::QemuConfig->load_config($vmid, $node); # VM exists ? # Note: VNC ports are acessible from outside, so we do not gain any # security if we verify that $param->{port} belongs to VM $vmid. This @@ -1388,8 +1488,9 @@ __PACKAGE__->register_method({ my $node = $param->{node}; my $proxy = $param->{proxy}; - my $conf = PVE::QemuServer::load_config($vmid, $node); - my $title = "VM $vmid - $conf->{'name'}", + my $conf = PVE::QemuConfig->load_config($vmid, $node); + my $title = "VM $vmid"; + $title .= " - ". $conf->{name} if $conf->{name}; my $port = PVE::QemuServer::spice_port($vmid); @@ -1432,7 +1533,7 @@ __PACKAGE__->register_method({ my ($param) = @_; # test if VM exists - my $conf = PVE::QemuServer::load_config($param->{vmid}); + my $conf = PVE::QemuConfig->load_config($param->{vmid}); my $res = [ { subdir => 'current' }, @@ -1443,16 +1544,6 @@ __PACKAGE__->register_method({ return $res; }}); -my $vm_is_ha_managed = sub { - my ($vmid) = @_; - - my $cc = PVE::Cluster::cfs_read_file('cluster.conf'); - if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $vmid, 1)) { - return 1; - } - return 0; -}; - __PACKAGE__->register_method({ name => 'vm_status', path => '{vmid}/status/current', @@ -1475,12 +1566,12 @@ __PACKAGE__->register_method({ my ($param) = @_; # test if VM exists - my $conf = PVE::QemuServer::load_config($param->{vmid}); + my $conf = PVE::QemuConfig->load_config($param->{vmid}); my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1); my $status = $vmstatus->{$param->{vmid}}; - $status->{ha} = &$vm_is_ha_managed($param->{vmid}); + $status->{ha} = PVE::HA::Config::get_service_status("vm:$param->{vmid}"); $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga}); @@ -1501,7 +1592,8 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', + { completion => \&PVE::QemuServer::complete_vmid_stopped }), skiplock => get_standard_option('skiplock'), stateuri => get_standard_option('pve-qm-stateuri'), migratedfrom => get_standard_option('pve-node',{ optional => 1 }), @@ -1545,17 +1637,19 @@ __PACKAGE__->register_method({ } } + PVE::Cluster::check_cfs_quorum(); + my $storecfg = PVE::Storage::config(); - if (&$vm_is_ha_managed($vmid) && !$stateuri && + if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri && $rpcenv->{type} ne 'ha') { my $hacmd = sub { my $upid = shift; - my $service = "pvevm:$vmid"; + my $service = "vm:$vmid"; - my $cmd = ['clusvcadm', '-e', $service, '-m', $node]; + my $cmd = ['ha-manager', 'enable', $service]; print "Executing HA start for VM $vmid\n"; @@ -1589,7 +1683,8 @@ __PACKAGE__->register_method({ method => 'POST', protected => 1, proxyto => 'node', - description => "Stop virtual machine.", + description => "Stop virtual machine. The qemu process will exit immediately. This" . + "is akin to pulling the power plug of a running computer and may damage the VM data", permissions => { check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], }, @@ -1597,7 +1692,8 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', + { completion => \&PVE::QemuServer::complete_vmid_running }), skiplock => get_standard_option('skiplock'), migratedfrom => get_standard_option('pve-node', { optional => 1 }), timeout => { @@ -1643,14 +1739,14 @@ __PACKAGE__->register_method({ my $storecfg = PVE::Storage::config(); - if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) { + if (PVE::HA::Config::vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) { my $hacmd = sub { my $upid = shift; - my $service = "pvevm:$vmid"; + my $service = "vm:$vmid"; - my $cmd = ['clusvcadm', '-d', $service]; + my $cmd = ['ha-manager', 'disable', $service]; print "Executing HA stop for VM $vmid\n"; @@ -1691,7 +1787,8 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', + { completion => \&PVE::QemuServer::complete_vmid_running }), skiplock => get_standard_option('skiplock'), }, }, @@ -1732,7 +1829,8 @@ __PACKAGE__->register_method({ method => 'POST', protected => 1, proxyto => 'node', - description => "Shutdown virtual machine.", + description => "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." . + "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.", permissions => { check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], }, @@ -1740,7 +1838,8 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', + { completion => \&PVE::QemuServer::complete_vmid_running }), skiplock => get_standard_option('skiplock'), timeout => { description => "Wait maximal timeout seconds.", @@ -1786,13 +1885,36 @@ __PACKAGE__->register_method({ my $storecfg = PVE::Storage::config(); + my $shutdown = 1; + + # if vm is paused, do not shutdown (but stop if forceStop = 1) + # otherwise, we will infer a shutdown command, but run into the timeout, + # then when the vm is resumed, it will instantly shutdown + # + # checking the qmp status here to get feedback to the gui/cli/api + # and the status query should not take too long + my $qmpstatus; + eval { + $qmpstatus = PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0); + }; + my $err = $@ if $@; + + if (!$err && $qmpstatus->{status} eq "paused") { + if ($param->{forceStop}) { + warn "VM is paused - stop instead of shutdown\n"; + $shutdown = 0; + } else { + die "VM is paused - cannot shutdown\n"; + } + } + my $realcmd = sub { my $upid = shift; syslog('info', "shutdown VM $vmid: $upid\n"); PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout}, - 1, $param->{forceStop}, $keepActive); + $shutdown, $param->{forceStop}, $keepActive); return; }; @@ -1814,7 +1936,8 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', + { completion => \&PVE::QemuServer::complete_vmid_running }), skiplock => get_standard_option('skiplock'), }, }, @@ -1865,8 +1988,11 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', + { completion => \&PVE::QemuServer::complete_vmid_running }), skiplock => get_standard_option('skiplock'), + nocheck => { type => 'boolean', optional => 1 }, + }, }, returns => { @@ -1887,14 +2013,16 @@ __PACKAGE__->register_method({ raise_param_exc({ skiplock => "Only root may use this option." }) if $skiplock && $authuser ne 'root@pam'; - die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid); + my $nocheck = extract_param($param, 'nocheck'); + + die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid, $nocheck); my $realcmd = sub { my $upid = shift; syslog('info', "resume VM $vmid: $upid\n"); - PVE::QemuServer::vm_resume($vmid, $skiplock); + PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck); return; }; @@ -1916,7 +2044,8 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', + { completion => \&PVE::QemuServer::complete_vmid_running }), skiplock => get_standard_option('skiplock'), key => { description => "The key (qemu monitor encoding).", @@ -1993,7 +2122,7 @@ __PACKAGE__->register_method({ my $running = PVE::QemuServer::check_running($vmid); - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); if($snapname){ my $snap = $conf->{snapshots}->{$snapname}; @@ -2003,7 +2132,7 @@ __PACKAGE__->register_method({ my $storecfg = PVE::Storage::config(); my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg); - my $hasFeature = PVE::QemuServer::has_feature($feature, $conf, $storecfg, $snapname, $running); + my $hasFeature = PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running); return { hasFeature => $hasFeature, @@ -2035,7 +2164,7 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), newid => get_standard_option('pve-vmid', { description => 'VMID for the clone.' }), name => { optional => 1, @@ -2142,9 +2271,9 @@ __PACKAGE__->register_method({ # do all tests after lock # we also try to do all tests before we fork the worker - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); - PVE::QemuServer::check_lock($conf); + PVE::QemuConfig->check_lock($conf); my $verify_running = PVE::QemuServer::check_running($vmid) || 0; @@ -2159,13 +2288,14 @@ __PACKAGE__->register_method({ die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm; - my $conffile = PVE::QemuServer::config_file($newid); + my $conffile = PVE::QemuConfig->config_file($newid); die "unable to create VM $newid: config file already exists\n" if -f $conffile; my $newconf = { lock => 'clone' }; my $drives = {}; + my $fullclone = {}; my $vollist = []; foreach my $opt (keys %$oldconf) { @@ -2175,12 +2305,15 @@ __PACKAGE__->register_method({ next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate' || $opt eq 'snapstate'; + # no need to copy unused images, because VMID(owner) changes anyways + next if $opt =~ m/^unused\d+$/; + # always change MAC! address if ($opt =~ m/^net(\d+)$/) { my $net = PVE::QemuServer::parse_net($value); $net->{macaddr} = PVE::Tools::random_ether_addr(); $newconf->{$opt} = PVE::QemuServer::print_net($net); - } elsif (PVE::QemuServer::valid_drivename($opt)) { + } elsif (PVE::QemuServer::is_valid_drivename($opt)) { my $drive = PVE::QemuServer::parse_drive($opt, $value); die "unable to parse drive options for '$opt'\n" if !$drive; if (PVE::QemuServer::drive_is_cdrom($drive)) { @@ -2189,7 +2322,7 @@ __PACKAGE__->register_method({ if ($param->{full}) { die "Full clone feature is not available" if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running); - $drive->{full} = 1; + $fullclone->{$opt} = 1; } else { # not full means clone instead of copy die "Linked clone feature is not available" @@ -2239,27 +2372,27 @@ __PACKAGE__->register_method({ eval { local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; }; - PVE::Storage::activate_volumes($storecfg, $vollist); + PVE::Storage::activate_volumes($storecfg, $vollist, $snapname); foreach my $opt (keys %$drives) { my $drive = $drives->{$opt}; my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname, - $newid, $storage, $format, $drive->{full}, $newvollist); + $newid, $storage, $format, $fullclone->{$opt}, $newvollist); $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive); - PVE::QemuServer::update_config_nolock($newid, $newconf, 1); + PVE::QemuConfig->write_config($newid, $newconf); } delete $newconf->{lock}; - PVE::QemuServer::update_config_nolock($newid, $newconf, 1); + PVE::QemuConfig->write_config($newid, $newconf); if ($target) { # always deactivate volumes - avoid lvm LVs to be active on several nodes - PVE::Storage::deactivate_volumes($storecfg, $vollist); + PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running; - my $newconffile = PVE::QemuServer::config_file($newid, $target); + my $newconffile = PVE::QemuConfig->config_file($newid, $target); die "Failed to move config to node '$target' - rename failed: $!\n" if !rename($conffile, $newconffile); } @@ -2281,12 +2414,14 @@ __PACKAGE__->register_method({ return; }; + PVE::Firewall::clone_vmfw_conf($vmid, $newid); + return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd); }; - return PVE::QemuServer::lock_config_mode($vmid, 1, $shared_lock, sub { + return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub { # Aquire exclusive lock lock for $newid - return PVE::QemuServer::lock_config_full($newid, 1, $clonefn); + return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn); }); }}); @@ -2311,13 +2446,16 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), disk => { type => 'string', description => "The disk you want to move.", - enum => [ PVE::QemuServer::disknames() ], + enum => [ PVE::QemuServer::valid_drive_names() ], }, - storage => get_standard_option('pve-storage-id', { description => "Target Storage." }), + storage => get_standard_option('pve-storage-id', { + description => "Target storage.", + completion => \&PVE::QemuServer::complete_storage, + }), 'format' => { type => 'string', description => "Target Format.", @@ -2365,7 +2503,7 @@ __PACKAGE__->register_method({ my $updatefn = sub { - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); die "checksum missmatch (file change by other user?)\n" if $digest && $digest ne $conf->{digest}; @@ -2405,9 +2543,9 @@ __PACKAGE__->register_method({ $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive); - PVE::QemuServer::add_unused_volume($conf, $old_volid) if !$param->{delete}; + PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete}; - PVE::QemuServer::update_config_nolock($vmid, $conf, 1); + PVE::QemuConfig->write_config($vmid, $conf); eval { # try to deactivate volumes - avoid lvm LVs to be active on several nodes @@ -2426,14 +2564,15 @@ __PACKAGE__->register_method({ } if ($param->{delete}) { - my $used_paths = PVE::QemuServer::get_used_paths($vmid, $storecfg, $conf, 1, 1); - my $path = PVE::Storage::path($storecfg, $old_volid); - if ($used_paths->{$path}){ - warn "volume $old_volid have snapshots. Can't delete it\n"; - PVE::QemuServer::add_unused_volume($conf, $old_volid); - PVE::QemuServer::update_config_nolock($vmid, $conf, 1); + if (PVE::QemuServer::is_volume_in_use($storecfg, $conf, undef, $old_volid)) { + warn "volume $old_volid still has snapshots, can't delete it\n"; + PVE::QemuConfig->add_unused_volume($conf, $old_volid); + PVE::QemuConfig->write_config($vmid, $conf); } else { - eval { PVE::Storage::vdisk_free($storecfg, $old_volid); }; + eval { + PVE::Storage::deactivate_volumes($storecfg, [$old_volid]); + PVE::Storage::vdisk_free($storecfg, $old_volid); + }; warn $@ if $@; } } @@ -2442,7 +2581,7 @@ __PACKAGE__->register_method({ return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd); }; - return PVE::QemuServer::lock_config($vmid, $updatefn); + return PVE::QemuConfig->lock_config($vmid, $updatefn); }}); __PACKAGE__->register_method({ @@ -2459,8 +2598,11 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), - target => get_standard_option('pve-node', { description => "Target node." }), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), + target => get_standard_option('pve-node', { + description => "Target node.", + completion => \&PVE::Cluster::complete_migration_target, + }), online => { type => 'boolean', description => "Use online/live migration.", @@ -2501,11 +2643,11 @@ __PACKAGE__->register_method({ if $param->{force} && $authuser ne 'root@pam'; # test if VM exists - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); # try to detect errors early - PVE::QemuServer::check_lock($conf); + PVE::QemuConfig->check_lock($conf); if (PVE::QemuServer::check_running($vmid)) { die "cant migrate running VM without --online\n" @@ -2515,14 +2657,14 @@ __PACKAGE__->register_method({ my $storecfg = PVE::Storage::config(); PVE::QemuServer::check_storage_availability($storecfg, $conf, $target); - if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { + if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { my $hacmd = sub { my $upid = shift; - my $service = "pvevm:$vmid"; + my $service = "vm:$vmid"; - my $cmd = ['clusvcadm', '-M', $service, '-m', $target]; + my $cmd = ['ha-manager', 'migrate', $service, $target]; print "Executing HA migrate for VM $vmid to node $target\n"; @@ -2573,7 +2715,7 @@ __PACKAGE__->register_method({ my $vmid = $param->{vmid}; - my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists + my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists my $res = ''; eval { @@ -2598,12 +2740,12 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), skiplock => get_standard_option('skiplock'), disk => { type => 'string', description => "The disk you want to resize.", - enum => [PVE::QemuServer::disknames()], + enum => [PVE::QemuServer::valid_drive_names()], }, size => { type => 'string', @@ -2644,16 +2786,22 @@ __PACKAGE__->register_method({ my $updatefn = sub { - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); die "checksum missmatch (file change by other user?)\n" if $digest && $digest ne $conf->{digest}; - PVE::QemuServer::check_lock($conf) if !$skiplock; + PVE::QemuConfig->check_lock($conf) if !$skiplock; die "disk '$disk' does not exist\n" if !$conf->{$disk}; my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk}); + my (undef, undef, undef, undef, undef, undef, $format) = + PVE::Storage::parse_volname($storecfg, $drive->{file}); + + die "can't resize volume: $disk if snapshot exists\n" + if %{$conf->{snapshots}} && $format eq 'qcow2'; + my $volid = $drive->{file}; die "disk '$disk' has no associated volume\n" if !$volid; @@ -2664,6 +2812,7 @@ __PACKAGE__->register_method({ $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']); + PVE::Storage::activate_volumes($storecfg, [$volid]); my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5); die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/; @@ -2693,10 +2842,10 @@ __PACKAGE__->register_method({ $drive->{size} = $newsize; $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive); - PVE::QemuServer::update_config_nolock($vmid, $conf, 1); + PVE::QemuConfig->write_config($vmid, $conf); }; - PVE::QemuServer::lock_config($vmid, $updatefn); + PVE::QemuConfig->lock_config($vmid, $updatefn); return undef; }}); @@ -2730,7 +2879,7 @@ __PACKAGE__->register_method({ my $vmid = $param->{vmid}; - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); my $snaphash = $conf->{snapshots} || {}; my $res = []; @@ -2771,7 +2920,7 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), snapname => get_standard_option('pve-snapshot-name'), vmstate => { optional => 1, @@ -2807,7 +2956,7 @@ __PACKAGE__->register_method({ my $realcmd = sub { PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname"); - PVE::QemuServer::snapshot_create($vmid, $snapname, $param->{vmstate}, + PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate}, $param->{description}); }; @@ -2888,9 +3037,9 @@ __PACKAGE__->register_method({ my $updatefn = sub { - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); - PVE::QemuServer::check_lock($conf); + PVE::QemuConfig->check_lock($conf); my $snap = $conf->{snapshots}->{$snapname}; @@ -2898,10 +3047,10 @@ __PACKAGE__->register_method({ $snap->{description} = $param->{description} if defined($param->{description}); - PVE::QemuServer::update_config_nolock($vmid, $conf, 1); + PVE::QemuConfig->write_config($vmid, $conf); }; - PVE::QemuServer::lock_config($vmid, $updatefn); + PVE::QemuConfig->lock_config($vmid, $updatefn); return undef; }}); @@ -2935,7 +3084,7 @@ __PACKAGE__->register_method({ my $snapname = extract_param($param, 'snapname'); - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); my $snap = $conf->{snapshots}->{$snapname}; @@ -2958,7 +3107,7 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), snapname => get_standard_option('pve-snapshot-name'), }, }, @@ -2981,7 +3130,7 @@ __PACKAGE__->register_method({ my $realcmd = sub { PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname"); - PVE::QemuServer::snapshot_rollback($vmid, $snapname); + PVE::QemuConfig->snapshot_rollback($vmid, $snapname); }; return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd); @@ -3001,7 +3150,7 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }), snapname => get_standard_option('pve-snapshot-name'), force => { optional => 1, @@ -3029,7 +3178,7 @@ __PACKAGE__->register_method({ my $realcmd = sub { PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname"); - PVE::QemuServer::snapshot_delete($vmid, $snapname, $param->{force}); + PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force}); }; return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd); @@ -3050,12 +3199,12 @@ __PACKAGE__->register_method({ additionalProperties => 0, properties => { node => get_standard_option('pve-node'), - vmid => get_standard_option('pve-vmid'), + vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }), disk => { optional => 1, type => 'string', description => "If you want to convert only 1 disk to base image.", - enum => [PVE::QemuServer::disknames()], + enum => [PVE::QemuServer::valid_drive_names()], }, }, @@ -3076,15 +3225,15 @@ __PACKAGE__->register_method({ my $updatefn = sub { - my $conf = PVE::QemuServer::load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); - PVE::QemuServer::check_lock($conf); + PVE::QemuConfig->check_lock($conf); die "unable to create template, because VM contains snapshots\n" if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}}); die "you can't convert a template to a template\n" - if PVE::QemuServer::is_template($conf) && !$disk; + if PVE::QemuConfig->is_template($conf) && !$disk; die "you can't convert a VM to template if VM is running\n" if PVE::QemuServer::check_running($vmid); @@ -3094,12 +3243,12 @@ __PACKAGE__->register_method({ }; $conf->{template} = 1; - PVE::QemuServer::update_config_nolock($vmid, $conf, 1); + PVE::QemuConfig->write_config($vmid, $conf); return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd); }; - PVE::QemuServer::lock_config($vmid, $updatefn); + PVE::QemuConfig->lock_config($vmid, $updatefn); return undef; }});