]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
fix #1829: Cloudinit ignore format parameter
[qemu-server.git] / PVE / API2 / Qemu.pm
index d509bb7951e7f9378e4a282e4b991defdc542121..be2551010c8672b89548cee6de40e71377738d24 100644 (file)
@@ -130,7 +130,7 @@ my $check_storage_access_clone = sub {
 # Note: $pool is only needed when creating a VM, because pool permissions
 # are automatically inherited if VM already exists inside a pool.
 my $create_disks = sub {
-    my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
+    my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
 
     my $vollist = [];
 
@@ -149,15 +149,17 @@ my $create_disks = sub {
            die "no storage ID specified (and no default storage)\n" if !$storeid;
            my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
            my $name = "vm-$vmid-cloudinit";
+
            my $fmt = undef;
            if ($scfg->{path}) {
-               $name .= ".qcow2";
-               $fmt = 'qcow2';
-           }else{
-               $fmt = 'raw';
+               $fmt = $disk->{format} ? $disk->{format} : "qcow2";
+               $name .= ".$fmt";
+           } else {
+               $fmt = $disk->{format};
            }
-           # FIXME: Reasonable size? qcow2 shouldn't grow if the space isn't used anyway?
-           my $cloudinit_iso_size = 5; # in MB
+
+           # Initial disk created with 4MB, every time it is regenerated the disk is aligned to 4MB again.
+           my $cloudinit_iso_size = 4; # in MB
            my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, 
                                                  $fmt, $name, $cloudinit_iso_size*1024);
            $disk->{file} = $volid;
@@ -175,7 +177,7 @@ my $create_disks = sub {
 
            my $volid;
            if ($ds eq 'efidisk0') {
-               ($volid, $size) = PVE::QemuServer::create_efidisk($storecfg, $storeid, $vmid, $fmt);
+               ($volid, $size) = PVE::QemuServer::create_efidisk($storecfg, $storeid, $vmid, $fmt, $arch);
            } else {
                $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $size);
            }
@@ -291,16 +293,28 @@ my $diskoptions = {
     'vmstatestorage' => 1,
 };
 
+my $cloudinitoptions = {
+    cicustom => 1,
+    cipassword => 1,
+    citype => 1,
+    ciuser => 1,
+    nameserver => 1,
+    searchdomain => 1,
+    sshkeys => 1,
+};
+
 my $check_vm_modify_config_perm = sub {
     my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
 
     return 1 if $authuser eq 'root@pam';
 
     foreach my $opt (@$key_list) {
-       # disk checks need to be done somewhere else
+       # some checks (e.g., disk, serial port, usb) need to be done somewhere
+       # 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\d+$/;
+       next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
+
 
        if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
@@ -318,10 +332,10 @@ 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 ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
        } else {
-           # catches usb\d+, hostpci\d+, args, lock, etc.
+           # catches hostpci\d+, args, lock, etc.
            # new options will be checked here
            die "only root can set '$opt' config\n";
        }
@@ -356,7 +370,7 @@ __PACKAGE__->register_method({
        type => 'array',
        items => {
            type => "object",
-           properties => {},
+           properties => $PVE::QemuServer::vmstatus_return_properties,
        },
        links => [ { rel => 'child', href => "{vmid}" } ],
     },
@@ -373,7 +387,6 @@ __PACKAGE__->register_method({
            next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
 
            my $data = $vmstatus->{$vmid};
-           $data->{vmid} = int($vmid);
            push @$res, $data;
        }
 
@@ -431,11 +444,18 @@ __PACKAGE__->register_method({
                    description => "Add the VM to the specified pool.",
                },
                bwlimit => {
-                   description => "Override io bandwidth.",
+                   description => "Override I/O bandwidth limit (in KiB/s).",
                    optional => 1,
                    type => 'integer',
                    minimum => '0',
-               }
+                   default => 'restore limit from datacenter or storage config',
+               },
+               start => {
+                   optional => 1,
+                   type => 'boolean',
+                   default => 0,
+                   description => "Start VM after it was created successfully.",
+               },
            }),
     },
     returns => {
@@ -453,6 +473,7 @@ __PACKAGE__->register_method({
        my $vmid = extract_param($param, 'vmid');
 
        my $archive = extract_param($param, 'archive');
+       my $is_restore = !!$archive;
 
        my $storage = extract_param($param, 'storage');
 
@@ -464,6 +485,8 @@ __PACKAGE__->register_method({
 
        my $bwlimit = extract_param($param, 'bwlimit');
 
+       my $start_after_create = extract_param($param, 'start');
+
        my $filename = PVE::QemuConfig->config_file($vmid);
 
        my $storecfg = PVE::Storage::config();
@@ -524,28 +547,18 @@ __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");
+       my $emsg = $is_restore ? "unable to restore VM $vmid -" : "unable to create VM $vmid -";
 
-                   die "unable to restore vm $vmid - config file already exists\n"
-                       if !$force;
+       eval { PVE::QemuConfig->create_and_lock_config($vmid, $force) };
+       die "$emsg $@" if $@;
 
-                   die "unable to restore vm $vmid - vm is running\n"
-                       if PVE::QemuServer::check_running($vmid);
+       my $restorefn = sub {
+           my $conf = PVE::QemuConfig->load_config($vmid);
 
-                   die "unable to restore vm $vmid - vm is a template\n"
-                       if PVE::QemuConfig->is_template($conf);
+           PVE::QemuConfig->check_protection($conf, $emsg);
 
-               } else {
-                   die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
-               }
-           }
+           die "$emsg vm is running\n" if PVE::QemuServer::check_running($vmid);
+           die "$emsg vm is a template\n" if PVE::QemuConfig->is_template($conf);
 
            my $realcmd = sub {
                PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
@@ -555,19 +568,20 @@ __PACKAGE__->register_method({
                    bwlimit => $bwlimit, });
 
                PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
+
+               if ($start_after_create) {
+                   eval { PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node }) };
+                   warn $@ if $@;
+               }
            };
 
            # ensure no old replication state are exists
            PVE::ReplicationState::delete_guest_states($vmid);
 
-           return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
+           return PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
        };
 
        my $createfn = sub {
-
-           # test after locking
-           PVE::Cluster::check_vmid_unused($vmid);
-
            # ensure no old replication state are exists
            PVE::ReplicationState::delete_guest_states($vmid);
 
@@ -577,9 +591,11 @@ __PACKAGE__->register_method({
 
                my $conf = $param;
 
+               my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf);
+
                eval {
 
-                   $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
+                   $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
 
                    if (!$conf->{bootdisk}) {
                        my $firstdisk = PVE::QemuServer::resolve_first_disk($conf);
@@ -591,6 +607,10 @@ __PACKAGE__->register_method({
                        $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();
                    }
 
+                   if ((!defined($conf->{vmgenid}) || $conf->{vmgenid} eq '1') && $arch ne 'aarch64') {
+                       $conf->{vmgenid} = PVE::QemuServer::generate_uuid();
+                   }
+
                    PVE::QemuConfig->write_config($vmid, $conf);
 
                };
@@ -601,16 +621,48 @@ __PACKAGE__->register_method({
                        eval { PVE::Storage::vdisk_free($storecfg, $volid); };
                        warn $@ if $@;
                    }
-                   die "create failed - $err";
+                   die "$emsg $err";
                }
 
                PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
            };
 
-           return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
+           PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
+
+           if ($start_after_create) {
+               print "Execute autostart\n";
+               eval { PVE::API2::Qemu->vm_start({vmid => $vmid, node => $node}) };
+               warn $@ if $@;
+           }
        };
 
-       return PVE::QemuConfig->lock_config_full($vmid, 1, $archive ? $restorefn : $createfn);
+       my ($code, $worker_name);
+       if ($is_restore) {
+           $worker_name = 'qmrestore';
+           $code = sub {
+               eval { $restorefn->() };
+               if (my $err = $@) {
+                   eval { PVE::QemuConfig->remove_lock($vmid, 'create') };
+                   warn $@ if $@;
+                   die $err;
+               }
+           };
+       } else {
+           $worker_name = 'qmcreate';
+           $code = sub {
+               eval { $createfn->() };
+               if (my $err = $@) {
+                   eval {
+                       my $conffile = PVE::QemuConfig->config_file($vmid);
+                       unlink($conffile) or die "failed to remove config file: $!\n";
+                   };
+                   warn $@ if $@;
+                   die $err;
+               }
+           };
+       }
+
+       return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
     }});
 
 __PACKAGE__->register_method({
@@ -777,28 +829,46 @@ __PACKAGE__->register_method({
        properties => {
            node => get_standard_option('pve-node'),
            vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
-            current => {
-                description => "Get current values (instead of pending values).",
-                optional => 1,
+           current => {
+               description => "Get current values (instead of pending values).",
+               optional => 1,
                default => 0,
                type => 'boolean',
-            },
+           },
+           snapshot => get_standard_option('pve-snapshot-name', {
+               description => "Fetch config values from given snapshot.",
+               optional => 1,
+               completion => sub {
+                   my ($cmd, $pname, $cur, $args) = @_;
+                   PVE::QemuConfig->snapshot_list($args->[0]);
+               },
+           }),
        },
     },
     returns => {
+       description => "The current VM configuration.",
        type => "object",
-       properties => {
+       properties => PVE::QemuServer::json_config_properties({
            digest => {
                type => 'string',
                description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
            }
-       },
+       }),
     },
     code => sub {
        my ($param) = @_;
 
        my $conf = PVE::QemuConfig->load_config($param->{vmid});
 
+       if (my $snapname = $param->{snapshot}) {
+           my $snapshot = $conf->{snapshots}->{$snapname};
+           die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
+
+           $snapshot->{digest} = $conf->{digest}; # keep file digest for API
+
+           $conf = $snapshot;
+       }
+
        delete $conf->{snapshots};
 
        if (!$param->{current}) {
@@ -1049,6 +1119,13 @@ my $update_vm_api  = sub {
            # add macaddr
            my $net = PVE::QemuServer::parse_net($param->{$opt});
            $param->{$opt} = PVE::QemuServer::print_net($net);
+       } elsif ($opt eq 'vmgenid') {
+           if ($param->{$opt} eq '1') {
+               $param->{$opt} = PVE::QemuServer::generate_uuid();
+           }
+       } elsif ($opt eq 'hookscript') {
+           eval { PVE::GuestHelpers::check_hookscript($param->{$opt}, $storecfg); };
+           raise_param_exc({ $opt => $@ }) if $@;
        }
     }
 
@@ -1117,6 +1194,22 @@ my $update_vm_api  = sub {
                        if defined($conf->{pending}->{$opt});
                    PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
                    PVE::QemuConfig->write_config($vmid, $conf);
+               } elsif ($opt =~ m/^serial\d+$/) {
+                   if ($conf->{$opt} eq 'socket') {
+                       $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";
+                   }
+                   PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
+                   PVE::QemuConfig->write_config($vmid, $conf);
+               } elsif ($opt =~ m/^usb\d+$/) {
+                   if ($conf->{$opt} =~ 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";
+                   }
+                   PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
+                   PVE::QemuConfig->write_config($vmid, $conf);
                } else {
                    PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
                    PVE::QemuConfig->write_config($vmid, $conf);
@@ -1128,6 +1221,8 @@ my $update_vm_api  = sub {
                $conf = PVE::QemuConfig->load_config($vmid); # update/reload
                next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed
 
+               my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf);
+
                if (PVE::QemuServer::is_valid_drivename($opt)) {
                    my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
                    # FIXME: cloudinit: CDROM or Disk?
@@ -1139,7 +1234,21 @@ my $update_vm_api  = sub {
                    PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
                        if defined($conf->{pending}->{$opt});
 
-                   &$create_disks($rpcenv, $authuser, $conf->{pending}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
+                   &$create_disks($rpcenv, $authuser, $conf->{pending}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
+               } elsif ($opt =~ m/^serial\d+/) {
+                   if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
+                       $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";
+                   }
+                   $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";
+                   }
+                   $conf->{pending}->{$opt} = $param->{$opt};
                } else {
                    $conf->{pending}->{$opt} = $param->{$opt};
                }
@@ -1471,6 +1580,7 @@ __PACKAGE__->register_method({
        my $websocket = $param->{websocket};
 
        my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists
+       my $use_serial = ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/));
 
        my $authpath = "/vms/$vmid";
 
@@ -1479,13 +1589,14 @@ __PACKAGE__->register_method({
        $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
            if !$sslcert;
 
-       my ($remip, $family);
+       my $family;
        my $remcmd = [];
 
        if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
-           ($remip, $family) = PVE::Cluster::remote_node_ip($node);
+           (undef, $family) = PVE::Cluster::remote_node_ip($node);
+           my $sshinfo = PVE::Cluster::get_ssh_info($node);
            # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
-           $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
+           $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, $use_serial ? '-t' : '-T');
        } else {
            $family = PVE::Tools::get_host_address_family($node);
        }
@@ -1501,8 +1612,7 @@ __PACKAGE__->register_method({
 
            my $cmd;
 
-           if ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/)) {
-
+           if ($use_serial) {
 
                my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga}, '-escape', '0' ];
 
@@ -1619,19 +1729,20 @@ __PACKAGE__->register_method({
 
        my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
 
-       my ($remip, $family);
+       my $family;
+       my $remcmd = [];
 
        if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
-           ($remip, $family) = PVE::Cluster::remote_node_ip($node);
+           (undef, $family) = PVE::Cluster::remote_node_ip($node);
+           my $sshinfo = PVE::Cluster::get_ssh_info($node);
+           $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, '-t');
+           push @$remcmd, '--';
        } else {
            $family = PVE::Tools::get_host_address_family($node);
        }
 
        my $port = PVE::Tools::next_vnc_port($family);
 
-       my $remcmd = $remip ?
-           ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
-
        my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
        push @$termcmd, '-iface', $serial if $serial;
 
@@ -1820,7 +1931,26 @@ __PACKAGE__->register_method({
            vmid => get_standard_option('pve-vmid'),
        },
     },
-    returns => { type => 'object' },
+    returns => {
+       type => 'object',
+       properties => {
+           %$PVE::QemuServer::vmstatus_return_properties,
+           ha => {
+               description => "HA manager service status.",
+               type => 'object',
+           },
+           spice => {
+               description => "Qemu VGA configuration supports spice.",
+               type => 'boolean',
+               optional => 1,
+           },
+           agent => {
+               description => "Qemu GuestAgent enabled in config.",
+               type => 'boolean',
+               optional => 1,
+           },
+       },
+    },
     code => sub {
        my ($param) = @_;
 
@@ -1833,8 +1963,7 @@ __PACKAGE__->register_method({
        $status->{ha} = PVE::HA::Config::get_service_status("vm:$param->{vmid}");
 
        $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
-
-       $status->{agent} = 1 if $conf->{agent};
+       $status->{agent} = 1 if (PVE::QemuServer::parse_guest_agent($conf)->{enabled});
 
        return $status;
     }});
@@ -2255,6 +2384,18 @@ __PACKAGE__->register_method({
            vmid => get_standard_option('pve-vmid',
                                        { completion => \&PVE::QemuServer::complete_vmid_running }),
            skiplock => get_standard_option('skiplock'),
+           todisk => {
+               type => 'boolean',
+               default => 0,
+               optional => 1,
+               description => 'If set, suspends the VM to disk. Will be resumed on next VM start.',
+           },
+           statestorage => get_standard_option('pve-storage-id', {
+               description => "The storage for the VM state",
+               requires => 'todisk',
+               optional => 1,
+               completion => \&PVE::Storage::complete_storage_enabled,
+           }),
        },
     },
     returns => {
@@ -2271,23 +2412,32 @@ __PACKAGE__->register_method({
 
        my $vmid = extract_param($param, 'vmid');
 
+       my $todisk = extract_param($param, 'todisk') // 0;
+
+       my $statestorage = extract_param($param, 'statestorage');
+
        my $skiplock = extract_param($param, 'skiplock');
        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);
 
+       die "Cannot suspend HA managed VM to disk\n"
+           if $todisk && PVE::HA::Config::vm_is_ha_managed($vmid);
+
+       my $taskname = $todisk ? 'qmsuspend' : 'qmpause';
+
        my $realcmd = sub {
            my $upid = shift;
 
            syslog('info', "suspend VM $vmid: $upid\n");
 
-           PVE::QemuServer::vm_suspend($vmid, $skiplock);
+           PVE::QemuServer::vm_suspend($vmid, $skiplock, $todisk, $statestorage);
 
            return;
        };
 
-       return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
+       return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
     }});
 
 __PACKAGE__->register_method({
@@ -2331,14 +2481,28 @@ __PACKAGE__->register_method({
 
        my $nocheck = extract_param($param, 'nocheck');
 
-       die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid, $nocheck);
+       my $to_disk_suspended;
+       eval {
+           PVE::QemuConfig->lock_config($vmid, sub {
+               my $conf = PVE::QemuConfig->load_config($vmid);
+               $to_disk_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');
+           });
+       };
+
+       die "VM $vmid not running\n"
+           if !$to_disk_suspended && !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, $nocheck);
+           if (!$to_disk_suspended) {
+               PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
+           } else {
+               my $storecfg = PVE::Storage::config();
+               PVE::QemuServer::vm_start($storecfg, $vmid, undef, $skiplock);
+           }
 
            return;
        };
@@ -2522,6 +2686,13 @@ __PACKAGE__->register_method({
                description => "Target node. Only allowed if the original VM is on shared storage.",
                optional => 1,
            }),
+           bwlimit => {
+               description => "Override I/O bandwidth limit (in KiB/s).",
+               optional => 1,
+               type => 'integer',
+               minimum => '0',
+               default => 'clone limit from datacenter or storage config',
+           },
         },
     },
     returns => {
@@ -2665,13 +2836,15 @@ __PACKAGE__->register_method({
            }
 
             # auto generate a new uuid
-            my ($uuid, $uuid_str);
-            UUID::generate($uuid);
-            UUID::unparse($uuid, $uuid_str);
            my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
-           $smbios1->{uuid} = $uuid_str;
+           $smbios1->{uuid} = PVE::QemuServer::generate_uuid();
            $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
 
+           # auto generate a new vmgenid if the option was set
+           if ($newconf->{vmgenid}) {
+               $newconf->{vmgenid} = PVE::QemuServer::generate_uuid();
+           }
+
            delete $newconf->{template};
 
            if ($param->{name}) {
@@ -2705,6 +2878,8 @@ __PACKAGE__->register_method({
 
                    PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
 
+                   my $bwlimit = extract_param($param, 'bwlimit');
+
                    my $total_jobs = scalar(keys %{$drives});
                    my $i = 1;
 
@@ -2712,9 +2887,14 @@ __PACKAGE__->register_method({
                        my $drive = $drives->{$opt};
                        my $skipcomplete = ($total_jobs != $i); # finish after last drive
 
+                       my $src_sid = PVE::Storage::parse_volume_id($drive->{file});
+                       my $storage_list = [ $src_sid ];
+                       push @$storage_list, $storage if defined($storage);
+                       my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', $storage_list, $bwlimit);
+
                        my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
                                                                   $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
-                                                                  $jobs, $skipcomplete, $oldconf->{agent});
+                                                                  $jobs, $skipcomplete, $oldconf->{agent}, $clonelimit);
 
                        $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
 
@@ -2723,6 +2903,14 @@ __PACKAGE__->register_method({
                    }
 
                    delete $newconf->{lock};
+
+                   # do not write pending changes
+                   if (my @changes = keys %{$newconf->{pending}}) {
+                       my $pending = join(',', @changes);
+                       warn "found pending changes for '$pending', discarding for clone\n";
+                       delete $newconf->{pending};
+                   }
+
                    PVE::QemuConfig->write_config($newid, $newconf);
 
                     if ($target) {
@@ -2812,6 +3000,13 @@ __PACKAGE__->register_method({
                maxLength => 40,
                optional => 1,
            },
+           bwlimit => {
+               description => "Override I/O bandwidth limit (in KiB/s).",
+               optional => 1,
+               type => 'integer',
+               minimum => '0',
+               default => 'move limit from datacenter or storage config',
+           },
        },
     },
     returns => {
@@ -2889,8 +3084,11 @@ __PACKAGE__->register_method({
                    warn "moving disk with snapshots, snapshots will not be moved!\n"
                        if $snapshotted;
 
+                   my $bwlimit = extract_param($param, 'bwlimit');
+                   my $movelimit = PVE::Storage::get_bandwidth_limit('move', [$oldstoreid, $storeid], $bwlimit);
+
                    my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
-                                                              $vmid, $storeid, $format, 1, $newvollist);
+                                                              $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
 
                    $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
 
@@ -2902,6 +3100,10 @@ __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 { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fstrim"); };
+                   }
+
                    eval {
                        # try to deactivate volumes - avoid lvm LVs to be active on several nodes
                        PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
@@ -2983,6 +3185,13 @@ __PACKAGE__->register_method({
                optional => 1,
                completion => \&PVE::QemuServer::complete_storage,
             }),
+           bwlimit => {
+               description => "Override I/O bandwidth limit (in KiB/s).",
+               optional => 1,
+               type => 'integer',
+               minimum => '0',
+               default => 'migrate limit from datacenter or storage config',
+           },
        },
     },
     returns => {
@@ -3270,7 +3479,32 @@ __PACKAGE__->register_method({
        type => 'array',
        items => {
            type => "object",
-           properties => {},
+           properties => {
+               name => {
+                   description => "Snapshot identifier. Value 'current' identifies the current VM.",
+                   type => 'string',
+               },
+               vmstate => {
+                   description => "Snapshot includes RAM.",
+                   type => 'boolean',
+                   optional => 1,
+               },
+               description => {
+                   description => "Snapshot description.",
+                   type => 'string',
+               },
+               snaptime => {
+                   description => "Snapshot creation time",
+                   type => 'integer',
+                   renderer => 'timestamp',
+                   optional => 1,
+               },
+               parent => {
+                   description => "Parent snapshot identifier.",
+                   type => 'string',
+                   optional => 1,
+               },
+           },
        },
        links => [ { rel => 'child', href => "{name}" } ],
     },
@@ -3298,7 +3532,12 @@ __PACKAGE__->register_method({
        }
 
        my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
-       my $current = { name => 'current', digest => $conf->{digest}, running => $running };
+       my $current = {
+           name => 'current',
+           digest => $conf->{digest},
+           running => $running,
+           description => "You are here!",
+       };
        $current->{parent} = $conf->{parent} if $conf->{parent};
 
        push @$res, $current;