[pve-devel] [PATCH] Add copy_disk and change_disk command

Serge NOEL serge.noel2008 at gmail.com
Fri May 22 12:29:50 CEST 2015


Hello,

this patch allow to copy a disk from a storage to another (without 
moving it), it's goal is to replace backup procedures when someone has 
dual bay store.

When we use backup provided from preliminary Proxmox, you have to save 
(with compression), and to restore to decompress file generated, wich 
can take some time.

In our configuration, we have a disk bay (DS3300) and a NAS. By copying 
directly disk from the bay to the NAS, we have just to change target 
drive in case of bay failure.

I hope this patch will be present in futures release of Proxmox. Please 
Dietmar, answser me on this particular point.

Regards
Serge NOEL

Signed-off-by: Serge NOEL <serge.noel at net6a.com>
---
  PVE/API2/Qemu.pm | 321 
+++++++++++++++++++++++++++++++++++++++++++++++++++----
  qm               |   4 +
  2 files changed, 304 insertions(+), 21 deletions(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index fae2872..8fbbe6d 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -20,7 +20,6 @@ use PVE::AccessControl;
  use PVE::INotify;
  use PVE::Network;
  use PVE::API2::Firewall::VM;
-use PVE::HA::Config;
   use Data::Dumper; # fixme: remove
  @@ -688,7 +687,7 @@ __PACKAGE__->register_method({
  	delete $conf->{snapshots};
   	if (!$param->{current}) {
-	    foreach my $opt (keys %{$conf->{pending}}) {
+	    foreach my $opt (keys $conf->{pending}) {
  		next if $opt eq 'delete';
  		my $value = $conf->{pending}->{$opt};
  		next if ref($value); # just to be sure
@@ -759,7 +758,7 @@ __PACKAGE__->register_method({
   	my $res = [];
  -	foreach my $opt (keys %$conf) {
+	foreach my $opt (keys $conf) {
  	    next if ref($conf->{$opt});
  	    my $item = { key => $opt };
  	    $item->{value} = $conf->{$opt} if defined($conf->{$opt});
@@ -768,7 +767,7 @@ __PACKAGE__->register_method({
  	    push @$res, $item;
  	}
  -	foreach my $opt (keys %{$conf->{pending}}) {
+	foreach my $opt (keys $conf->{pending}) {
  	    next if $opt eq 'delete';
  	    next if ref($conf->{pending}->{$opt}); # just to be sure
  	    next if defined($conf->{$opt});
@@ -1299,19 +1298,17 @@ __PACKAGE__->register_method({
  	$sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 
8192)
  	    if !$sslcert;
  -	my ($remip, $family);
+	my $port = PVE::Tools::next_vnc_port();
+
+	my $remip;
  	my $remcmd = [];
   	if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
-	    ($remip, $family) = PVE::Cluster::remote_node_ip($node);
+	    $remip = PVE::Cluster::remote_node_ip($node);
  	    # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
  	    $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
-	} else {
-	    $family = PVE::Tools::get_host_address_family($node);
  	}
  -	my $port = PVE::Tools::next_vnc_port($family);
-
  	my $timeout = 10;
   	my $realcmd = sub {
@@ -1339,7 +1336,7 @@ __PACKAGE__->register_method({
  		my $qmstr = join(' ', @$qmcmd);
   		# also redirect stderr (else we get RFB protocol errors)
-		$cmd = ['/bin/nc6', '-l', '-p', $port, '-w', $timeout, '-e', "$qmstr 
2>/dev/null"];
+		$cmd = ['/bin/nc', '-l', '-p', $port, '-w', $timeout, '-c', "$qmstr 
2>/dev/null"];
  	    }
   	    PVE::Tools::run_command($cmd);
@@ -1503,6 +1500,16 @@ __PACKAGE__->register_method({
  	return $res;
      }});
  +my $vm_is_ha_managed = sub {
+    my ($vmid) = @_;
+
+    my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
+    if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $vmid, 1)) {
+	return 1;
+    }
+    return 0;
+};
+
  __PACKAGE__->register_method({
      name => 'vm_status',
      path => '{vmid}/status/current',
@@ -1530,7 +1537,7 @@ __PACKAGE__->register_method({
  	my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
  	my $status = $vmstatus->{$param->{vmid}};
  -	$status->{ha} = PVE::HA::Config::vm_is_ha_managed($param->{vmid});
+	$status->{ha} = &$vm_is_ha_managed($param->{vmid});
   	$status->{spice} = 1 if 
PVE::QemuServer::vga_conf_has_spice($conf->{vga});
  @@ -1597,15 +1604,15 @@ __PACKAGE__->register_method({
   	my $storecfg = PVE::Storage::config();
  -	if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri &&
+	if (&$vm_is_ha_managed($vmid) && !$stateuri &&
  	    $rpcenv->{type} ne 'ha') {
   	    my $hacmd = sub {
  		my $upid = shift;
  -		my $service = "vm:$vmid";
+		my $service = "pvevm:$vmid";
  -		my $cmd = ['ha-manager', 'enable', $service];
+		my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
   		print "Executing HA start for VM $vmid\n";
  @@ -1693,14 +1700,14 @@ __PACKAGE__->register_method({
   	my $storecfg = PVE::Storage::config();
  -	if (PVE::HA::Config::vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 
'ha') && !defined($migratedfrom)) {
+	if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && 
!defined($migratedfrom)) {
   	    my $hacmd = sub {
  		my $upid = shift;
  -		my $service = "vm:$vmid";
+		my $service = "pvevm:$vmid";
  -		my $cmd = ['ha-manager', 'disable', $service];
+		my $cmd = ['clusvcadm', '-d', $service];
   		print "Executing HA stop for VM $vmid\n";
  @@ -2496,6 +2503,278 @@ __PACKAGE__->register_method({
      }});
   __PACKAGE__->register_method({
+    name => 'copy_vm_disk',
+    path => '{vmid}/copy_disk',
+    method => 'POST',
+    protected => 1,
+    proxyto => 'node',
+    description => "Copy 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 copy.",
+		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,
+            },
+	    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 copy 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 copy on the same storage with same format\n" if 
$oldstoreid eq $storeid &&
+                (!$format || !$oldfmt || $oldfmt eq $format);
+
+	    PVE::Cluster::log_msg('info', $authuser, "copy 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);
+
+		};
+	    };
+
+            return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, 
$realcmd);
+	};
+
+	return PVE::QemuServer::lock_config($vmid, $updatefn);
+    }});
+
+__PACKAGE__->register_method({
+    name => 'change_vm_disk',
+    path => '{vmid}/change_disk',
+    method => 'POST',
+    protected => 1,
+    proxyto => 'node',
+    description => "Change 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 change.",
+		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 change. 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 change 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 change on the same storage with same format\n" if 
$oldstoreid eq $storeid &&
+                (!$format || !$oldfmt || $oldfmt eq $format);
+
+	    PVE::Cluster::log_msg('info', $authuser, "change disk VM $vmid: 
change --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"; };
+
+		    $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);
+
+		    eval {
+			# try to deactivate volumes - avoid lvm LVs to be active on several 
nodes
+			PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
+			    if !$running;
+		    };
+		    warn $@ if $@;
+		};
+		if (my $err = $@) {
+
+                   foreach my $volid (@$newvollist) {
+                        eval { PVE::Storage::vdisk_free($storecfg, 
$volid); };
+                        warn $@ if $@;
+                    }
+		    die "storage migration failed: $err";
+                }
+
+		if ($param->{delete}) {
+                    my $used_paths = 
PVE::QemuServer::get_used_paths($vmid, $storecfg, $conf, 1, 1);
+                    my $path = PVE::Storage::path($storecfg, $old_volid);
+		    if ($used_paths->{$path}){
+			warn "volume $old_volid have snapshots. Can't delete it\n";
+			PVE::QemuServer::add_unused_volume($conf, $old_volid);
+			PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+		    } else {
+			eval { PVE::Storage::vdisk_free($storecfg, $old_volid); };
+			warn $@ if $@;
+		    }
+		}
+	    };
+
+            return $rpcenv->fork_worker('qmchange', $vmid, $authuser, 
$realcmd);
+	};
+
+	return PVE::QemuServer::lock_config($vmid, $updatefn);
+    }});
+
+__PACKAGE__->register_method({
      name => 'migrate_vm',
      path => '{vmid}/migrate',
      method => 'POST',
@@ -2565,14 +2844,14 @@ __PACKAGE__->register_method({
  	my $storecfg = PVE::Storage::config();
  	PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
  -	if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 
'ha') {
+	if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
   	    my $hacmd = sub {
  		my $upid = shift;
  -		my $service = "vm:$vmid";
+		my $service = "pvevm:$vmid";
  -		my $cmd = ['ha-manager', 'migrate', $service, $target];
+		my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
   		print "Executing HA migrate for VM $vmid to node $target\n";
  diff --git a/qm b/qm
index b661723..68c2dde 100755
--- a/qm
+++ b/qm
@@ -423,6 +423,10 @@ my $cmddef = {
       move_disk => [ "PVE::API2::Qemu", 'move_vm_disk', ['vmid', 
'disk', 'storage'], { node => $nodename }, $upid_exit ],
  +    copy_disk => [ "PVE::API2::Qemu", 'copy_vm_disk', ['vmid', 
'disk', 'storage'], { node => $nodename }, $upid_exit ],
+
+    change_disk => [ "PVE::API2::Qemu", 'change_vm_disk', ['vmid', 
'disk', 'storage'], { node => $nodename }, $upid_exit ],
+
      unlink => [ "PVE::API2::Qemu", 'unlink', ['vmid'], { node => 
$nodename } ],
       config => [ "PVE::API2::Qemu", 'vm_config', ['vmid'],
-- 
2.1.4






More information about the pve-devel mailing list