From 6116f729028df07f5e00ab837c36d5fbf2443195 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Mon, 29 Apr 2013 09:30:15 +0200 Subject: [PATCH] implement shared file locks and add a first prototype for copy_vm --- Makefile | 2 +- PVE/API2/Qemu.pm | 189 ++++++++++++++++++++++++++++++++++++++++++++++ PVE/QemuServer.pm | 14 +++- changelog.Debian | 6 ++ qm | 2 + 5 files changed, 211 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index eef84f8..23fe5db 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ RELEASE=3.0 VERSION=3.0 PACKAGE=qemu-server -PKGREL=4 +PKGREL=5 DESTDIR= PREFIX=/usr diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 53ba9cb..82d3a7f 100644 --- a/PVE/API2/Qemu.pm +++ b/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 # are automatically inherited if VM already exists inside a pool. my $create_disks = sub { @@ -344,6 +370,7 @@ __PACKAGE__->register_method({ my $restorefn = sub { + # fixme: this test does not work if VM exists on other node! if (-f $filename) { die "unable to restore vm $vmid: config file already exists\n" if !$force; @@ -1746,6 +1773,168 @@ __PACKAGE__->register_method({ 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({ name => 'migrate_vm', path => '{vmid}/migrate', diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 4d2710a..853fd42 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -21,7 +21,7 @@ use PVE::SafeSyslog; use Storable qw(dclone); use PVE::Exception qw(raise raise_param_exc); 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::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file); use PVE::INotify; @@ -1479,6 +1479,18 @@ sub lock_config_full { 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 { my ($vmid, $code, @param) = @_; diff --git a/changelog.Debian b/changelog.Debian index d107dad..ad24667 100644 --- a/changelog.Debian +++ b/changelog.Debian @@ -1,3 +1,9 @@ +qemu-server (3.0-5) unstable; urgency=low + + * implement shared file locks + + -- Proxmox Support Team Thu, 25 Apr 2013 11:35:01 +0200 + qemu-server (3.0-4) unstable; urgency=low * fix bug 377: make qm rescan work properly diff --git a/qm b/qm index 2ab5eaa..7f9f47e 100755 --- a/qm +++ b/qm @@ -351,6 +351,8 @@ my $cmddef = { 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 ], set => [ "PVE::API2::Qemu", 'update_vm', ['vmid'], { node => $nodename } ], -- 2.39.2