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
