From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 7C1AF97A5 for ; Tue, 26 Apr 2022 14:31:04 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 57E361B8FA for ; Tue, 26 Apr 2022 14:31:04 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 9FBA01B835 for ; Tue, 26 Apr 2022 14:30:59 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 792A042A84 for ; Tue, 26 Apr 2022 14:30:59 +0200 (CEST) From: Fabian Ebner To: pve-devel@lists.proxmox.com Date: Tue, 26 Apr 2022 14:30:52 +0200 Message-Id: <20220426123055.110358-4-f.ebner@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220426123055.110358-1-f.ebner@proxmox.com> References: <20220426123055.110358-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.083 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [qemu.pm, qemuserver.pm] Subject: [pve-devel] [PATCH v3 qemu-server 3/3] restore: allow specifying drive actions during restore X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 26 Apr 2022 12:31:04 -0000 Possible actions are restoring (with an optional target storage; default, when drive is not part of the backup), keeping the disk as unused (default, when drive is part of the backup) and keeping the disk configured. Signed-off-by: Fabian Ebner --- Changes from v2: * Switch to a parameter that allows explicitly setting the action for each drive and in case of restore, the target storage. This avoids automagic, covers all cases explicitly and allows for a per-disk target storage setting too. PVE/API2/Qemu.pm | 35 +++++++++++++++++++- PVE/QemuServer.pm | 83 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 54af11a0..7088e61f 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -745,6 +745,18 @@ __PACKAGE__->register_method({ description => "Start the VM immediately from the backup and restore in background. PBS only.", requires => 'archive', }, + 'restore-drive-actions' => { + optional => 1, + type => 'string', + format => 'pve-restore-drive-action-list', + description => "List of = pairs where the action can be ". + "'restore:' (restore from backup to specified storage; if no ". + "storage is specified, the default storage is used), 'unused' (keep ". + "current disk as unused) or 'preserve' (keep current config, even if ". + "empty). Default is 'restore' for drives in the backup and 'unused' for ". + "others.", + requires => 'archive', + }, pool => { optional => 1, type => 'string', format => 'pve-poolid', @@ -790,6 +802,11 @@ __PACKAGE__->register_method({ my $unique = extract_param($param, 'unique'); my $live_restore = extract_param($param, 'live-restore'); + my $restore_drive_actions = extract_param($param, 'restore-drive-actions'); + $restore_drive_actions = PVE::QemuServer::parse_restore_drive_actions( + $restore_drive_actions, + ); + if (defined(my $ssh_keys = $param->{sshkeys})) { $ssh_keys = URI::Escape::uri_unescape($ssh_keys); PVE::Tools::validate_ssh_public_keys($ssh_keys); @@ -821,7 +838,10 @@ __PACKAGE__->register_method({ if ($archive) { for my $opt (sort keys $param->%*) { if (PVE::QemuServer::Drive::is_valid_drivename($opt)) { - raise_param_exc({ $opt => "option conflicts with option 'archive'" }); + raise_param_exc({ + $opt => "option conflicts with option 'archive' (do you mean to use ". + "'restore-drive-actions'?)", + }); } } @@ -872,6 +892,17 @@ __PACKAGE__->register_method({ die "$emsg vm is running\n" if PVE::QemuServer::check_running($vmid); + my $restore_drives = []; + for my $drive (sort keys $restore_drive_actions->%*) { + my $action = $restore_drive_actions->{$drive}->{action}; + + die "$drive - invalid drive action '$action' - drive not present in config\n" + if !$conf->{$drive} && $action eq 'unused'; + + $param->{$drive} = $conf->{$drive} if $action eq 'preserve'; + $param->{$drive} = undef if $action eq 'unused'; + } + my $realcmd = sub { my $restore_options = { storage => $storage, @@ -880,7 +911,9 @@ __PACKAGE__->register_method({ bwlimit => $bwlimit, live => $live_restore, override_conf => $param, + drive_actions => $restore_drive_actions, }; + if ($archive->{type} eq 'file' || $archive->{type} eq 'pipe') { die "live-restore is only compatible with backup images from a Proxmox Backup Server\n" if $live_restore; diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 2a3e6d58..ad46741b 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -1206,6 +1206,46 @@ sub print_bootorder { return PVE::JSONSchema::print_property_string($data, $boot_fmt); } +PVE::JSONSchema::register_format('pve-restore-drive-action', \&verify_restore_drive_action); +sub verify_restore_drive_action { + my ($kv, $noerr) = @_; + + my $actions = eval { parse_restore_drive_actions($kv) }; + if (my $err = $@) { + die $err if !$noerr; + return; + } + return $actions; +} + +sub parse_restore_drive_actions { + my ($string) = @_; + + my $actions = {}; + + for my $kv (PVE::Tools::split_list($string)) { + die "expected a drive=action pair\n" if $kv !~ m/^([^=]+)=(.+)$/; + my ($drive, $action) = ($1, $2); + die "invalid drivename '$drive'\n" if !PVE::QemuServer::Drive::is_valid_drivename($drive); + die "drivename '$drive' specified multiple times\n" if $actions->{$drive}; + + if ($action =~ m/^restore(?::(.*))?$/) { + $actions->{$drive} = { action => 'restore' }; + if (my $storage = $1) { + eval { PVE::JSONSchema::check_format('pve-storage-id', $storage, ''); }; + die "invalid storage ID '$storage' - $@\n" if $@; + $actions->{$drive}->{storage} = $storage; + } + } elsif ($action eq 'preserve' || $action eq 'unused') { + $actions->{$drive} = { action => $action }; + } else { + die "invalid action '$action'\n"; + } + } + + return $actions; +} + my $kvm_api_version = 0; sub kvm_version { @@ -6295,7 +6335,10 @@ my $parse_backup_hints = sub { if $user ne 'root@pam'; }; + my $drive_actions = $options->{drive_actions}; + my $virtdev_hash = {}; + my $cdroms = {}; while (defined(my $line = <$fh>)) { if ($line =~ m/^\#qmdump\#map:(\S+):(\S+):(\S*):(\S*):$/) { my ($virtdev, $devname, $storeid, $format) = ($1, $2, $3, $4); @@ -6311,6 +6354,12 @@ my $parse_backup_hints = sub { $devinfo->{$devname}->{devname} = $devname; $devinfo->{$devname}->{virtdev} = $virtdev; $devinfo->{$devname}->{format} = $format; + + if ($drive_actions->{$virtdev}) { + next if $drive_actions->{$virtdev}->{action} ne 'restore'; + $storeid = $drive_actions->{$virtdev}->{storage} || $storeid; + } + $devinfo->{$devname}->{storeid} = $storeid; my $scfg = PVE::Storage::storage_config($storecfg, $storeid); @@ -6336,9 +6385,17 @@ my $parse_backup_hints = sub { is_cloudinit => 1, }; } + + $cdroms->{$virtdev} = 1 if drive_is_cdrom($drive); } } + for my $virtdev (sort keys $drive_actions->%*) { + my $action = $drive_actions->{$virtdev}->{action}; + die "requested restore for drive '$virtdev', but not present in backup\n" + if $action eq 'restore' && !$virtdev_hash->{$virtdev} && !$cdroms->{$virtdev}; + } + return $virtdev_hash; }; @@ -6485,7 +6542,11 @@ my $restore_merge_config = sub { my $backup_conf = parse_vm_config($filename, $backup_conf_raw); for my $key (keys $override_conf->%*) { - $backup_conf->{$key} = $override_conf->{$key}; + if (defined($override_conf->{$key})) { + $backup_conf->{$key} = $override_conf->{$key}; + } else { + delete $backup_conf->{$key}; + } } return $backup_conf; @@ -6822,6 +6883,12 @@ sub restore_proxmox_backup_archive { # these special drives are already restored before start delete $devinfo->{'drive-efidisk0'}; delete $devinfo->{'drive-tpmstate0-backup'}; + + for my $key (keys $options->{drive_actions}->%*) { + delete $devinfo->{"drive-$key"} + if $options->{drive_actions}->{$key}->{action} ne 'restore'; + } + pbs_live_restore($vmid, $conf, $storecfg, $devinfo, $repo, $keyfile, $pbs_backup_name); PVE::QemuConfig->remove_lock($vmid, "create"); @@ -7014,8 +7081,15 @@ sub restore_vma_archive { my $map = $restore_allocate_devices->($cfg, $virtdev_hash, $vmid); # print restore information to $fifofh - foreach my $virtdev (sort keys %$virtdev_hash) { - my $d = $virtdev_hash->{$virtdev}; + for my $devname (sort keys $devinfo->%*) { + my $d = $devinfo->{$devname}; + + if (!$virtdev_hash->{$d->{virtdev}}) { # skipped + print $fifofh "skip=$d->{devname}\n"; + print "not restoring '$d->{devname}', but keeping current disk\n"; + next; + } + next if $d->{is_cloudinit}; # no need to restore cloudinit my $storeid = $d->{storeid}; @@ -7122,6 +7196,9 @@ sub restore_tar_archive { die "cannot pass along options ($keystring) when restoring from tar archive\n"; } + die "drive actions are not supported when restoring from tar archive\n" + if scalar(keys $opts->{drive_actions}->%*) > 0; + if ($archive ne '-') { my $firstfile = tar_archive_read_firstfile($archive); die "ERROR: file '$archive' does not look like a QemuServer vzdump backup\n" -- 2.30.2