};
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) = @_;
$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
optional => 1,
}),
},
-
},
returns => {
type => 'boolean'
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',
my $rpcenv = PVE::RPCEnvironment::get();
- my $authuser = $rpcenv->get_user();
+ my $authuser = $rpcenv->get_user();
my $node = extract_param($param, 'node');
$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
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"
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+)$/) {
}
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);
$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);
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;
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);
});