]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
fix #4522: api: vncproxy: also set environment variable for ticket without websocket
[qemu-server.git] / PVE / API2 / Qemu.pm
index 6836c5570cc7d6a68b731341964b61e3e27dcfe7..9877ce24a9b7e2e581d894881787e29aa51657d2 100644 (file)
@@ -13,6 +13,7 @@ use Crypt::OpenSSL::Random;
 use Socket qw(SOCK_STREAM);
 
 use PVE::APIClient::LWP;
+use PVE::CGroup;
 use PVE::Cluster qw (cfs_read_file cfs_write_file);;
 use PVE::RRD;
 use PVE::SafeSyslog;
@@ -31,6 +32,9 @@ use PVE::QemuServer::Drive;
 use PVE::QemuServer::ImportDisk;
 use PVE::QemuServer::Monitor qw(mon_cmd);
 use PVE::QemuServer::Machine;
+use PVE::QemuServer::Memory qw(get_current_memory);
+use PVE::QemuServer::PCI;
+use PVE::QemuServer::USB;
 use PVE::QemuMigrate;
 use PVE::RPCEnvironment;
 use PVE::AccessControl;
@@ -559,6 +563,7 @@ my $cloudinitoptions = {
     cipassword => 1,
     citype => 1,
     ciuser => 1,
+    ciupgrade => 1,
     nameserver => 1,
     searchdomain => 1,
     sshkeys => 1,
@@ -582,19 +587,64 @@ my $check_vm_create_serial_perm = sub {
     return 1;
 };
 
-my $check_vm_create_usb_perm = sub {
+my sub check_usb_perm {
+    my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
+
+    return 1 if $authuser eq 'root@pam';
+
+    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
+
+    my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb', $value);
+    if ($device->{host} && $device->{host} !~ m/^spice$/i) {
+       die "only root can set '$opt' config for real devices\n";
+    } elsif ($device->{mapping}) {
+       $rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
+    } else {
+       die "either 'host' or 'mapping' must be set.\n";
+    }
+
+    return 1;
+}
+
+my sub check_vm_create_usb_perm {
     my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
 
     return 1 if $authuser eq 'root@pam';
 
     foreach my $opt (keys %{$param}) {
        next if $opt !~ m/^usb\d+$/;
+       check_usb_perm($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
+    }
 
-       if ($param->{$opt} =~ m/spice/) {
-           $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
-       } else {
-           die "only root can set '$opt' config for real devices\n";
-       }
+    return 1;
+};
+
+my sub check_hostpci_perm {
+    my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_;
+
+    return 1 if $authuser eq 'root@pam';
+
+    my $device = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $value);
+    if ($device->{host}) {
+       die "only root can set '$opt' config for non-mapped devices\n";
+    } elsif ($device->{mapping}) {
+       $rpcenv->check_full($authuser, "/mapping/pci/$device->{mapping}", ['Mapping.Use']);
+       $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
+    } else {
+       die "either 'host' or 'mapping' must be set.\n";
+    }
+
+    return 1;
+}
+
+my sub check_vm_create_hostpci_perm {
+    my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
+
+    return 1 if $authuser eq 'root@pam';
+
+    foreach my $opt (keys %{$param}) {
+       next if $opt !~ m/^hostpci\d+$/;
+       check_hostpci_perm($rpcenv, $authuser, $vmid, $pool, $opt, $param->{$opt});
     }
 
     return 1;
@@ -610,7 +660,7 @@ my $check_vm_modify_config_perm = sub {
        # else, as there the permission can be value dependend
        next if PVE::QemuServer::is_valid_drivename($opt);
        next if $opt eq 'cdrom';
-       next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
+       next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
        next if $opt eq 'tags';
 
 
@@ -630,16 +680,16 @@ my $check_vm_modify_config_perm = sub {
            $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|ipconfig)\d+$/) {
+       } elsif ($opt =~ m/^net\d+$/) {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
-       } elsif ($cloudinitoptions->{$opt}) {
+       } elsif ($cloudinitoptions->{$opt} || $opt =~ m/^ipconfig\d+$/) {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
        } 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.
+           # catches args, lock, etc.
            # new options will be checked here
            die "only root can set '$opt' config\n";
        }
@@ -702,20 +752,20 @@ my $parse_restore_archive = sub {
 
     my ($archive_storeid, $archive_volname) = PVE::Storage::parse_volume_id($archive, 1);
 
+    my $res = {};
+
     if (defined($archive_storeid)) {
        my $scfg =  PVE::Storage::storage_config($storecfg, $archive_storeid);
+       $res->{volid} = $archive;
        if ($scfg->{type} eq 'pbs') {
-           return {
-               type => 'pbs',
-               volid => $archive,
-           };
+           $res->{type} = 'pbs';
+           return $res;
        }
     }
     my $path = PVE::Storage::abs_filesystem_path($storecfg, $archive);
-    return {
-       type => 'file',
-       path => $path,
-    };
+    $res->{type} = 'file';
+    $res->{path} = $path;
+    return $res;
 };
 
 
@@ -727,7 +777,8 @@ __PACKAGE__->register_method({
     permissions => {
        description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
            "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
-           "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
+           "If you create disks you need 'Datastore.AllocateSpace' on any used storage." .
+           "If you use a bridge/vlan, you need 'SDN.Use' on any used bridge/vlan.",
         user => 'all', # check inside
     },
     protected => 1,
@@ -818,7 +869,7 @@ __PACKAGE__->register_method({
                PVE::Tools::validate_ssh_public_keys($ssh_keys);
        }
 
-       $param->{cpuunits} = PVE::GuestHelpers::get_cpuunits($param->{cpuunits})
+       $param->{cpuunits} = PVE::CGroup::clamp_cpu_shares($param->{cpuunits})
            if defined($param->{cpuunits}); # clamp value depending on cgroup version
 
        PVE::Cluster::check_cfs_quorum();
@@ -839,7 +890,7 @@ __PACKAGE__->register_method({
            # OK
        } elsif ($archive && $force && (-f $filename) &&
                 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
-           # OK: user has VM.Backup permissions, and want to restore an existing VM
+           # OK: user has VM.Backup permissions and wants to restore an existing VM
        } else {
            raise_perm_exc();
        }
@@ -876,8 +927,10 @@ __PACKAGE__->register_method({
            &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
 
            &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
-           &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
+           check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
+           check_vm_create_hostpci_perm($rpcenv, $authuser, $vmid, $pool, $param);
 
+           PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $param);
            &$check_cpu_model_access($rpcenv, $authuser, $param);
 
            $check_drive_param->($param, $storecfg);
@@ -907,6 +960,19 @@ __PACKAGE__->register_method({
                    live => $live_restore,
                    override_conf => $param,
                };
+               if (my $volid = $archive->{volid}) {
+                   # best effort, real check is after restoring!
+                   my $merged = eval {
+                       my $old_conf = PVE::Storage::extract_vzdump_config($storecfg, $volid);
+                       PVE::QemuServer::restore_merge_config("backup/qemu-server/$vmid.conf", $old_conf, $param);
+                   };
+                   if ($@) {
+                       warn "Could not extract backed up config: $@\n";
+                       warn "Skipping early checks!\n";
+                   } else {
+                       PVE::QemuServer::check_restore_permissions($rpcenv, $authuser, $merged);
+                   }
+               }
                if ($archive->{type} eq 'file' || $archive->{type} eq 'pipe') {
                    die "live-restore is only compatible with backup images from a Proxmox Backup Server\n"
                        if $live_restore;
@@ -1343,16 +1409,23 @@ __PACKAGE__->register_method({
                    description => "Configuration option name.",
                    type => 'string',
                },
-               old => {
+               value => {
                    description => "Value as it was used to generate the current cloudinit image.",
                    type => 'string',
                    optional => 1,
                },
-               new => {
+               pending => {
                    description => "The new pending value.",
                    type => 'string',
                    optional => 1,
                },
+               delete => {
+                   description => "Indicates a pending delete request if present and not 0. ",
+                   type => 'integer',
+                   minimum => 0,
+                   maximum => 1,
+                   optional => 1,
+               },
            },
        },
     },
@@ -1364,26 +1437,39 @@ __PACKAGE__->register_method({
 
        my $ci = $conf->{cloudinit};
 
-       my $res = {};
+       $conf->{cipassword} = '**********' if exists $conf->{cipassword};
+       $ci->{cipassword} = '**********' if exists $ci->{cipassword};
+
+       my $res = [];
+
+       # All the values that got added
        my $added = delete($ci->{added}) // '';
        for my $key (PVE::Tools::split_list($added)) {
-           $res->{$key} = { new => $conf->{$key} };
+           push @$res, { key => $key, pending => $conf->{$key} };
        }
 
-       for my $key (keys %$ci) {
-           if (!exists($conf->{$key})) {
-               $res->{$key} = { old => $ci->{$key} };
+       # All already existing values (+ their new value, if it exists)
+       for my $opt (keys %$cloudinitoptions) {
+           next if !$conf->{$opt};
+           next if $added =~ m/$opt/;
+           my $item = {
+               key => $opt,
+           };
+
+           if (my $pending = $ci->{$opt}) {
+               $item->{value} = $pending;
+               $item->{pending} = $conf->{$opt};
            } else {
-               $res->{$key} = {
-                   old => $ci->{$key},
-                   new => $conf->{$key},
-               };
+               $item->{value} = $conf->{$opt},
            }
+
+           push @$res, $item;
        }
 
-       if (defined(my $pw = $res->{cipassword})) {
-           $pw->{old} = '**********' if exists $pw->{old};
-           $pw->{new} = '**********' if exists $pw->{new};
+       # Now, we'll find the deleted ones
+       for my $opt (keys %$ci) {
+           next if $conf->{$opt};
+           push @$res, { key => $opt, delete => 1 };
        }
 
        return $res;
@@ -1397,7 +1483,7 @@ __PACKAGE__->register_method({
     proxyto => 'node',
     description => "Regenerate and change cloudinit config drive.",
     permissions => {
-       check => ['perm', '/vms/{vmid}', 'VM.Config.Cloudinit'],
+       check => ['perm', '/vms/{vmid}', ['VM.Config.Cloudinit']],
     },
     parameters => {
        additionalProperties => 0,
@@ -1481,15 +1567,13 @@ my $update_vm_api  = sub {
        PVE::Tools::validate_ssh_public_keys($ssh_keys);
     }
 
-    $param->{cpuunits} = PVE::GuestHelpers::get_cpuunits($param->{cpuunits})
+    $param->{cpuunits} = PVE::CGroup::clamp_cpu_shares($param->{cpuunits})
        if defined($param->{cpuunits}); # clamp value depending on cgroup version
 
     die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
 
     my $storecfg = PVE::Storage::config();
 
-    my $defaults = PVE::QemuServer::load_defaults();
-
     &$resolve_cdrom_alias($param);
 
     # now try to verify all parameters
@@ -1577,6 +1661,8 @@ my $update_vm_api  = sub {
 
     &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
 
+    PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $param);
+
     my $updatefn =  sub {
 
        my $conf = PVE::QemuConfig->load_config($vmid);
@@ -1607,7 +1693,9 @@ my $update_vm_api  = sub {
        }
 
        if ($param->{memory} || defined($param->{balloon})) {
-           my $maxmem = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory} || $defaults->{memory};
+
+           my $memory = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory};
+           my $maxmem = get_current_memory($memory);
            my $balloon = defined($param->{balloon}) ? $param->{balloon} : $conf->{pending}->{balloon} || $conf->{balloon};
 
            die "balloon value too large (must be smaller than assigned memory)\n"
@@ -1695,17 +1783,27 @@ my $update_vm_api  = sub {
                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
                    PVE::QemuConfig->write_config($vmid, $conf);
                } elsif ($opt =~ m/^usb\d+$/) {
-                   if ($val =~ m/spice/) {
-                       $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
-                   } elsif ($authuser ne 'root@pam') {
-                       die "only root can delete '$opt' config for real devices\n";
-                   }
+                   check_usb_perm($rpcenv, $authuser, $vmid, undef, $opt, $val);
+                   PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
+                   PVE::QemuConfig->write_config($vmid, $conf);
+               } elsif ($opt =~ m/^hostpci\d+$/) {
+                   check_hostpci_perm($rpcenv, $authuser, $vmid, undef, $opt, $val);
                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
                    PVE::QemuConfig->write_config($vmid, $conf);
                } elsif ($opt eq 'tags') {
                    assert_tag_permissions($vmid, $val, '', $rpcenv, $authuser);
                    delete $conf->{$opt};
                    PVE::QemuConfig->write_config($vmid, $conf);
+               } elsif ($opt =~ m/^net\d+$/) {
+                   if ($conf->{$opt}) {
+                       PVE::QemuServer::check_bridge_access(
+                           $rpcenv,
+                           $authuser,
+                           { $opt => $conf->{$opt} },
+                       );
+                   }
+                   PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
+                   PVE::QemuConfig->write_config($vmid, $conf);
                } else {
                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
                    PVE::QemuConfig->write_config($vmid, $conf);
@@ -1760,14 +1858,28 @@ my $update_vm_api  = sub {
                    }
                    $conf->{pending}->{$opt} = $param->{$opt};
                } elsif ($opt =~ m/^usb\d+/) {
-                   if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
-                       $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
-                   } elsif ($authuser ne 'root@pam') {
-                       die "only root can modify '$opt' config for real devices\n";
+                   if (my $olddevice = $conf->{$opt}) {
+                       check_usb_perm($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});
+                   }
+                   check_usb_perm($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
+                   $conf->{pending}->{$opt} = $param->{$opt};
+               } elsif ($opt =~ m/^hostpci\d+$/) {
+                   if (my $oldvalue = $conf->{$opt}) {
+                       check_hostpci_perm($rpcenv, $authuser, $vmid, undef, $opt, $oldvalue);
                    }
+                   check_hostpci_perm($rpcenv, $authuser, $vmid, undef, $opt, $param->{$opt});
                    $conf->{pending}->{$opt} = $param->{$opt};
                } elsif ($opt eq 'tags') {
                    assert_tag_permissions($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
+                   $conf->{pending}->{$opt} = PVE::GuestHelpers::get_unique_tags($param->{$opt});
+               } elsif ($opt =~ m/^net\d+$/) {
+                   if ($conf->{$opt}) {
+                       PVE::QemuServer::check_bridge_access(
+                           $rpcenv,
+                           $authuser,
+                           { $opt => $conf->{$opt} },
+                       );
+                   }
                    $conf->{pending}->{$opt} = $param->{$opt};
                } else {
                    $conf->{pending}->{$opt} = $param->{$opt};
@@ -2155,7 +2267,8 @@ __PACKAGE__->register_method({
            websocket => {
                optional => 1,
                type => 'boolean',
-               description => "starts websockify instead of vncproxy",
+               description => "Prepare for websocket upgrade (only required when using "
+                   ."serial terminal, otherwise upgrade is always possible).",
            },
            'generate-password' => {
                optional => 1,
@@ -2253,7 +2366,7 @@ __PACKAGE__->register_method({
 
            } else {
 
-               $ENV{LC_PVE_TICKET} = $password if $websocket; # set ticket with "qm vncproxy"
+               $ENV{LC_PVE_TICKET} = $password; # set ticket with "qm vncproxy"
 
                $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
 
@@ -2570,12 +2683,12 @@ __PACKAGE__->register_method({
                type => 'object',
            },
            spice => {
-               description => "Qemu VGA configuration supports spice.",
+               description => "QEMU VGA configuration supports spice.",
                type => 'boolean',
                optional => 1,
            },
            agent => {
-               description => "Qemu GuestAgent enabled in config.",
+               description => "QEMU Guest Agent is enabled in config.",
                type => 'boolean',
                optional => 1,
            },
@@ -2969,12 +3082,8 @@ __PACKAGE__->register_method({
 
        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
+       # sending a graceful shutdown command to paused VMs runs into timeouts, and even worse, when
+       # the VM gets resumed later, it still gets the request delivered and powers off
        if (PVE::QemuServer::vm_is_paused($vmid)) {
            if ($param->{forceStop}) {
                warn "VM is paused - stop instead of shutdown\n";
@@ -3199,6 +3308,8 @@ __PACKAGE__->register_method({
        raise_param_exc({ skiplock => "Only root may use this option." })
            if $skiplock && $authuser ne 'root@pam';
 
+       # nocheck is used as part of migration when config file might be still
+       # be on source node
        my $nocheck = extract_param($param, 'nocheck');
        raise_param_exc({ nocheck => "Only root may use this option." })
            if $nocheck && $authuser ne 'root@pam';
@@ -3352,7 +3463,7 @@ __PACKAGE__->register_method({
     permissions => {
        description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
            "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
-           "'Datastore.AllocateSpace' on any used storage.",
+           "'Datastore.AllocateSpace' on any used storage and 'SDN.Use' on any used bridge/vnet",
        check =>
        [ 'and',
          ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
@@ -3485,6 +3596,9 @@ __PACKAGE__->register_method({
            my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
 
            my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
+           PVE::QemuServer::check_mapping_access($rpcenv, $authuser, $oldconf);
+
+           PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $oldconf);
 
            die "can't clone VM to node '$target' (VM uses local storage)\n"
                if $target && !$sharedvm;
@@ -4204,7 +4318,11 @@ __PACKAGE__->register_method({
            local_resources => {
                type => 'array',
                description => "List local resources e.g. pci, usb"
-           }
+           },
+           'mapped-resources' => {
+               type => 'array',
+               description => "List of mapped resources e.g. pci, usb"
+           },
        },
     },
     code => sub {
@@ -4233,7 +4351,11 @@ __PACKAGE__->register_method({
 
        $res->{running} = PVE::QemuServer::check_running($vmid) ? 1:0;
 
-       # if vm is not running, return target nodes where local storage is available
+       my ($local_resources, $mapped_resources, $missing_mappings_by_node) =
+           PVE::QemuServer::check_local_resources($vmconf, 1);
+       delete $missing_mappings_by_node->{$localnode};
+
+       # if vm is not running, return target nodes where local storage/mapped devices are available
        # for offline migration
        if (!$res->{running}) {
            $res->{allowed_nodes} = [];
@@ -4241,7 +4363,13 @@ __PACKAGE__->register_method({
            delete $checked_nodes->{$localnode};
 
            foreach my $node (keys %$checked_nodes) {
-               if (!defined $checked_nodes->{$node}->{unavailable_storages}) {
+               my $missing_mappings = $missing_mappings_by_node->{$node};
+               if (scalar($missing_mappings->@*)) {
+                   $checked_nodes->{$node}->{'unavailable-resources'} = $missing_mappings;
+                   next;
+               }
+
+               if (!defined($checked_nodes->{$node}->{unavailable_storages})) {
                    push @{$res->{allowed_nodes}}, $node;
                }
 
@@ -4249,13 +4377,11 @@ __PACKAGE__->register_method({
            $res->{not_allowed_nodes} = $checked_nodes;
        }
 
-
        my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
        $res->{local_disks} = [ values %$local_disks ];;
 
-       my $local_resources =  PVE::QemuServer::check_local_resources($vmconf, 1);
-
        $res->{local_resources} = $local_resources;
+       $res->{'mapped-resources'} = $mapped_resources;
 
        return $res;
 
@@ -4543,17 +4669,6 @@ __PACKAGE__->register_method({
            $param->{online} = 0;
        }
 
-       # FIXME: fork worker hear to avoid timeout? or poll these periodically
-       # in pvestatd and access cached info here? all of the below is actually
-       # checked at the remote end anyway once we call the mtunnel endpoint,
-       # we could also punt it to the client and not do it here at all..
-       my $resources = $api_client->get("/cluster/resources", { type => 'vm' });
-       if (grep { defined($_->{vmid}) && $_->{vmid} eq $target_vmid } @$resources) {
-           raise_param_exc({ target_vmid => "Guest with ID '$target_vmid' already exists on remote cluster" });
-       }
-
-       my $storages = $api_client->get("/nodes/localhost/storage", { enabled => 1 });
-
        my $storecfg = PVE::Storage::config();
        my $target_storage = extract_param($param, 'target-storage');
        my $storagemap = eval { PVE::JSONSchema::parse_idmap($target_storage, 'pve-storage-id') };
@@ -4565,26 +4680,6 @@ __PACKAGE__->register_method({
        raise_param_exc({ 'target-bridge' => "failed to parse bridge map: $@" })
            if $@;
 
-       my $check_remote_storage = sub {
-           my ($storage) = @_;
-           my $found = [ grep { $_->{storage} eq $storage } @$storages ];
-           die "remote: storage '$storage' does not exist!\n"
-               if !@$found;
-
-           $found = @$found[0];
-
-           my $content_types = [ PVE::Tools::split_list($found->{content}) ];
-           die "remote: storage '$storage' cannot store images\n"
-               if !grep { $_ eq 'images' } @$content_types;
-       };
-
-       foreach my $target_sid (values %{$storagemap->{entries}}) {
-           $check_remote_storage->($target_sid);
-       }
-
-       $check_remote_storage->($storagemap->{default})
-           if $storagemap->{default};
-
        die "remote migration requires explicit storage mapping!\n"
            if $storagemap->{identity};
 
@@ -4629,7 +4724,7 @@ __PACKAGE__->register_method({
     method => 'POST',
     protected => 1,
     proxyto => 'node',
-    description => "Execute Qemu monitor commands.",
+    description => "Execute QEMU monitor commands.",
     permissions => {
        description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
         check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
@@ -4708,7 +4803,10 @@ __PACKAGE__->register_method({
            },
        },
     },
-    returns => { type => 'null'},
+    returns => {
+       type => 'string',
+       description => "the task ID.",
+    },
     code => sub {
         my ($param) = @_;
 
@@ -4747,9 +4845,6 @@ __PACKAGE__->register_method({
            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;
@@ -4795,8 +4890,11 @@ __PACKAGE__->register_method({
            PVE::QemuConfig->write_config($vmid, $conf);
        };
 
-        PVE::QemuConfig->lock_config($vmid, $updatefn);
-        return;
+       my $worker = sub {
+           PVE::QemuConfig->lock_config($vmid, $updatefn);
+       };
+
+       return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
     }});
 
 __PACKAGE__->register_method({
@@ -5094,7 +5192,8 @@ __PACKAGE__->register_method({
            snapname => get_standard_option('pve-snapshot-name'),
            start => {
                type => 'boolean',
-               description => "Whether the VM should get started after rolling back successfully",
+               description => "Whether the VM should get started after rolling back successfully."
+                   . " (Note: VMs will be automatically started if the snapshot includes RAM.)",
                optional => 1,
                default => 0,
            },
@@ -5121,7 +5220,7 @@ __PACKAGE__->register_method({
            PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
            PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
 
-           if ($param->{start}) {
+           if ($param->{start} && !PVE::QemuServer::Helpers::vm_running_locally($vmid)) {
                PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node });
            }
        };
@@ -5553,7 +5652,6 @@ __PACKAGE__->register_method({
                        'disk' => [
                            undef,
                            $storeid,
-                           undef,
                            $drive,
                            0,
                            $format,