[pve-devel] [PATCH 1/4] qm copy implementation

Alexandre Derumier aderumier at odiso.com
Fri Oct 26 14:32:01 CEST 2012


This add

qm copy <vmid> <vmiddest>

This duplicate vmid config, regenerate mac address, and copy disks for new vm.

default destination storeid is the same than source storeid.
default format (if file) is the same than source format.

destinations storage and file format can be override with

qm copy <vmid> <vmiddest> -virtio0 local:qcow2 -virtio1 sheepdog: -virtio2 nfs:raw  -virtio3 rbd:

Current implement don't copy snapshots, and copy disks from "you are here" state

Signed-off-by: Alexandre Derumier <aderumier at odiso.com>
---
 PVE/API2/Qemu.pm |  137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 qm               |    2 +
 2 files changed, 139 insertions(+)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index f4ae566..6ff3b79 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -418,6 +418,143 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
+    name => 'copy_vm',
+    path => '',
+    method => 'POST',
+    description => "Copy 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'],
+	    ],
+    },
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+    	additionalProperties => 0,
+	properties => PVE::QemuServer::json_config_properties(
+	    {
+		node => get_standard_option('pve-node'),
+		vmid => get_standard_option('pve-vmid'),
+		vmiddest => get_standard_option('pve-vmid'),
+		pool => { 
+		    optional => 1,
+		    type => 'string', format => 'pve-poolid',
+		    description => "Add the VM to the specified pool.",
+		},
+	    }),
+    },
+    returns => {
+	type => 'string',
+    },
+    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 $vmiddest = extract_param($param, 'vmiddest');
+
+	my $pool = extract_param($param, 'pool');
+
+	my $filename = PVE::QemuServer::config_file($vmiddest);
+
+	my $storecfg = PVE::Storage::config();
+
+	my $conf = PVE::QemuServer::load_config($vmid);
+
+	PVE::Cluster::check_cfs_quorum();
+
+	if (defined($pool)) {
+	    $rpcenv->check_pool_exist($pool);
+	} 
+
+
+    	&$resolve_cdrom_alias($param);
+	&$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
+
+	#fixme
+	#&$check_storage_access($rpcenv, $authuser, $storecfg, $vmiddest, $param, $storage);
+
+	#remove snapshots from conf (we only copy from "you are here" for now)
+	delete $conf->{parent};
+	delete $conf->{snapshots};
+
+       foreach my $opt (keys %$conf) {
+            if ($opt =~ m/^net(\d+)$/) {
+                # add macaddr
+                my $net = PVE::QemuServer::parse_net($conf->{$opt});
+		$net->{macaddr} =  PVE::Tools::random_ether_addr();
+                $conf->{$opt} = PVE::QemuServer::print_net($net);
+            }
+        }
+
+	my $addVMtoPoolFn = sub {		       
+	    my $usercfg = cfs_read_file("user.cfg");
+	    if (my $data = $usercfg->{pools}->{$pool}) {
+		$data->{vms}->{$vmiddest} = 1;
+		$usercfg->{vms}->{$vmiddest} = $pool;
+		cfs_write_file("user.cfg", $usercfg);
+	    }
+	};
+
+        my $createfn = sub {
+
+            # test after locking
+            die "unable to create vm $vmiddest: config file already exists\n"
+                if -f $filename;
+
+            my $realcmd = sub {
+
+                my $vollist = [];
+
+                eval {
+
+                    $vollist = &$copy_disks($rpcenv, $authuser, $conf, $storecfg, $vmiddest, $pool, $param);
+
+                    # try to be smart about bootdisk
+                    my @disks = PVE::QemuServer::disknames();
+                    my $firstdisk;
+                    foreach my $ds (reverse @disks) {
+                        next if !$conf->{$ds};
+                        my $disk = PVE::QemuServer::parse_drive($ds, $conf->{$ds});
+                        next if PVE::QemuServer::drive_is_cdrom($disk);
+                        $firstdisk = $ds;
+                    }
+
+                    if (!$conf->{bootdisk} && $firstdisk) {
+                        $conf->{bootdisk} = $firstdisk;
+                    }
+
+                    PVE::QemuServer::update_config_nolock($vmiddest, $conf);
+
+                };
+                my $err = $@;
+
+                if ($err) {
+                    foreach my $volid (@$vollist) {
+                        eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+                        warn $@ if $@;
+                    }
+                    die "copy failed - $err";
+                }
+
+                PVE::AccessControl::lock_user_config($addVMtoPoolFn, "can't add VM to pool") if $pool;
+            };
+
+            return $rpcenv->fork_worker('qmcopy', $vmiddest, $authuser, $realcmd);
+        };
+
+	return PVE::QemuServer::lock_config_full($vmiddest, 1, $createfn);
+    }});
+
+__PACKAGE__->register_method({
     name => 'vmdiridx',
     path => '{vmid}',
     method => 'GET',
diff --git a/qm b/qm
index 25f84ea..3a8712e 100755
--- a/qm
+++ b/qm
@@ -409,6 +409,8 @@ my $cmddef = {
 
     create => [ "PVE::API2::Qemu", 'create_vm', ['vmid'], { node => $nodename }, $upid_exit ],
 
+    copy => [ "PVE::API2::Qemu", 'copy_vm', ['vmid', 'vmiddest'], { node => $nodename }, $upid_exit ],
+
     destroy => [ "PVE::API2::Qemu", 'destroy_vm', ['vmid'], { node => $nodename }, $upid_exit ],
 
     migrate => [ "PVE::API2::Qemu", 'migrate_vm', ['vmid', 'target'], { node => $nodename }, $upid_exit ],
-- 
1.7.10.4




More information about the pve-devel mailing list