From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 4731F1FF16B for ; Thu, 14 Nov 2024 16:09:18 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id A2B543433C; Thu, 14 Nov 2024 16:08:13 +0100 (CET) From: Fiona Ebner To: pve-devel@lists.proxmox.com Date: Thu, 14 Nov 2024 16:07:44 +0100 Message-Id: <20241114150754.374376-18-f.ebner@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20241114150754.374376-1-f.ebner@proxmox.com> References: <20241114150754.374376-1-f.ebner@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.055 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy 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 Subject: [pve-devel] [PATCH qemu-server v4 17/27] backup: implement restore for external providers 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: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" First, the provider is asked about what restore mechanism to use. Currently, only 'qemu-img' is possible. Then the configuration files are restored, the provider gives information about volumes contained in the backup and finally the volumes are restored via 'qemu-img convert'. The code for the restore_external_archive() function was copied and adapted from the restore_proxmox_backup_archive() function. Together with restore_vma_archive() it seems sensible to extract the common parts and use a dedicated module for restore code. The parse_restore_archive() helper was renamed, because it's not just parsing. Signed-off-by: Fiona Ebner --- Changes in v4: * improve handling for temporary directory PVE/API2/Qemu.pm | 30 +++++++++- PVE/QemuServer.pm | 139 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 3 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 1c3cb271..319518e8 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -845,7 +845,7 @@ __PACKAGE__->register_method({ return $res; }}); -my $parse_restore_archive = sub { +my $classify_restore_archive = sub { my ($storecfg, $archive) = @_; my ($archive_storeid, $archive_volname) = PVE::Storage::parse_volume_id($archive, 1); @@ -859,6 +859,22 @@ my $parse_restore_archive = sub { $res->{type} = 'pbs'; return $res; } + if (PVE::Storage::storage_has_feature($storecfg, $archive_storeid, 'backup-provider')) { + my $log_function = sub { + my ($log_level, $message) = @_; + my $prefix = $log_level eq 'err' ? 'ERROR' : uc($log_level); + print "$prefix: $message\n"; + }; + my $backup_provider = PVE::Storage::new_backup_provider( + $storecfg, + $archive_storeid, + $log_function, + ); + + $res->{type} = 'external'; + $res->{'backup-provider'} = $backup_provider; + return $res; + } } my $path = PVE::Storage::abs_filesystem_path($storecfg, $archive); $res->{type} = 'file'; @@ -1011,7 +1027,7 @@ __PACKAGE__->register_method({ 'backup', ); - $archive = $parse_restore_archive->($storecfg, $archive); + $archive = $classify_restore_archive->($storecfg, $archive); } } @@ -1069,7 +1085,15 @@ __PACKAGE__->register_method({ PVE::QemuServer::check_restore_permissions($rpcenv, $authuser, $merged); } } - if ($archive->{type} eq 'file' || $archive->{type} eq 'pipe') { + if (my $backup_provider = $archive->{'backup-provider'}) { + PVE::QemuServer::restore_external_archive( + $backup_provider, + $archive->{volid}, + $vmid, + $authuser, + $restore_options, + ); + } elsif ($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; PVE::QemuServer::restore_file_archive($archive->{path} // '-', $vmid, $authuser, $restore_options); diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index cb1e0b82..8beb8940 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -7306,6 +7306,145 @@ sub restore_proxmox_backup_archive { } } +sub restore_external_archive { + my ($backup_provider, $archive, $vmid, $user, $options) = @_; + + die "live restore from backup provider is not implemented\n" if $options->{live}; + + my $storecfg = PVE::Storage::config(); + + my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive); + my $scfg = PVE::Storage::storage_config($storecfg, $storeid); + + my $tmpdir = "/run/qemu-server/vzdumptmp$$"; + rmtree($tmpdir); + mkpath($tmpdir) or die "unable to create $tmpdir\n"; + + my $conffile = PVE::QemuConfig->config_file($vmid); + # disable interrupts (always do cleanups) + local $SIG{INT} = + local $SIG{TERM} = + local $SIG{QUIT} = + local $SIG{HUP} = sub { print STDERR "got interrupt - ignored\n"; }; + + # Note: $oldconf is undef if VM does not exists + my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid); + my $oldconf = PVE::Cluster::cfs_read_file($cfs_path); + my $new_conf_raw = ''; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $devinfo = {}; # info about drives included in backup + my $virtdev_hash = {}; # info about allocated drives + + eval { + # enable interrupts + local $SIG{INT} = + local $SIG{TERM} = + local $SIG{QUIT} = + local $SIG{HUP} = + local $SIG{PIPE} = sub { die "interrupted by signal\n"; }; + + my $cfgfn = "$tmpdir/qemu-server.conf"; + my $firewall_config_fn = "$tmpdir/fw.conf"; + + my $cmd = "restore"; + + my ($mechanism, $vmtype) = + $backup_provider->restore_get_mechanism($volname, $storeid); + die "mechanism '$mechanism' requested by backup provider is not supported for VMs\n" + if $mechanism ne 'qemu-img'; + die "cannot restore non-VM guest of type '$vmtype'\n" if $vmtype ne 'qemu'; + + $devinfo = $backup_provider->restore_vm_init($volname, $storeid); + + my $data = $backup_provider->restore_get_guest_config($volname, $storeid) + or die "backup provider failed to extract guest configuration\n"; + PVE::Tools::file_set_contents($cfgfn, $data); + + if ($data = $backup_provider->restore_get_firewall_config($volname, $storeid)) { + PVE::Tools::file_set_contents($firewall_config_fn, $data); + my $pve_firewall_dir = '/etc/pve/firewall'; + mkdir $pve_firewall_dir; # make sure the dir exists + PVE::Tools::file_copy($firewall_config_fn, "${pve_firewall_dir}/$vmid.fw"); + } + + my $fh = IO::File->new($cfgfn, "r") or die "unable to read qemu-server.conf - $!\n"; + + $virtdev_hash = $parse_backup_hints->($rpcenv, $user, $storecfg, $fh, $devinfo, $options); + + # create empty/temp config + PVE::Tools::file_set_contents($conffile, "memory: 128\nlock: create"); + + $restore_cleanup_oldconf->($storecfg, $vmid, $oldconf, $virtdev_hash) if $oldconf; + + # allocate volumes + my $map = $restore_allocate_devices->($storecfg, $virtdev_hash, $vmid); + + for my $virtdev (sort keys $virtdev_hash->%*) { + my $d = $virtdev_hash->{$virtdev}; + next if $d->{is_cloudinit}; # no need to restore cloudinit + + my $info = + $backup_provider->restore_vm_volume_init($volname, $storeid, $d->{devname}, {}); + my $source_path = $info->{'qemu-img-path'} + or die "did not get source image path from backup provider\n"; + eval { + qemu_img_convert( + $source_path, $d->{volid}, $d->{size}, undef, 0, $options->{bwlimit}); + }; + my $err = $@; + eval { + $backup_provider->restore_vm_volume_cleanup($volname, $storeid, $d->{devname}, {}); + }; + if (my $cleanup_err = $@) { + die $cleanup_err if !$err; + warn $cleanup_err; + } + die $err if $err + } + + $fh->seek(0, 0) || die "seek failed - $!\n"; + + my $cookie = { netcount => 0 }; + while (defined(my $line = <$fh>)) { + $new_conf_raw .= restore_update_config_line( + $cookie, + $map, + $line, + $options->{unique}, + ); + } + + $fh->close(); + }; + my $err = $@; + + eval { $backup_provider->restore_vm_cleanup($volname, $storeid); }; + warn "backup provider cleanup after restore failed - $@" if $@; + + if ($err) { + $restore_deactivate_volumes->($storecfg, $virtdev_hash); + } + + rmtree($tmpdir); + + if ($err) { + $restore_destroy_volumes->($storecfg, $virtdev_hash); + die $err; + } + + my $new_conf = restore_merge_config($conffile, $new_conf_raw, $options->{override_conf}); + check_restore_permissions($rpcenv, $user, $new_conf); + PVE::QemuConfig->write_config($vmid, $new_conf); + + eval { rescan($vmid, 1); }; + warn $@ if $@; + + PVE::AccessControl::add_vm_to_pool($vmid, $options->{pool}) if $options->{pool}; + + return; +} + sub pbs_live_restore { my ($vmid, $conf, $storecfg, $restored_disks, $opts) = @_; -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel