]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
copy_vm: new option to move final VM to other node (option target)
[qemu-server.git] / PVE / API2 / Qemu.pm
index 8a15abcca655be18dfbda1a567e4ab03665f6695..d33af929ffb6ba9e63ad76977198d8408bdee9d3 100644 (file)
@@ -60,7 +60,9 @@ my $check_storage_access = sub {
 };
 
 my $check_storage_access_copy = sub {
-   my ($rpcenv, $authuser, $storecfg, $conf) = @_;
+   my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
+
+   my $sharedvm = 1;
 
    PVE::QemuServer::foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
@@ -76,13 +78,22 @@ my $check_storage_access_copy = sub {
                $rpcenv->check($authuser, "/", ['Sys.Console']);
            } else {
                # 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};
+
            }
        } else {
-           my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
-           die "unable to copy arbitrary files\n" if !$sid;
+           my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
+           my $scfg = PVE::Storage::storage_config($storecfg, $sid);
+           $sharedvm = 0 if !$scfg->{shared};
+
+           $sid = $storage if $storage;
            $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
        }
     });
+
+   return $sharedvm;
 };
 
 # Note: $pool is only needed when creating a VM, because pool permissions
@@ -1741,7 +1752,6 @@ __PACKAGE__->register_method({
                 optional => 1,
             }),
        },
-
     },
     returns => {
         type => 'boolean'
@@ -1796,24 +1806,52 @@ __PACKAGE__->register_method({
     parameters => {
        additionalProperties => 0,
        properties => {
-           # fixme: add other parameters like name and description?
            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 copy.' }),
+           name => {
+               optional => 1,
+               type => 'string', format => 'dns-name',
+               description => "Set a name for the new VM.",
+           },
+           description => {
+               optional => 1,
+               type => 'string',
+               description => "Description for the new VM.",
+           },
            pool => { 
                optional => 1,
                type => 'string', format => 'pve-poolid',
                description => "Add the new VM to the specified pool.",
            },
+            snapname => get_standard_option('pve-snapshot-name', {
+               requires => 'full',
+               optional => 1,
+            }),
+           storage => get_standard_option('pve-storage-id', {
+               description => "Target storage for full copy.",
+               requires => 'full',
+               optional => 1,
+           }),
+           'format' => {
+               description => "Target format for file storage.",
+               requires => 'full',
+               type => 'string',
+               optional => 1,
+               enum => [ 'raw', 'qcow2', 'vmdk'],
+           },
            full => {
                optional => 1,
-               type => 'boolean',
-               description => "Create a full copy of all disk. This is always done when " .
+               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.",
                default => 0,
            },
-       },
+           target => get_standard_option('pve-node', { 
+               description => "Target node. Only allowed if the original VM is on shared storage.",
+               optional => 1,
+           }),
+        },
     },
     returns => {
        type => 'string',
@@ -1823,7 +1861,7 @@ __PACKAGE__->register_method({
 
        my $rpcenv = PVE::RPCEnvironment::get();
 
-       my $authuser = $rpcenv->get_user();
+        my $authuser = $rpcenv->get_user();
 
        my $node = extract_param($param, 'node');
 
@@ -1838,9 +1876,30 @@ __PACKAGE__->register_method({
            $rpcenv->check_pool_exist($pool);
        }
 
+        my $snapname = extract_param($param, 'snapname');
+
+       my $storage = extract_param($param, 'storage');
+
+       my $format = extract_param($param, 'format');
+
+       my $target = extract_param($param, 'target');
+
+        my $localnode = PVE::INotify::nodename();
+
+        undef $target if $target eq $localnode || $target eq 'localhost';
+
+       PVE::Cluster::check_node_exists($target) if $target;
+
        my $storecfg = PVE::Storage::config();
 
-       PVE::Cluster::check_cfs_quorum();
+        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 
 
@@ -1853,14 +1912,19 @@ __PACKAGE__->register_method({
 
            PVE::QemuServer::check_lock($conf);
 
-           my $running = PVE::QemuServer::check_running($vmid);
+           my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
 
-           die "Copy running VM $vmid not implemented\n" if $running;
+           die "unexpected state change\n" if $verify_running != $running;
 
-           &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $conf);
+           die "snapshot '$snapname' does not exist\n" 
+               if $snapname && !defined( $conf->{snapshots}->{$snapname}); 
 
-           # fixme: snapshots??
+           my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf; 
 
+           my $sharedvm = &$check_storage_access_copy($rpcenv, $authuser, $storecfg, $oldconf, $storage);
+
+           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"
@@ -1878,10 +1942,13 @@ __PACKAGE__->register_method({
                    my $newconf = { lock => 'copy' };
                    my $drives = {};
                    my $vollist = [];
-                   foreach my $opt (keys %$conf) {
-                       my $value = $conf->{$opt};
 
-                       next if $opt eq 'snapshots'; #  do not copy snapshot info
+                   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+)$/) {
@@ -1902,6 +1969,16 @@ __PACKAGE__->register_method({
                    }
 
                    delete $newconf->{template};
+
+                   if ($param->{name}) {
+                       $newconf->{name} = $param->{name};
+                   } else {
+                       $newconf->{name} = "Copy-of-$oldconf->{name}";
+                   }
+
+                   if ($param->{description}) {
+                       $newconf->{description} = $param->{description};
+                   }
                    
                    PVE::Storage::activate_volumes($storecfg, $vollist);
 
@@ -1917,15 +1994,22 @@ __PACKAGE__->register_method({
                                $newvolid = PVE::Storage::vdisk_clone($storecfg,  $drive->{file}, $newid);
                            } else {
                                my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
-                               my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
-                               my $fmt = $drive->{format} || $defformat;
+                               $storeid = $storage if $storage;
+
+                               my $fmt = undef;
+                               if($format){
+                                   $fmt = $format;
+                               }else{
+                                   my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
+                                   $fmt = $drive->{format} || $defformat;
+                               }
 
                                my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
 
                                print "copy drive $opt ($drive->{file})\n";
                                $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newid, $fmt, undef, ($size/1024));
 
-                               PVE::QemuServer::qemu_img_convert($drive->{file}, $newvolid, $size);
+                               PVE::QemuServer::qemu_img_convert($drive->{file}, $newvolid, $size, $snapname);
                            }
 
                            my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
@@ -1940,6 +2024,12 @@ __PACKAGE__->register_method({
 
                    delete $newconf->{lock};
                    PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
+
+                    if ($target) {
+                       my $newconffile = PVE::QemuServer::config_file($newid, $target);
+                       die "Failed to move config to node '$target' - rename failed: $!\n"
+                           if !rename($conffile, $newconffile);
+                   }
                };
                if (my $err = $@) { 
                    unlink $conffile;
@@ -1959,8 +2049,7 @@ __PACKAGE__->register_method({
            return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
        };
 
-       # Aquire shared lock for $vmid
-       return PVE::QemuServer::lock_config_shared($vmid, 1, sub {
+       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);
        });