mirror of
https://git.proxmox.com/git/qemu-server
synced 2025-08-07 00:41:58 +00:00
implement shared file locks
and add a first prototype for copy_vm
This commit is contained in:
parent
6fa741fcc3
commit
6116f72902
2
Makefile
2
Makefile
@ -2,7 +2,7 @@ RELEASE=3.0
|
|||||||
|
|
||||||
VERSION=3.0
|
VERSION=3.0
|
||||||
PACKAGE=qemu-server
|
PACKAGE=qemu-server
|
||||||
PKGREL=4
|
PKGREL=5
|
||||||
|
|
||||||
DESTDIR=
|
DESTDIR=
|
||||||
PREFIX=/usr
|
PREFIX=/usr
|
||||||
|
189
PVE/API2/Qemu.pm
189
PVE/API2/Qemu.pm
@ -59,6 +59,32 @@ my $check_storage_access = sub {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
my $check_storage_access_copy = sub {
|
||||||
|
my ($rpcenv, $authuser, $storecfg, $conf) = @_;
|
||||||
|
|
||||||
|
PVE::QemuServer::foreach_drive($conf, sub {
|
||||||
|
my ($ds, $drive) = @_;
|
||||||
|
|
||||||
|
my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
|
||||||
|
|
||||||
|
my $volid = $drive->{file};
|
||||||
|
|
||||||
|
return if !$volid || $volid eq 'none';
|
||||||
|
|
||||||
|
if ($isCDROM) {
|
||||||
|
if ($volid eq 'cdrom') {
|
||||||
|
$rpcenv->check($authuser, "/", ['Sys.Console']);
|
||||||
|
} else {
|
||||||
|
# we simply allow access
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
|
||||||
|
die "unable to copy arbitrary files\n" if !$sid;
|
||||||
|
$rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
# Note: $pool is only needed when creating a VM, because pool permissions
|
# Note: $pool is only needed when creating a VM, because pool permissions
|
||||||
# are automatically inherited if VM already exists inside a pool.
|
# are automatically inherited if VM already exists inside a pool.
|
||||||
my $create_disks = sub {
|
my $create_disks = sub {
|
||||||
@ -344,6 +370,7 @@ __PACKAGE__->register_method({
|
|||||||
|
|
||||||
my $restorefn = sub {
|
my $restorefn = sub {
|
||||||
|
|
||||||
|
# fixme: this test does not work if VM exists on other node!
|
||||||
if (-f $filename) {
|
if (-f $filename) {
|
||||||
die "unable to restore vm $vmid: config file already exists\n"
|
die "unable to restore vm $vmid: config file already exists\n"
|
||||||
if !$force;
|
if !$force;
|
||||||
@ -1746,6 +1773,168 @@ __PACKAGE__->register_method({
|
|||||||
return $res;
|
return $res;
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
__PACKAGE__->register_method({
|
||||||
|
name => 'copy_vm',
|
||||||
|
path => '{vmid}/copy',
|
||||||
|
method => 'POST',
|
||||||
|
protected => 1,
|
||||||
|
proxyto => 'node',
|
||||||
|
description => "Creat a copy of virtual machine/template.",
|
||||||
|
permissions => {
|
||||||
|
description => "You need 'VM.Copy' 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',
|
||||||
|
[ 'perm', '/vms/{newid}', ['VM.Allocate']],
|
||||||
|
[ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
},
|
||||||
|
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.' }),
|
||||||
|
pool => {
|
||||||
|
optional => 1,
|
||||||
|
type => 'string', format => 'pve-poolid',
|
||||||
|
description => "Add the new VM to the specified pool.",
|
||||||
|
},
|
||||||
|
full => {
|
||||||
|
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.",
|
||||||
|
default => 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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 $newid = extract_param($param, 'newid');
|
||||||
|
|
||||||
|
# fixme: update pool after create
|
||||||
|
my $pool = extract_param($param, 'pool');
|
||||||
|
|
||||||
|
if (defined($pool)) {
|
||||||
|
$rpcenv->check_pool_exist($pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $storecfg = PVE::Storage::config();
|
||||||
|
|
||||||
|
PVE::Cluster::check_cfs_quorum();
|
||||||
|
|
||||||
|
# fixme: do early checks - re-check after lock
|
||||||
|
|
||||||
|
# fixme: impl. target node parameter (mv VM config if all storages are shared)
|
||||||
|
|
||||||
|
my $copyfn = sub {
|
||||||
|
|
||||||
|
# all tests after lock
|
||||||
|
my $conf = PVE::QemuServer::load_config($vmid);
|
||||||
|
|
||||||
|
PVE::QemuServer::check_lock($conf);
|
||||||
|
|
||||||
|
my $running = PVE::QemuServer::check_running($vmid);
|
||||||
|
|
||||||
|
die "Copy running VM $vmid not implemented\n" if $running;
|
||||||
|
|
||||||
|
&$check_storage_access_copy($rpcenv, $authuser, $storecfg, $conf);
|
||||||
|
|
||||||
|
# fixme: snapshots??
|
||||||
|
|
||||||
|
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\n");
|
||||||
|
|
||||||
|
my $realcmd = sub {
|
||||||
|
my $upid = shift;
|
||||||
|
|
||||||
|
eval {
|
||||||
|
print "COPY VM $vmid start\n";
|
||||||
|
|
||||||
|
my $newconf = {};
|
||||||
|
my $drives = {};
|
||||||
|
my $vollist = [];
|
||||||
|
foreach my $opt (keys %$conf) {
|
||||||
|
my $value = $conf->{$opt};
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete $newconf->{template};
|
||||||
|
|
||||||
|
PVE::Storage::activate_volumes($storecfg, $vollist);
|
||||||
|
|
||||||
|
foreach my $opt (keys %$drives) {
|
||||||
|
my $drive = $drives->{$opt};
|
||||||
|
print "COPY drive $opt: $drive->{file}\n";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(10);
|
||||||
|
PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
|
||||||
|
|
||||||
|
print "COPY VM $vmid end\n";
|
||||||
|
};
|
||||||
|
if (my $err = $@) {
|
||||||
|
# fixme: remove all created files
|
||||||
|
unlink $conffile;
|
||||||
|
|
||||||
|
die "copy failed: $err";
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
return $rpcenv->fork_worker('qmcopy', $vmid, $authuser, $realcmd);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Aquire shared lock for $vmid
|
||||||
|
return PVE::QemuServer::lock_config_shared($vmid, 1, sub {
|
||||||
|
# Aquire exclusive lock lock for $newid
|
||||||
|
return PVE::QemuServer::lock_config_full($newid, 1, $copyfn);
|
||||||
|
});
|
||||||
|
|
||||||
|
}});
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
name => 'migrate_vm',
|
name => 'migrate_vm',
|
||||||
path => '{vmid}/migrate',
|
path => '{vmid}/migrate',
|
||||||
|
@ -21,7 +21,7 @@ use PVE::SafeSyslog;
|
|||||||
use Storable qw(dclone);
|
use Storable qw(dclone);
|
||||||
use PVE::Exception qw(raise raise_param_exc);
|
use PVE::Exception qw(raise raise_param_exc);
|
||||||
use PVE::Storage;
|
use PVE::Storage;
|
||||||
use PVE::Tools qw(run_command lock_file file_read_firstline);
|
use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline);
|
||||||
use PVE::JSONSchema qw(get_standard_option);
|
use PVE::JSONSchema qw(get_standard_option);
|
||||||
use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
|
use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
|
||||||
use PVE::INotify;
|
use PVE::INotify;
|
||||||
@ -1479,6 +1479,18 @@ sub lock_config_full {
|
|||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub lock_config_shared {
|
||||||
|
my ($vmid, $timeout, $code, @param) = @_;
|
||||||
|
|
||||||
|
my $filename = config_file_lock($vmid);
|
||||||
|
|
||||||
|
my $res = lock_file_full($filename, $timeout, 1, $code, @param);
|
||||||
|
|
||||||
|
die $@ if $@;
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
sub lock_config {
|
sub lock_config {
|
||||||
my ($vmid, $code, @param) = @_;
|
my ($vmid, $code, @param) = @_;
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
qemu-server (3.0-5) unstable; urgency=low
|
||||||
|
|
||||||
|
* implement shared file locks
|
||||||
|
|
||||||
|
-- Proxmox Support Team <support@proxmox.com> Thu, 25 Apr 2013 11:35:01 +0200
|
||||||
|
|
||||||
qemu-server (3.0-4) unstable; urgency=low
|
qemu-server (3.0-4) unstable; urgency=low
|
||||||
|
|
||||||
* fix bug 377: make qm rescan work properly
|
* fix bug 377: make qm rescan work properly
|
||||||
|
2
qm
2
qm
@ -351,6 +351,8 @@ my $cmddef = {
|
|||||||
|
|
||||||
destroy => [ "PVE::API2::Qemu", 'destroy_vm', ['vmid'], { node => $nodename }, $upid_exit ],
|
destroy => [ "PVE::API2::Qemu", 'destroy_vm', ['vmid'], { node => $nodename }, $upid_exit ],
|
||||||
|
|
||||||
|
copy => [ "PVE::API2::Qemu", 'copy_vm', ['vmid', 'newid'], { node => $nodename }, $upid_exit ],
|
||||||
|
|
||||||
migrate => [ "PVE::API2::Qemu", 'migrate_vm', ['vmid', 'target'], { node => $nodename }, $upid_exit ],
|
migrate => [ "PVE::API2::Qemu", 'migrate_vm', ['vmid', 'target'], { node => $nodename }, $upid_exit ],
|
||||||
|
|
||||||
set => [ "PVE::API2::Qemu", 'update_vm', ['vmid'], { node => $nodename } ],
|
set => [ "PVE::API2::Qemu", 'update_vm', ['vmid'], { node => $nodename } ],
|
||||||
|
Loading…
Reference in New Issue
Block a user