[pve-devel] [PATCH qemu-server 2/4] api: clone: fork before locking
Fabian Grünbichler
f.gruenbichler at proxmox.com
Mon Jan 31 13:34:11 CET 2022
On January 27, 2022 3:01 pm, Fabian Ebner wrote:
> using the familiar early+repeated checks pattern from other API calls.
> Only intended functional changes are with regard to locking/forking.
two questions:
- the FW config cloning happens inside the worker now, while it was
previously before forking the worker (LGTM, but might be called out
explicitly if intentional ;))
- there are some checks at the start of the endpoint (checking
storage/target), which are not repeated after the fork+lock - while
unlikely, our view of storage.cfg could change in-between (lock guest
config -> cfs_update). should those be moved in the check sub (or into
the check_storage_access_clone helper)?
rest of the series LGTM
>
> For a full clone of a running VM without guest agent, this also fixes
> issuing vm_{resume,suspend} calls for drive mirror completion.
> Previously, those just timed out, because of not getting the lock:
>
>> create full clone of drive scsi0 (rbdkvm:vm-104-disk-0)
>> Formatting '/var/lib/vz/images/105/vm-105-disk-0.raw', fmt=raw
>> size=4294967296 preallocation=off
>> drive mirror is starting for drive-scsi0
>> drive-scsi0: transferred 2.0 MiB of 4.0 GiB (0.05%) in 0s
>> drive-scsi0: transferred 635.0 MiB of 4.0 GiB (15.50%) in 1s
>> drive-scsi0: transferred 1.6 GiB of 4.0 GiB (40.50%) in 2s
>> drive-scsi0: transferred 3.6 GiB of 4.0 GiB (90.23%) in 3s
>> drive-scsi0: transferred 4.0 GiB of 4.0 GiB (100.00%) in 4s, ready
>> all 'mirror' jobs are ready
>> suspend vm
>> trying to acquire lock...
>> can't lock file '/var/lock/qemu-server/lock-104.conf' - got timeout
>> drive-scsi0: Cancelling block job
>> drive-scsi0: Done.
>> resume vm
>> trying to acquire lock...
>> can't lock file '/var/lock/qemu-server/lock-104.conf' - got timeout
>
> Signed-off-by: Fabian Ebner <f.ebner at proxmox.com>
> ---
>
> Best viewed with -w.
>
> PVE/API2/Qemu.pm | 220 ++++++++++++++++++++++++-----------------------
> 1 file changed, 112 insertions(+), 108 deletions(-)
>
> diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
> index 6992f6f..38e08c8 100644
> --- a/PVE/API2/Qemu.pm
> +++ b/PVE/API2/Qemu.pm
> @@ -3079,9 +3079,7 @@ __PACKAGE__->register_method({
>
> my $running = PVE::QemuServer::check_running($vmid) || 0;
>
> - my $clonefn = sub {
> - # do all tests after lock but before forking worker - if possible
> -
> + my $load_and_check = sub {
> my $conf = PVE::QemuConfig->load_config($vmid);
> PVE::QemuConfig->check_lock($conf);
>
> @@ -3091,7 +3089,7 @@ __PACKAGE__->register_method({
> die "snapshot '$snapname' does not exist\n"
> if $snapname && !defined( $conf->{snapshots}->{$snapname});
>
> - my $full = extract_param($param, 'full') // !PVE::QemuConfig->is_template($conf);
> + my $full = $param->{full} // !PVE::QemuConfig->is_template($conf);
>
> die "parameter 'storage' not allowed for linked clones\n"
> if defined($storage) && !$full;
> @@ -3156,7 +3154,13 @@ __PACKAGE__->register_method({
> }
> }
>
> - # auto generate a new uuid
> + return ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone);
> + };
> +
> + my $clonefn = sub {
> + my ($conffile, $newconf, $oldconf, $vollist, $drives, $fullclone) = $load_and_check->();
> +
> + # auto generate a new uuid
> my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
> $smbios1->{uuid} = PVE::QemuServer::generate_uuid();
> $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
> @@ -3181,105 +3185,99 @@ __PACKAGE__->register_method({
> # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
> PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
>
> - my $realcmd = sub {
> - my $upid = shift;
> -
> - my $newvollist = [];
> - my $jobs = {};
> -
> - eval {
> - local $SIG{INT} =
> - local $SIG{TERM} =
> - local $SIG{QUIT} =
> - local $SIG{HUP} = sub { die "interrupted by signal\n"; };
> -
> - PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
> -
> - my $bwlimit = extract_param($param, 'bwlimit');
> -
> - my $total_jobs = scalar(keys %{$drives});
> - my $i = 1;
> -
> - foreach my $opt (sort keys %$drives) {
> - my $drive = $drives->{$opt};
> - my $skipcomplete = ($total_jobs != $i); # finish after last drive
> - my $completion = $skipcomplete ? 'skip' : 'complete';
> -
> - my $src_sid = PVE::Storage::parse_volume_id($drive->{file});
> - my $storage_list = [ $src_sid ];
> - push @$storage_list, $storage if defined($storage);
> - my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', $storage_list, $bwlimit);
> -
> - my $newdrive = PVE::QemuServer::clone_disk(
> - $storecfg,
> - $vmid,
> - $running,
> - $opt,
> - $drive,
> - $snapname,
> - $newid,
> - $storage,
> - $format,
> - $fullclone->{$opt},
> - $newvollist,
> - $jobs,
> - $completion,
> - $oldconf->{agent},
> - $clonelimit,
> - $oldconf
> - );
> -
> - $newconf->{$opt} = PVE::QemuServer::print_drive($newdrive);
> -
> - PVE::QemuConfig->write_config($newid, $newconf);
> - $i++;
> - }
> -
> - delete $newconf->{lock};
> -
> - # do not write pending changes
> - if (my @changes = keys %{$newconf->{pending}}) {
> - my $pending = join(',', @changes);
> - warn "found pending changes for '$pending', discarding for clone\n";
> - delete $newconf->{pending};
> - }
> -
> - PVE::QemuConfig->write_config($newid, $newconf);
> -
> - if ($target) {
> - # always deactivate volumes - avoid lvm LVs to be active on several nodes
> - PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
> - PVE::Storage::deactivate_volumes($storecfg, $newvollist);
> -
> - my $newconffile = PVE::QemuConfig->config_file($newid, $target);
> - die "Failed to move config to node '$target' - rename failed: $!\n"
> - if !rename($conffile, $newconffile);
> - }
> -
> - PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
> - };
> - if (my $err = $@) {
> - eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
> - sleep 1; # some storage like rbd need to wait before release volume - really?
> -
> - foreach my $volid (@$newvollist) {
> - eval { PVE::Storage::vdisk_free($storecfg, $volid); };
> - warn $@ if $@;
> - }
> -
> - PVE::Firewall::remove_vmfw_conf($newid);
> -
> - unlink $conffile; # avoid races -> last thing before die
> -
> - die "clone failed: $err";
> - }
> -
> - return;
> - };
> -
> PVE::Firewall::clone_vmfw_conf($vmid, $newid);
>
> - return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
> + my $newvollist = [];
> + my $jobs = {};
> +
> + eval {
> + local $SIG{INT} =
> + local $SIG{TERM} =
> + local $SIG{QUIT} =
> + local $SIG{HUP} = sub { die "interrupted by signal\n"; };
> +
> + PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
> +
> + my $bwlimit = extract_param($param, 'bwlimit');
> +
> + my $total_jobs = scalar(keys %{$drives});
> + my $i = 1;
> +
> + foreach my $opt (sort keys %$drives) {
> + my $drive = $drives->{$opt};
> + my $skipcomplete = ($total_jobs != $i); # finish after last drive
> + my $completion = $skipcomplete ? 'skip' : 'complete';
> +
> + my $src_sid = PVE::Storage::parse_volume_id($drive->{file});
> + my $storage_list = [ $src_sid ];
> + push @$storage_list, $storage if defined($storage);
> + my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', $storage_list, $bwlimit);
> +
> + my $newdrive = PVE::QemuServer::clone_disk(
> + $storecfg,
> + $vmid,
> + $running,
> + $opt,
> + $drive,
> + $snapname,
> + $newid,
> + $storage,
> + $format,
> + $fullclone->{$opt},
> + $newvollist,
> + $jobs,
> + $completion,
> + $oldconf->{agent},
> + $clonelimit,
> + $oldconf
> + );
> +
> + $newconf->{$opt} = PVE::QemuServer::print_drive($newdrive);
> +
> + PVE::QemuConfig->write_config($newid, $newconf);
> + $i++;
> + }
> +
> + delete $newconf->{lock};
> +
> + # do not write pending changes
> + if (my @changes = keys %{$newconf->{pending}}) {
> + my $pending = join(',', @changes);
> + warn "found pending changes for '$pending', discarding for clone\n";
> + delete $newconf->{pending};
> + }
> +
> + PVE::QemuConfig->write_config($newid, $newconf);
> +
> + if ($target) {
> + # always deactivate volumes - avoid lvm LVs to be active on several nodes
> + PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
> + PVE::Storage::deactivate_volumes($storecfg, $newvollist);
> +
> + my $newconffile = PVE::QemuConfig->config_file($newid, $target);
> + die "Failed to move config to node '$target' - rename failed: $!\n"
> + if !rename($conffile, $newconffile);
> + }
> +
> + PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
> + };
> + if (my $err = $@) {
> + eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
> + sleep 1; # some storage like rbd need to wait before release volume - really?
> +
> + foreach my $volid (@$newvollist) {
> + eval { PVE::Storage::vdisk_free($storecfg, $volid); };
> + warn $@ if $@;
> + }
> +
> + PVE::Firewall::remove_vmfw_conf($newid);
> +
> + unlink $conffile; # avoid races -> last thing before die
> +
> + die "clone failed: $err";
> + }
> +
> + return;
> };
>
> # Aquire exclusive lock lock for $newid
> @@ -3287,12 +3285,18 @@ __PACKAGE__->register_method({
> return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
> };
>
> - # exclusive lock if VM is running - else shared lock is enough;
> - if ($running) {
> - return PVE::QemuConfig->lock_config_full($vmid, 1, $lock_target_vm);
> - } else {
> - return PVE::QemuConfig->lock_config_shared($vmid, 1, $lock_target_vm);
> - }
> + my $lock_source_vm = sub {
> + # exclusive lock if VM is running - else shared lock is enough;
> + if ($running) {
> + return PVE::QemuConfig->lock_config_full($vmid, 1, $lock_target_vm);
> + } else {
> + return PVE::QemuConfig->lock_config_shared($vmid, 1, $lock_target_vm);
> + }
> + };
> +
> + $load_and_check->(); # early checks before forking/locking
> +
> + return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $lock_source_vm);
> }});
>
> __PACKAGE__->register_method({
> --
> 2.30.2
>
>
>
> _______________________________________________
> pve-devel mailing list
> pve-devel at lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
>
>
>
More information about the pve-devel
mailing list