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 <[email protected]>
---

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 <drive>=<action> pairs where the 
action can be ".
+                       "'restore:<storage ID>' (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



_______________________________________________
pve-devel mailing list
[email protected]
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to