]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
Allow VMAdmin to delete disk with Datastore.AllocateSpace permissions
[qemu-server.git] / PVE / API2 / Qemu.pm
index d33af929ffb6ba9e63ad76977198d8408bdee9d3..bf16bf23c2c4b435ddfe5e410f04142a82251dd9 100644 (file)
@@ -3,11 +3,12 @@ package PVE::API2::Qemu;
 use strict;
 use warnings;
 use Cwd 'abs_path';
+use Net::SSLeay;
 
 use PVE::Cluster qw (cfs_read_file cfs_write_file);;
 use PVE::SafeSyslog;
 use PVE::Tools qw(extract_param);
-use PVE::Exception qw(raise raise_param_exc);
+use PVE::Exception qw(raise raise_param_exc raise_perm_exc);
 use PVE::Storage;
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::RESTHandler;
@@ -59,7 +60,7 @@ my $check_storage_access = sub {
     });
 };
 
-my $check_storage_access_copy = sub {
+my $check_storage_access_clone = sub {
    my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
 
    my $sharedvm = 1;
@@ -77,7 +78,7 @@ my $check_storage_access_copy = sub {
            if ($volid eq 'cdrom') {
                $rpcenv->check($authuser, "/", ['Sys.Console']);
            } else {
-               # we simply allow access 
+               # we simply allow access
                my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
                my $scfg = PVE::Storage::storage_config($storecfg, $sid);
                $sharedvm = 0 if !$scfg->{shared};
@@ -127,28 +128,27 @@ my $create_disks = sub {
        } else {
 
            my $path = $rpcenv->check_volume_access($authuser, $storecfg, $vmid, $volid);
-           
-           my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
 
-           my $foundvolid = undef;
+           my $volid_is_new = 1;
 
-           if ($storeid) {
-               PVE::Storage::activate_volumes($storecfg, [ $volid ]);
-               my $dl = PVE::Storage::vdisk_list($storecfg, $storeid, undef);
+           if ($conf->{$ds}) {
+               my $olddrive = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
+               $volid_is_new = undef if $olddrive->{file} && $olddrive->{file} eq $volid;
+           }
 
-               PVE::Storage::foreach_volid($dl, sub {
-                   my ($volumeid) = @_;
-                   if($volumeid eq $volid) {
-                       $foundvolid = 1;
-                       return;
-                   }
-               });
+           if ($volid_is_new) {
+
+               my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+               PVE::Storage::activate_volumes($storecfg, [ $volid ]) if $storeid;
+
+               my $size = PVE::Storage::volume_size_info($storecfg, $volid);
+
+               die "volume $volid does not exists\n" if !$size;
+
+               $disk->{size} = $size;
            }
-       
-           die "image '$path' does not exists\n" if (!(-f $path || -b $path || $foundvolid));
 
-           my ($size) = PVE::Storage::volume_size_info($storecfg, $volid, 1);
-           $disk->{size} = $size;
            $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
        }
     });
@@ -181,7 +181,7 @@ my $check_vm_modify_config_perm = sub {
        next if PVE::QemuServer::valid_drivename($opt);
 
        if ($opt eq 'sockets' || $opt eq 'cores' ||
-           $opt eq 'cpu' || $opt eq 'smp' || 
+           $opt eq 'cpu' || $opt eq 'smp' ||
            $opt eq 'cpulimit' || $opt eq 'cpuunits') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
        } elsif ($opt eq 'boot' || $opt eq 'bootdisk') {
@@ -190,7 +190,7 @@ my $check_vm_modify_config_perm = sub {
            $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' || 
+       } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
                 $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
        } elsif ($opt =~ m/^net\d+$/) {
@@ -248,17 +248,18 @@ __PACKAGE__->register_method({
        return $res;
     }});
 
+
+
 __PACKAGE__->register_method({
     name => 'create_vm',
     path => '',
     method => 'POST',
     description => "Create or restore a virtual machine.",
     permissions => {
-       description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
-       check => [ 'or', 
-                  [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
-                  [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
-           ],
+       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.",
+        user => 'all', # check inside
     },
     protected => 1,
     proxyto => 'node',
@@ -290,7 +291,7 @@ __PACKAGE__->register_method({
                    description => "Assign a unique random ethernet address.",
                    requires => 'archive',
                },
-               pool => { 
+               pool => {
                    optional => 1,
                    type => 'string', format => 'pve-poolid',
                    description => "Add the VM to the specified pool.",
@@ -318,7 +319,7 @@ __PACKAGE__->register_method({
        my $force = extract_param($param, 'force');
 
        my $unique = extract_param($param, 'unique');
-       
+
        my $pool = extract_param($param, 'pool');
 
        my $filename = PVE::QemuServer::config_file($vmid);
@@ -329,11 +330,22 @@ __PACKAGE__->register_method({
 
        if (defined($pool)) {
            $rpcenv->check_pool_exist($pool);
-       } 
+       }
 
        $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
            if defined($storage);
 
+       if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
+           # OK
+       } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
+           # 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
+       } else {
+           raise_perm_exc();
+       }
+
        if (!$archive) {
            &$resolve_cdrom_alias($param);
 
@@ -370,15 +382,6 @@ __PACKAGE__->register_method({
            }
        }
 
-       my $addVMtoPoolFn = sub {                      
-           my $usercfg = cfs_read_file("user.cfg");
-           if (my $data = $usercfg->{pools}->{$pool}) {
-               $data->{vms}->{$vmid} = 1;
-               $usercfg->{vms}->{$vmid} = $pool;
-               cfs_write_file("user.cfg", $usercfg);
-           }
-       };
-
        my $restorefn = sub {
 
            # fixme: this test does not work if VM exists on other node!
@@ -396,7 +399,7 @@ __PACKAGE__->register_method({
                    pool => $pool,
                    unique => $unique });
 
-               PVE::AccessControl::lock_user_config($addVMtoPoolFn, "can't add VM to pool") if $pool;
+               PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
            };
 
            return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
@@ -445,7 +448,7 @@ __PACKAGE__->register_method({
                    die "create failed - $err";
                }
 
-               PVE::AccessControl::lock_user_config($addVMtoPoolFn, "can't add VM to pool") if $pool;
+               PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
            };
 
            return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
@@ -490,10 +493,12 @@ __PACKAGE__->register_method({
            { subdir => 'vncproxy' },
            { subdir => 'migrate' },
            { subdir => 'resize' },
+           { subdir => 'move' },
            { subdir => 'rrd' },
            { subdir => 'rrddata' },
            { subdir => 'monitor' },
            { subdir => 'snapshot' },
+           { subdir => 'spiceproxy' },
            ];
 
        return $res;
@@ -657,9 +662,19 @@ my $delete_drive = sub {
 
     if (!PVE::QemuServer::drive_is_cdrom($drive)) {
        my $volid = $drive->{file};
+
        if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
            if ($force || $key =~ m/^unused/) {
-               eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+               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);
+
+                   die "unable to delete '$volid' - volume is still in use (snapshot?)\n"
+                       if $used_paths->{$path};
+
+                   PVE::Storage::vdisk_free($storecfg, $volid);
+               };
                die $@ if $@;
            } else {
                PVE::QemuServer::add_unused_volume($conf, $volid, $vmid);
@@ -681,8 +696,8 @@ my $vmconfig_delete_option = sub {
        $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.Allocate']);
+       if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
+           $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
        }
     }
 
@@ -714,9 +729,9 @@ my $vmconfig_delete_option = sub {
 my $safe_num_ne = sub {
     my ($a, $b) = @_;
 
-    return 0 if !defined($a) && !defined($b); 
-    return 1 if !defined($a); 
-    return 1 if !defined($b); 
+    return 0 if !defined($a) && !defined($b);
+    return 1 if !defined($a);
+    return 1 if !defined($b);
 
     return $a != $b;
 };
@@ -753,9 +768,13 @@ my $vmconfig_update_disk = sub {
                &$safe_num_ne($drive->{iops}, $old_drive->{iops}) ||
                &$safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd}) ||
                &$safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr})) {
-               PVE::QemuServer::qemu_block_set_io_throttle($vmid,"drive-$opt", $drive->{mbps}*1024*1024, 
-                                                          $drive->{mbps_rd}*1024*1024, $drive->{mbps_wr}*1024*1024, 
-                                                          $drive->{iops}, $drive->{iops_rd}, $drive->{iops_wr}) 
+               PVE::QemuServer::qemu_block_set_io_throttle($vmid,"drive-$opt", 
+                                                          ($drive->{mbps} || 0)*1024*1024,
+                                                          ($drive->{mbps_rd} || 0)*1024*1024, 
+                                                          ($drive->{mbps_wr} || 0)*1024*1024,
+                                                          $drive->{iops} || 0, 
+                                                          $drive->{iops_rd} || 0, 
+                                                          $drive->{iops_wr} || 0)
                   if !PVE::QemuServer::drive_is_cdrom($drive);
             }
        }
@@ -796,7 +815,7 @@ my $vmconfig_update_net = sub {
            #if model change, we try to hot-unplug
             die "error hot-unplug $opt for update" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
        }else{
-               
+
            if($newnet->{bridge} && $oldnet->{bridge}){
                my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
 
@@ -814,7 +833,7 @@ my $vmconfig_update_net = sub {
                die "error hot-unplug $opt for update" if !PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
            }
        }
-       
+
     }
     $conf->{$opt} = $value;
     PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
@@ -825,140 +844,111 @@ my $vmconfig_update_net = sub {
     die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, $net);
 };
 
-my $vm_config_perm_list = [
-           'VM.Config.Disk', 
-           'VM.Config.CDROM', 
-           'VM.Config.CPU', 
-           'VM.Config.Memory', 
-           'VM.Config.Network', 
-           'VM.Config.HWType',
-           'VM.Config.Options',
-    ];
+# POST/PUT {vmid}/config implementation
+#
+# The original API used PUT (idempotent) an we assumed that all operations
+# are fast. But it turned out that almost any configuration change can
+# involve hot-plug actions, or disk alloc/free. Such actions can take long
+# time to complete and have side effects (not idempotent).
+#
+# The new implementation uses POST and forks a worker process. We added
+# a new option 'background_delay'. If specified we wait up to
+# 'background_delay' second for the worker task to complete. It returns null
+# if the task is finished within that time, else we return the UPID.
 
-__PACKAGE__->register_method({
-    name => 'update_vm',
-    path => '{vmid}/config',
-    method => 'PUT',
-    protected => 1,
-    proxyto => 'node',
-    description => "Set virtual machine options.",
-    permissions => {
-       check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => PVE::QemuServer::json_config_properties(
-           {
-               node => get_standard_option('pve-node'),
-               vmid => get_standard_option('pve-vmid'),
-               skiplock => get_standard_option('skiplock'),
-               delete => {
-                   type => 'string', format => 'pve-configid-list',
-                   description => "A list of settings you want to delete.",
-                   optional => 1,
-               },
-               force => {
-                   type => 'boolean',
-                   description => $opt_force_description,
-                   optional => 1,
-                   requires => 'delete',
-               },
-               digest => {
-                   type => 'string',
-                   description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
-                   maxLength => 40,
-                   optional => 1,
-               }
-           }),
-    },
-    returns => { type => 'null'},
-    code => sub {
-       my ($param) = @_;
+my $update_vm_api  = sub {
+    my ($param, $sync) = @_;
 
-       my $rpcenv = PVE::RPCEnvironment::get();
+    my $rpcenv = PVE::RPCEnvironment::get();
 
-       my $authuser = $rpcenv->get_user();
+    my $authuser = $rpcenv->get_user();
 
-       my $node = extract_param($param, 'node');
+    my $node = extract_param($param, 'node');
 
-       my $vmid = extract_param($param, 'vmid');
+    my $vmid = extract_param($param, 'vmid');
 
-       my $digest = extract_param($param, 'digest');
+    my $digest = extract_param($param, 'digest');
 
-       my @paramarr = (); # used for log message
-       foreach my $key (keys %$param) {
-           push @paramarr, "-$key", $param->{$key};
-       }
+    my $background_delay = extract_param($param, 'background_delay');
 
-       my $skiplock = extract_param($param, 'skiplock');
-       raise_param_exc({ skiplock => "Only root may use this option." })
-           if $skiplock && $authuser ne 'root@pam';
+    my @paramarr = (); # used for log message
+    foreach my $key (keys %$param) {
+       push @paramarr, "-$key", $param->{$key};
+    }
 
-       my $delete_str = extract_param($param, 'delete');
+    my $skiplock = extract_param($param, 'skiplock');
+    raise_param_exc({ skiplock => "Only root may use this option." })
+       if $skiplock && $authuser ne 'root@pam';
 
-       my $force = extract_param($param, 'force');
+    my $delete_str = extract_param($param, 'delete');
 
-       die "no options specified\n" if !$delete_str && !scalar(keys %$param);
+    my $force = extract_param($param, 'force');
 
-       my $storecfg = PVE::Storage::config();
+    die "no options specified\n" if !$delete_str && !scalar(keys %$param);
 
-       my $defaults = PVE::QemuServer::load_defaults();
+    my $storecfg = PVE::Storage::config();
 
-       &$resolve_cdrom_alias($param);
+    my $defaults = PVE::QemuServer::load_defaults();
 
-       # now try to verify all parameters
+    &$resolve_cdrom_alias($param);
 
-       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});
+    # now try to verify all parameters
 
-           if (!PVE::QemuServer::option_exists($opt)) {
-               raise_param_exc({ delete => "unknown option '$opt'" });
-           }
+    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});
 
-           push @delete, $opt;
+       if (!PVE::QemuServer::option_exists($opt)) {
+           raise_param_exc({ delete => "unknown option '$opt'" });
        }
 
-       foreach my $opt (keys %$param) {
-           if (PVE::QemuServer::valid_drivename($opt)) {
-               # cleanup drive path
-               my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
-               PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
-               $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
-           } elsif ($opt =~ m/^net(\d+)$/) { 
-               # add macaddr
-               my $net = PVE::QemuServer::parse_net($param->{$opt});
-               $param->{$opt} = PVE::QemuServer::print_net($net);
-           }
+       push @delete, $opt;
+    }
+
+    foreach my $opt (keys %$param) {
+       if (PVE::QemuServer::valid_drivename($opt)) {
+           # cleanup drive path
+           my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
+           PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
+           $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
+       } elsif ($opt =~ m/^net(\d+)$/) {
+           # add macaddr
+           my $net = PVE::QemuServer::parse_net($param->{$opt});
+           $param->{$opt} = PVE::QemuServer::print_net($net);
        }
+    }
 
-       &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
+    &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
 
-       &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
+    &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
 
-       &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
+    &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
 
-       my $updatefn =  sub {
+    my $updatefn =  sub {
 
-           my $conf = PVE::QemuServer::load_config($vmid);
+       my $conf = PVE::QemuServer::load_config($vmid);
 
-           die "checksum missmatch (file change by other user?)\n"
-               if $digest && $digest ne $conf->{digest};
+       die "checksum missmatch (file change by other user?)\n"
+           if $digest && $digest ne $conf->{digest};
 
-           PVE::QemuServer::check_lock($conf) if !$skiplock;
+       PVE::QemuServer::check_lock($conf) if !$skiplock;
 
-           if ($param->{memory} || defined($param->{balloon})) {
-               my $maxmem = $param->{memory} || $conf->{memory} || $defaults->{memory};
-               my $balloon = defined($param->{balloon}) ?  $param->{balloon} : $conf->{balloon};
+       if ($param->{memory} || defined($param->{balloon})) {
+           my $maxmem = $param->{memory} || $conf->{memory} || $defaults->{memory};
+           my $balloon = defined($param->{balloon}) ?  $param->{balloon} : $conf->{balloon};
 
-               die "balloon value too large (must be smaller than assigned memory)\n"
-                   if $balloon > $maxmem;
-           }
+           die "balloon value too large (must be smaller than assigned memory)\n"
+               if $balloon && $balloon > $maxmem;
+       }
+
+       PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
 
-           PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
+       my $worker = sub {
+
+           print "update VM $vmid: " . join (' ', @paramarr) . "\n";
 
            foreach my $opt (@delete) { # delete
                $conf = PVE::QemuServer::load_config($vmid); # update/reload
@@ -975,19 +965,19 @@ __PACKAGE__->register_method({
 
                if (PVE::QemuServer::valid_drivename($opt)) {
 
-                   &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid, 
+                   &$vmconfig_update_disk($rpcenv, $authuser, $conf, $storecfg, $vmid,
                                           $opt, $param->{$opt}, $force);
-       
+
                } elsif ($opt =~ m/^net(\d+)$/) { #nics
 
-                   &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid, 
+                   &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, $vmid,
                                          $opt, $param->{$opt});
 
                } else {
 
                    if($opt eq 'tablet' && $param->{$opt} == 1){
                        PVE::QemuServer::vm_deviceplug(undef, $conf, $vmid, $opt);
-                   }elsif($opt eq 'tablet' && $param->{$opt} == 0){
+                   } elsif($opt eq 'tablet' && $param->{$opt} == 0){
                        PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
                    }
 
@@ -997,18 +987,152 @@ __PACKAGE__->register_method({
            }
 
            # allow manual ballooning if shares is set to zero
-           if ($running && defined($param->{balloon}) && 
+           if ($running && defined($param->{balloon}) &&
                defined($conf->{shares}) && ($conf->{shares} == 0)) {
                my $balloon = $param->{'balloon'} || $conf->{memory} || $defaults->{memory};
                PVE::QemuServer::vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
            }
-
        };
 
-       PVE::QemuServer::lock_config($vmid, $updatefn);
+       if ($sync) {
+           &$worker();
+           return undef;
+       } else {
+           my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
+
+           if ($background_delay) {
 
+               # Note: It would be better to do that in the Event based HTTPServer
+               # to avoid blocking call to sleep.
+
+               my $end_time = time() + $background_delay;
+
+               my $task = PVE::Tools::upid_decode($upid);
+
+               my $running = 1;
+               while (time() < $end_time) {
+                   $running = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
+                   last if !$running;
+                   sleep(1); # this gets interrupted when child process ends
+               }
+
+               if (!$running) {
+                   my $status = PVE::Tools::upid_read_status($upid);
+                   return undef if $status eq 'OK';
+                   die $status;
+               }
+           }
+
+           return $upid;
+       }
+    };
+
+    return PVE::QemuServer::lock_config($vmid, $updatefn);
+};
+
+my $vm_config_perm_list = [
+           'VM.Config.Disk',
+           'VM.Config.CDROM',
+           'VM.Config.CPU',
+           'VM.Config.Memory',
+           'VM.Config.Network',
+           'VM.Config.HWType',
+           'VM.Config.Options',
+    ];
+
+__PACKAGE__->register_method({
+    name => 'update_vm_async',
+    path => '{vmid}/config',
+    method => 'POST',
+    protected => 1,
+    proxyto => 'node',
+    description => "Set virtual machine options (asynchrounous API).",
+    permissions => {
+       check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::QemuServer::json_config_properties(
+           {
+               node => get_standard_option('pve-node'),
+               vmid => get_standard_option('pve-vmid'),
+               skiplock => get_standard_option('skiplock'),
+               delete => {
+                   type => 'string', format => 'pve-configid-list',
+                   description => "A list of settings you want to delete.",
+                   optional => 1,
+               },
+               force => {
+                   type => 'boolean',
+                   description => $opt_force_description,
+                   optional => 1,
+                   requires => 'delete',
+               },
+               digest => {
+                   type => 'string',
+                   description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
+                   maxLength => 40,
+                   optional => 1,
+               },
+               background_delay => {
+                   type => 'integer',
+                   description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
+                   minimum => 1,
+                   maximum => 30,
+                   optional => 1,
+               },
+           }),
+    },
+    returns => {
+       type => 'string',
+       optional => 1,
+    },
+    code => $update_vm_api,
+});
+
+__PACKAGE__->register_method({
+    name => 'update_vm',
+    path => '{vmid}/config',
+    method => 'PUT',
+    protected => 1,
+    proxyto => 'node',
+    description => "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
+    permissions => {
+       check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::QemuServer::json_config_properties(
+           {
+               node => get_standard_option('pve-node'),
+               vmid => get_standard_option('pve-vmid'),
+               skiplock => get_standard_option('skiplock'),
+               delete => {
+                   type => 'string', format => 'pve-configid-list',
+                   description => "A list of settings you want to delete.",
+                   optional => 1,
+               },
+               force => {
+                   type => 'boolean',
+                   description => $opt_force_description,
+                   optional => 1,
+                   requires => 'delete',
+               },
+               digest => {
+                   type => 'string',
+                   description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
+                   maxLength => 40,
+                   optional => 1,
+               },
+           }),
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+       &$update_vm_api($param, 1);
        return undef;
-    }});
+    }
+});
 
 
 __PACKAGE__->register_method({
@@ -1050,7 +1174,7 @@ __PACKAGE__->register_method({
 
        my $storecfg = PVE::Storage::config();
 
-       my $delVMfromPoolFn = sub {                    
+       my $delVMfromPoolFn = sub {
            my $usercfg = cfs_read_file("user.cfg");
            if (my $pool = $usercfg->{vms}->{$vmid}) {
                if (my $data = $usercfg->{pools}->{$pool}) {
@@ -1068,7 +1192,7 @@ __PACKAGE__->register_method({
 
            PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
 
-           PVE::AccessControl::lock_user_config($delVMfromPoolFn, "pool cleanup failed");
+           PVE::AccessControl::remove_vm_from_pool($vmid);
        };
 
        return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
@@ -1149,6 +1273,8 @@ __PACKAGE__->register_method({
        my $vmid = $param->{vmid};
        my $node = $param->{node};
 
+       my $conf = PVE::QemuServer::load_config($vmid, $node); # check if VM exists
+
        my $authpath = "/vms/$vmid";
 
        my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
@@ -1159,14 +1285,14 @@ __PACKAGE__->register_method({
        my $port = PVE::Tools::next_vnc_port();
 
        my $remip;
+       my $remcmd = [];
 
        if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
            $remip = PVE::Cluster::remote_node_ip($node);
+           # NOTE: kvm VNC traffic is already TLS encrypted
+           $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
        }
 
-       # NOTE: kvm VNC traffic is already TLS encrypted
-       my $remcmd = $remip ? ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip] : [];
-
        my $timeout = 10;
 
        my $realcmd = sub {
@@ -1174,12 +1300,24 @@ __PACKAGE__->register_method({
 
            syslog('info', "starting vnc proxy $upid\n");
 
-           my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
+           my $cmd;
+
+           if ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/)) {
+
+               my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga} ];
+               #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
+               $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
+                       '-timeout', $timeout, '-authpath', $authpath, 
+                       '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
+           } else {
 
-           my $qmstr = join(' ', @$qmcmd);
+               my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
 
-           # also redirect stderr (else we get RFB protocol errors)
-           my $cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 2>/dev/null"];
+               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"];
+           }
 
            PVE::Tools::run_command($cmd);
 
@@ -1199,6 +1337,83 @@ __PACKAGE__->register_method({
        };
     }});
 
+__PACKAGE__->register_method({
+    name => 'spiceproxy',
+    path => '{vmid}/spiceproxy',
+    method => 'GET',
+    protected => 1,
+    proxyto => 'node', # fixme: use direct connections or ssh tunnel?
+    permissions => {
+       check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
+    },
+    description => "Returns a SPICE configuration to connect to the VM.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+           proxy => {
+               description => "This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI).",
+               type => 'string', format => 'dns-name',
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       description => "Returned values can be directly passed to the 'remote-viewer' application.",
+       additionalProperties => 1,
+       properties => {
+           type => { type => 'string' },
+           password => { type => 'string' },
+           proxy => { type => 'string' },
+           host => { type => 'string' },
+           'tls-port' => { type => 'integer' },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $authuser = $rpcenv->get_user();
+
+       my $vmid = $param->{vmid};
+       my $node = $param->{node};
+       my $proxy = $param->{proxy};
+
+       my ($ticket, $proxyticket) = PVE::AccessControl::assemble_spice_ticket($authuser, $vmid, $node);
+
+       my $timeout = 10;
+
+       my $port = PVE::QemuServer::spice_port($vmid);
+       PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket);
+       PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
+
+       if (!$proxy) {
+           my $host = `hostname -f` || PVE::INotify::nodename();
+           chomp $host;
+           $proxy = $host;
+       }
+
+       my $filename = "/etc/pve/local/pve-ssl.pem";
+       my $subject = PVE::QemuServer::read_x509_subject_spice($filename);
+
+       my $cacert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192);
+       $cacert =~ s/\n/\\n/g;
+
+       return {
+           type => 'spice',
+           title => "VM $vmid",
+           host => $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
+           proxy => "http://$proxy:3128",
+           'tls-port' => $port,
+           'host-subject' => $subject,
+           ca => $cacert,
+           password => $ticket,
+           'delete-this-file' => 1,
+       };
+    }});
+
 __PACKAGE__->register_method({
     name => 'vmcmdidx',
     path => '{vmid}/status',
@@ -1246,7 +1461,7 @@ my $vm_is_ha_managed = sub {
     my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
     if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $vmid, 1)) {
        return 1;
-    } 
+    }
     return 0;
 };
 
@@ -1279,6 +1494,8 @@ __PACKAGE__->register_method({
 
        $status->{ha} = &$vm_is_ha_managed($param->{vmid});
 
+       $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
+
        return $status;
     }});
 
@@ -1300,7 +1517,7 @@ __PACKAGE__->register_method({
            skiplock => get_standard_option('skiplock'),
            stateuri => get_standard_option('pve-qm-stateuri'),
            migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
-
+           machine => get_standard_option('pve-qm-machine'),
        },
     },
     returns => {
@@ -1317,6 +1534,8 @@ __PACKAGE__->register_method({
 
        my $vmid = extract_param($param, 'vmid');
 
+       my $machine = extract_param($param, 'machine');
+
        my $stateuri = extract_param($param, 'stateuri');
        raise_param_exc({ stateuri => "Only root may use this option." })
            if $stateuri && $authuser ne 'root@pam';
@@ -1329,6 +1548,15 @@ __PACKAGE__->register_method({
        raise_param_exc({ migratedfrom => "Only root may use this option." })
            if $migratedfrom && $authuser ne 'root@pam';
 
+       # read spice ticket from STDIN
+       my $spice_ticket;
+       if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
+           if (defined(my $line = <>)) {
+               chomp $line;
+               $spice_ticket = $line;
+           }
+       }
+
        my $storecfg = PVE::Storage::config();
 
        if (&$vm_is_ha_managed($vmid) && !$stateuri &&
@@ -1357,7 +1585,8 @@ __PACKAGE__->register_method({
 
                syslog('info', "start VM $vmid: $upid\n");
 
-               PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom);
+               PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, 
+                                         $machine, $spice_ticket);
 
                return;
            };
@@ -1733,7 +1962,7 @@ __PACKAGE__->register_method({
     path => '{vmid}/feature',
     method => 'GET',
     proxyto => 'node',
-    protected => 1, 
+    protected => 1,
     description => "Check if feature for virtual machine is available.",
     permissions => {
        check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
@@ -1746,7 +1975,7 @@ __PACKAGE__->register_method({
             feature => {
                 description => "Feature to check.",
                 type => 'string',
-                enum => [ 'snapshot', 'clone' ],
+                enum => [ 'snapshot', 'clone', 'copy' ],
             },
             snapname => get_standard_option('pve-snapshot-name', {
                 optional => 1,
@@ -1754,7 +1983,14 @@ __PACKAGE__->register_method({
        },
     },
     returns => {
-        type => 'boolean'
+       type => "object",
+       properties => {
+           hasFeature => { type => 'boolean' },
+           nodes => {
+               type => 'array',
+               items => { type => 'string' },
+           }
+       },
     },
     code => sub {
        my ($param) = @_;
@@ -1778,26 +2014,30 @@ __PACKAGE__->register_method({
        }
        my $storecfg = PVE::Storage::config();
 
-       my $hasfeature = PVE::QemuServer::has_feature($feature, $conf, $storecfg, $snapname, $running);
-       my $res = $hasfeature ? 1 : 0 ;
-       return $res;
+       my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
+       my $hasFeature = PVE::QemuServer::has_feature($feature, $conf, $storecfg, $snapname, $running);
+
+       return {
+           hasFeature => $hasFeature,
+           nodes => [ keys %$nodelist ],
+       };
     }});
 
 __PACKAGE__->register_method({
-    name => 'copy_vm',
-    path => '{vmid}/copy',
+    name => 'clone_vm',
+    path => '{vmid}/clone',
     method => 'POST',
     protected => 1,
     proxyto => 'node',
     description => "Create a copy of virtual machine/template.",
     permissions => {
-       description => "You need 'VM.Copy' permissions on /vms/{vmid}, and 'VM.Allocate' 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.",
-       check => 
-       [ 'and', 
-         ['perm', '/vms/{vmid}', [ 'VM.Copy' ]],
-         [ 'or', 
+       check =>
+       [ 'and',
+         ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
+         [ 'or',
            [ 'perm', '/vms/{newid}', ['VM.Allocate']],
            [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
          ],
@@ -1808,7 +2048,7 @@ __PACKAGE__->register_method({
        properties => {
            node => get_standard_option('pve-node'),
            vmid => get_standard_option('pve-vmid'),
-           newid => get_standard_option('pve-vmid', { description => 'VMID for the copy.' }),
+           newid => get_standard_option('pve-vmid', { description => 'VMID for the clone.' }),
            name => {
                optional => 1,
                type => 'string', format => 'dns-name',
@@ -1819,7 +2059,7 @@ __PACKAGE__->register_method({
                type => 'string',
                description => "Description for the new VM.",
            },
-           pool => { 
+           pool => {
                optional => 1,
                type => 'string', format => 'pve-poolid',
                description => "Add the new VM to the specified pool.",
@@ -1829,7 +2069,7 @@ __PACKAGE__->register_method({
                optional => 1,
             }),
            storage => get_standard_option('pve-storage-id', {
-               description => "Target storage for full copy.",
+               description => "Target storage for full clone.",
                requires => 'full',
                optional => 1,
            }),
@@ -1844,10 +2084,10 @@ __PACKAGE__->register_method({
                optional => 1,
                type => 'boolean',
                description => "Create a full copy of all disk. This is always done when " .
-                   "you copy a normal VM. For VM templates, we try to create a linked copy by default.",
+                   "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
                default => 0,
            },
-           target => get_standard_option('pve-node', { 
+           target => get_standard_option('pve-node', {
                description => "Target node. Only allowed if the original VM is on shared storage.",
                optional => 1,
            }),
@@ -1869,7 +2109,6 @@ __PACKAGE__->register_method({
 
        my $newid = extract_param($param, 'newid');
 
-       # fixme: update pool after create
        my $pool = extract_param($param, 'pool');
 
        if (defined($pool)) {
@@ -1886,28 +2125,36 @@ __PACKAGE__->register_method({
 
         my $localnode = PVE::INotify::nodename();
 
-        undef $target if $target eq $localnode || $target eq 'localhost';
+        undef $target if $target && ($target eq $localnode || $target eq 'localhost');
 
        PVE::Cluster::check_node_exists($target) if $target;
 
        my $storecfg = PVE::Storage::config();
 
+       if ($storage) {
+           # check if storage is enabled on local node
+           PVE::Storage::storage_check_enabled($storecfg, $storage);
+           if ($target) {
+               # check if storage is available on target node
+               PVE::Storage::storage_check_node($storecfg, $storage, $target);
+               # clone only works if target storage is shared
+               my $scfg = PVE::Storage::storage_config($storecfg, $storage);
+               die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
+           }
+       }
+
         PVE::Cluster::check_cfs_quorum();
 
        my $running = PVE::QemuServer::check_running($vmid) || 0;
 
-       die "Copy running VM $vmid not implemented\n" if $running; # fixme: implement this
-
        # exclusive lock if VM is running - else shared lock is enough;
        my $shared_lock = $running ? 0 : 1;
 
-       # fixme: do early checks - re-check after lock 
-
-       # fixme: impl. target node parameter (mv VM config if all storages are shared)
+       my $clonefn = sub {
 
-       my $copyfn = sub {
+           # do all tests after lock
+           # we also try to do all tests before we fork the worker
 
-           # all tests after lock
            my $conf = PVE::QemuServer::load_config($vmid);
 
            PVE::QemuServer::check_lock($conf);
@@ -1916,111 +2163,93 @@ __PACKAGE__->register_method({
 
            die "unexpected state change\n" if $verify_running != $running;
 
-           die "snapshot '$snapname' does not exist\n" 
-               if $snapname && !defined( $conf->{snapshots}->{$snapname}); 
+           die "snapshot '$snapname' does not exist\n"
+               if $snapname && !defined( $conf->{snapshots}->{$snapname});
 
-           my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf; 
+           my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
 
-           my $sharedvm = &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $oldconf, $storage);
+           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 copy VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
-           
            my $conffile = PVE::QemuServer::config_file($newid);
 
            die "unable to create VM $newid: config file already exists\n"
                if -f $conffile;
 
-           # create empty/temp config - this fails if VM already exists on other node
-           PVE::Tools::file_set_contents($conffile, "# qmcopy temporary file\nlock: copy\n");
-
-           my $realcmd = sub {
-               my $upid = shift;
-
-               my $newvollist = [];
-
-               eval {
-                   my $newconf = { lock => 'copy' };
-                   my $drives = {};
-                   my $vollist = [];
-
-                   foreach my $opt (keys %$oldconf) {
-                       my $value = $oldconf->{$opt};
-
-                       # do not copy snapshot related info
-                       next if $opt eq 'snapshots' ||  $opt eq 'parent' || $opt eq 'snaptime' ||
-                           $opt eq 'vmstate' || $opt eq 'snapstate';
-
-                       # 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 (my $drive = PVE::QemuServer::parse_drive($opt, $value)) {
-                           if (PVE::QemuServer::drive_is_cdrom($drive)) {
-                               $newconf->{$opt} = $value; # simply copy configuration
-                           } else {
-                               $drives->{$opt} = $drive;
-                               push @$vollist, $drive->{file};
-                           }
-                       } else {
-                           # copy everything else
-                           $newconf->{$opt} = $value;  
+           my $newconf = { lock => 'clone' };
+           my $drives = {};
+           my $vollist = [];
+
+           foreach my $opt (keys %$oldconf) {
+               my $value = $oldconf->{$opt};
+
+               # do not copy snapshot related info
+               next if $opt eq 'snapshots' ||  $opt eq 'parent' || $opt eq 'snaptime' ||
+                   $opt eq 'vmstate' || $opt eq 'snapstate';
+
+               # 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 (my $drive = PVE::QemuServer::parse_drive($opt, $value)) {
+                   if (PVE::QemuServer::drive_is_cdrom($drive)) {
+                       $newconf->{$opt} = $value; # simply copy configuration
+                   } else {
+                       if ($param->{full} || !PVE::Storage::volume_is_base($storecfg,  $drive->{file})) {
+                           die "Full clone feature is not available"
+                               if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
+                           $drive->{full} = 1;
                        }
+                       $drives->{$opt} = $drive;
+                       push @$vollist, $drive->{file};
                    }
+               } else {
+                   # copy everything else
+                   $newconf->{$opt} = $value;
+               }
+           }
 
-                   delete $newconf->{template};
+           delete $newconf->{template};
 
-                   if ($param->{name}) {
-                       $newconf->{name} = $param->{name};
-                   } else {
-                       $newconf->{name} = "Copy-of-$oldconf->{name}";
-                   }
+           if ($param->{name}) {
+               $newconf->{name} = $param->{name};
+           } else {
+               if ($oldconf->{name}) {
+                   $newconf->{name} = "Copy-of-$oldconf->{name}";
+               } else {
+                   $newconf->{name} = "Copy-of-VM-$vmid";
+               }
+           }
 
-                   if ($param->{description}) {
-                       $newconf->{description} = $param->{description};
-                   }
-                   
-                   PVE::Storage::activate_volumes($storecfg, $vollist);
+           if ($param->{description}) {
+               $newconf->{description} = $param->{description};
+           }
 
-                   eval {
-                       local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
+           # create empty/temp config - this fails if VM already exists on other node
+           PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
 
-                       foreach my $opt (keys %$drives) {
-                           my $drive = $drives->{$opt};
+           my $realcmd = sub {
+               my $upid = shift;
 
-                           my $newvolid;
-                           if (!$param->{full} && PVE::Storage::volume_is_base($storecfg,  $drive->{file})) {
-                               print "clone drive $opt ($drive->{file})\n";
-                               $newvolid = PVE::Storage::vdisk_clone($storecfg,  $drive->{file}, $newid);
-                           } else {
-                               my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
-                               $storeid = $storage if $storage;
+               my $newvollist = [];
 
-                               my $fmt = undef;
-                               if($format){
-                                   $fmt = $format;
-                               }else{
-                                   my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
-                                   $fmt = $drive->{format} || $defformat;
-                               }
+               eval {
+                   local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
 
-                               my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
+                   PVE::Storage::activate_volumes($storecfg, $vollist);
 
-                               print "copy drive $opt ($drive->{file})\n";
-                               $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
+                   foreach my $opt (keys %$drives) {
+                       my $drive = $drives->{$opt};
 
-                               PVE::QemuServer::qemu_img_convert($drive->{file}, $newvolid, $size, $snapname);
-                           }
+                       my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
+                                                                  $newid, $storage, $format, $drive->{full}, $newvollist);
 
-                           my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
-                           my $disk = { file => $newvolid, size => $size };
-                           $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $disk); 
-                           push @$newvollist, $newvolid;
+                       $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
 
-                           PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
-                       }
-                   };
-                   die $@ if $@;
+                       PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
+                   }
 
                    delete $newconf->{lock};
                    PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
@@ -2030,8 +2259,10 @@ __PACKAGE__->register_method({
                        die "Failed to move config to node '$target' - rename failed: $!\n"
                            if !rename($conffile, $newconffile);
                    }
+
+                   PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
                };
-               if (my $err = $@) { 
+               if (my $err = $@) {
                    unlink $conffile;
 
                    sleep 1; # some storage like rbd need to wait before release volume - really?
@@ -2040,22 +2271,161 @@ __PACKAGE__->register_method({
                        eval { PVE::Storage::vdisk_free($storecfg, $volid); };
                        warn $@ if $@;
                    }
-                   die "copy failed: $err";
+                   die "clone failed: $err";
                }
 
                return;
            };
 
-           return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
+           return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
        };
 
        return PVE::QemuServer::lock_config_mode($vmid, 1, $shared_lock, sub {
            # Aquire exclusive lock lock for $newid
-           return PVE::QemuServer::lock_config_full($newid, 1, $copyfn);
+           return PVE::QemuServer::lock_config_full($newid, 1, $clonefn);
        });
 
     }});
 
+__PACKAGE__->register_method({
+    name => 'move_vm_disk',
+    path => '{vmid}/move_disk',
+    method => 'POST',
+    protected => 1,
+    proxyto => 'node',
+    description => "Move volume to different storage.",
+    permissions => {
+       description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
+           "and 'Datastore.AllocateSpace' permissions on the storage.",
+       check =>
+       [ 'and',
+         ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
+         ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
+       ],
+    },
+    parameters => {
+        additionalProperties => 0,
+        properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid'),
+           disk => {
+               type => 'string',
+               description => "The disk you want to move.",
+               enum => [ PVE::QemuServer::disknames() ],
+           },
+            storage => get_standard_option('pve-storage-id', { description => "Target Storage." }),
+            'format' => {
+                type => 'string',
+                description => "Target Format.",
+                enum => [ 'raw', 'qcow2', 'vmdk' ],
+                optional => 1,
+            },
+           delete => {
+               type => 'boolean',
+               description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
+               optional => 1,
+               default => 0,
+           },
+           digest => {
+               type => 'string',
+               description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
+               maxLength => 40,
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => 'string',
+       description => "the task ID.",
+    },
+    code => sub {
+       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::QemuServer::load_config($vmid);
+
+           die "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 "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
+
+           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 &&
+                (!$format || !$oldfmt || $oldfmt eq $format);
+
+           PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
+
+           my $running = PVE::QemuServer::check_running($vmid);
+
+           PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
+
+           my $realcmd = sub {
+
+               my $newvollist = [];
+
+               eval {
+                   local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
+
+                   my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
+                                                              $vmid, $storeid, $format, 1, $newvollist);
+
+                   $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
+
+                   PVE::QemuServer::add_unused_volume($conf, $old_volid) if !$param->{delete};
+
+                   PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+               };
+               if (my $err = $@) {
+
+                   foreach my $volid (@$newvollist) {
+                        eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+                        warn $@ if $@;
+                    }
+                   die "storage migration failed: $err";
+                }
+
+               if ($param->{delete}) {
+                   eval { PVE::Storage::vdisk_free($storecfg, $old_volid); };
+                   warn $@ if $@;
+               }
+           };
+
+            return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
+       };
+
+       return PVE::QemuServer::lock_config($vmid, $updatefn);
+    }});
+
 __PACKAGE__->register_method({
     name => 'migrate_vm',
     path => '{vmid}/migrate',
@@ -2244,7 +2614,7 @@ __PACKAGE__->register_method({
         my $digest = extract_param($param, 'digest');
 
         my $disk = extract_param($param, 'disk');
+
        my $sizestr = extract_param($param, 'size');
 
        my $skiplock = extract_param($param, 'skiplock');
@@ -2271,7 +2641,7 @@ __PACKAGE__->register_method({
 
            die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
 
-           die "you can't online resize a virtio windows bootdisk\n" 
+           die "you can't online resize a virtio windows bootdisk\n"
                if PVE::QemuServer::check_running($vmid) && $conf->{bootdisk} eq $disk && $conf->{ostype} =~ m/^w/ && $disk =~ m/^virtio/;
 
            my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
@@ -2303,7 +2673,7 @@ __PACKAGE__->register_method({
             PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
 
            PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
-           
+
            $drive->{size} = $newsize;
            $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
 
@@ -2351,9 +2721,9 @@ __PACKAGE__->register_method({
 
        foreach my $name (keys %$snaphash) {
            my $d = $snaphash->{$name};
-           my $item = { 
-               name => $name, 
-               snaptime => $d->{snaptime} || 0, 
+           my $item = {
+               name => $name,
+               snaptime => $d->{snaptime} || 0,
                vmstate => $d->{vmstate} ? 1 : 0,
                description => $d->{description} || '',
            };
@@ -2426,7 +2796,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::QemuServer::snapshot_create($vmid, $snapname, $param->{vmstate},
                                             $param->{freezefs}, $param->{description});
        };
 
@@ -2513,8 +2883,8 @@ __PACKAGE__->register_method({
 
            my $snap = $conf->{snapshots}->{$snapname};
 
-           die "snapshot '$snapname' does not exist\n" if !defined($snap); 
-           
+           die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
            $snap->{description} = $param->{description} if defined($param->{description});
 
             PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
@@ -2558,8 +2928,8 @@ __PACKAGE__->register_method({
 
        my $snap = $conf->{snapshots}->{$snapname};
 
-       die "snapshot '$snapname' does not exist\n" if !defined($snap); 
-           
+       die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
        return $snap;
     }});
 
@@ -2662,11 +3032,8 @@ __PACKAGE__->register_method({
     proxyto => 'node',
     description => "Create a Template.",
     permissions => {
-       description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}.",
-       check => [ 'or', 
-                  [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
-                  [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
-           ],
+       description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
+       check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
     },
     parameters => {
        additionalProperties => 0,
@@ -2702,13 +3069,13 @@ __PACKAGE__->register_method({
 
            PVE::QemuServer::check_lock($conf);
 
-           die "unable to create template, because VM contains snapshots\n" 
+           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" 
+           die "you can't convert a template to a template\n"
                if PVE::QemuServer::is_template($conf) && !$disk;
 
-           die "you can't convert a VM to template if VM is running\n" 
+           die "you can't convert a VM to template if VM is running\n"
                if PVE::QemuServer::check_running($vmid);
 
            my $realcmd = sub {
@@ -2725,6 +3092,4 @@ __PACKAGE__->register_method({
        return undef;
     }});
 
-
-
 1;