]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
fix #2580: api/delete: drop VM from HA resources if purge is set
[qemu-server.git] / PVE / API2 / Qemu.pm
index e853a8355f4456f662cb2b4cc619dffa47c77219..d8d3f3e6b312b6fa7e4e6a357c167736c9473147 100644 (file)
@@ -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;
@@ -400,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({
@@ -422,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,
@@ -540,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);
            }
        }
 
@@ -559,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)) {
@@ -593,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 $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;
                    }
 
@@ -827,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' ]],
     },
@@ -853,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 => {
@@ -885,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' ]],
     },
@@ -1444,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);
@@ -1470,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
@@ -1993,7 +2028,14 @@ __PACKAGE__->register_method({
                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 => {
@@ -2007,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');
 
@@ -2060,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;
            };
 
@@ -2730,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;
 
@@ -2773,7 +2808,7 @@ __PACKAGE__->register_method({
            }
        }
 
-        PVE::Cluster::check_cfs_quorum();
+       PVE::Cluster::check_cfs_quorum();
 
        my $running = PVE::QemuServer::check_running($vmid) || 0;
 
@@ -2781,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;
@@ -2811,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;
 
@@ -2867,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();
            }
@@ -2878,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}) {
@@ -2890,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 {
@@ -2954,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";
                }
 
@@ -3004,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.",
@@ -3045,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};
 
@@ -3100,7 +3118,6 @@ __PACKAGE__->register_method({
            PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
 
            my $realcmd = sub {
-
                my $newvollist = [];
 
                eval {
@@ -3128,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 {
@@ -3140,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";
                 }
 
@@ -3511,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',
@@ -3607,7 +3624,8 @@ __PACKAGE__->register_method({
 
            PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
 
-           $drive->{size} = $newsize;
+           my $effective_size = eval { PVE::Storage::volume_size_info($storecfg, $volid, 3); };
+           $drive->{size} = $effective_size // $newsize;
            $conf->{$disk} = PVE::QemuServer::print_drive($drive);
 
            PVE::QemuConfig->write_config($vmid, $conf);
@@ -3863,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,
@@ -4010,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()],
            },
 
        },