From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 774F11FF168 for ; Tue, 26 Nov 2024 16:24:17 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 33F055B53; Tue, 26 Nov 2024 16:24:14 +0100 (CET) From: Filip Schauer To: pve-devel@lists.proxmox.com Date: Tue, 26 Nov 2024 16:23:21 +0100 Message-Id: <20241126152325.113926-4-f.schauer@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20241126152325.113926-1-f.schauer@proxmox.com> References: <20241126152325.113926-1-f.schauer@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.027 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 storage v5 3/7] api: content: support moving backups between path based storages 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" This commit adds the "backup+size" export format. When this format is used, the data stream starts with metadata of the backup (protected flag & notes) followed by the contents of the backup archive. Signed-off-by: Filip Schauer --- src/PVE/API2/Storage/Content.pm | 15 ++++++++++-- src/PVE/Storage.pm | 10 +++++++- src/PVE/Storage/Plugin.pm | 42 +++++++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/PVE/API2/Storage/Content.pm b/src/PVE/API2/Storage/Content.pm index ac451dc..9ee3c51 100644 --- a/src/PVE/API2/Storage/Content.pm +++ b/src/PVE/API2/Storage/Content.pm @@ -548,10 +548,10 @@ __PACKAGE__->register_method ({ my $cfg = PVE::Storage::config(); - my ($vtype) = PVE::Storage::parse_volname($cfg, $src_volid); + my ($vtype, undef, $ownervm) = PVE::Storage::parse_volname($cfg, $src_volid); die "use pct move-volume or qm disk move" if $vtype eq 'images' || $vtype eq 'rootdir'; die "moving volume of type '$vtype' not implemented\n" - if (!grep { $vtype eq $_ } qw(import iso snippets vztmpl)); + if (!grep { $vtype eq $_ } qw(backup import iso snippets vztmpl)); my $rpcenv = PVE::RPCEnvironment::get(); my $user = $rpcenv->get_user(); @@ -560,10 +560,21 @@ __PACKAGE__->register_method ({ if ($delete) { $rpcenv->check($user, "/storage/$src_storeid", ["Datastore.Allocate"]); + + if ($vtype eq 'backup') { + my $src_cfg = PVE::Storage::storage_config($cfg, $src_storeid); + my $src_plugin = PVE::Storage::Plugin->lookup($src_cfg->{type}); + my $protected = $src_plugin->get_volume_attribute($src_cfg, $src_storeid, $volname, 'protected'); + die "cannot delete protected backup\n" if $protected; + } } else { $rpcenv->check($user, "/storage/$dst_storeid", ["Datastore.AllocateSpace"]); } + if ($vtype eq 'backup' && $ownervm) { + $rpcenv->check($user, "/vms/$ownervm", ['VM.Backup']); + } + my $worker = sub { PVE::Storage::storage_check_enabled($cfg, $dst_storeid, $dst_node); my $sshinfo; diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm index 230c57b..ae190cd 100755 --- a/src/PVE/Storage.pm +++ b/src/PVE/Storage.pm @@ -48,7 +48,15 @@ use constant APIVER => 10; # see https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html use constant APIAGE => 1; -our $KNOWN_EXPORT_FORMATS = ['raw+size', 'tar+size', 'qcow2+size', 'vmdk+size', 'zfs', 'btrfs']; +our $KNOWN_EXPORT_FORMATS = [ + 'raw+size', + 'tar+size', + 'qcow2+size', + 'vmdk+size', + 'zfs', + 'btrfs', + 'backup+size' +]; # load standard plugins PVE::Storage::DirPlugin->register(); diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm index c010d9c..4c3c3ac 100644 --- a/src/PVE/Storage/Plugin.pm +++ b/src/PVE/Storage/Plugin.pm @@ -1624,6 +1624,8 @@ sub prune_backups { # unprivileged container. In other words, this is from the root user # namespace's point of view with no uid-mapping in effect. # As produced via `tar -C vm-100-disk-1.subvol -cpf TheOutputFile.dat .` +# backup+size: (backups only) +# A raw binary data stream prefixed with a protection flag and notes. # Plugins may reuse these helpers. Changes to the header format should be # reflected by changes to the function prototypes. @@ -1670,7 +1672,18 @@ sub volume_export { die $err_msg if $file_format ne 'subvol'; write_common_header($fh, $size); run_command(['tar', @COMMON_TAR_FLAGS, '-cf', '-', '-C', $file, '.'], - output => '>&'.fileno($fh)); + output => '>&'.fileno($fh)); + return; + } elsif ($format eq 'backup+size') { + write_common_header($fh, $size); + my $protected = $class->get_volume_attribute( + $scfg, $storeid, $volname, 'protected') ? 1 : 0; + my $notes = Encode::encode( + 'utf8', $class->get_volume_attribute($scfg, $storeid, $volname, 'notes')) // ""; + syswrite($fh, pack("C", $protected), 1); + syswrite($fh, pack("Q<", length($notes)), 8); + syswrite($fh, $notes, length($notes)); + run_command(['dd', "if=$file", "bs=4k", "status=progress"], output => '>&'.fileno($fh)); return; } } @@ -1684,6 +1697,9 @@ sub volume_export_formats { or return; my ($size, $format) = file_size_info($file); + my ($vtype) = $class->parse_volname($volname); + + return ('backup+size') if $vtype eq 'backup'; return ('raw+size') if !defined($format); if ($with_snapshots) { @@ -1701,7 +1717,7 @@ sub volume_import { my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_; die "volume import format '$format' not available for $class\n" - if $format !~ /^(raw|tar|qcow2|vmdk)\+size$/; + if $format !~ /^(raw|tar|qcow2|vmdk|backup)\+size$/; my $data_format = $1; die "format $format cannot be imported without snapshots\n" @@ -1758,7 +1774,19 @@ sub volume_import { warn $@ if $@; die $err; } - } elsif (grep { $vtype eq $_ } qw(import iso snippets vztmpl)) { + } elsif (grep { $vtype eq $_ } qw(import iso snippets vztmpl backup)) { + my $protected; + my $notes; + + if ($vtype eq 'backup') { + sysread($fh, $protected, 1); + $protected = unpack('C', $protected); + sysread($fh, my $notes_len, 8); + $notes_len = unpack('Q<', $notes_len); + sysread($fh, $notes, $notes_len); + $notes = Encode::decode('utf8', $notes) // ""; + } + eval { run_command(['dd', "of=$file", 'bs=64k'], input => '<&'.fileno($fh)); }; @@ -1769,6 +1797,11 @@ sub volume_import { } die $err; } + + if ($vtype eq 'backup') { + $class->update_volume_attribute($scfg, $storeid, $volname, 'protected', $protected); + $class->update_volume_attribute($scfg, $storeid, $volname, 'notes', $notes); + } } else { die "importing volume of type '$vtype' not implemented\n"; } @@ -1779,8 +1812,9 @@ sub volume_import { sub volume_import_formats { my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_; if ($scfg->{path} && !defined($base_snapshot)) { - my $format = ($class->parse_volname($volname))[6]; + my ($vtype, $format) = ($class->parse_volname($volname))[0, 6]; + return ('backup+size') if $vtype eq 'backup'; return ('raw+size') if !defined($format); if ($with_snapshots) { -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel