[pve-devel] [PATCH v3 qemu-server 3/3] restore: allow specifying drive actions during restore
Fabian Ebner
f.ebner at proxmox.com
Tue Apr 26 14:30:52 CEST 2022
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 <f.ebner at proxmox.com>
---
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 at 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
More information about the pve-devel
mailing list