[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