[pve-devel] [PATCH storage 09/10] add FileRestore API for PBS

Stefan Reiter s.reiter at proxmox.com
Wed Apr 21 15:38:14 CEST 2021


On 21/04/2021 15:26, Fabian Grünbichler wrote:
> On April 21, 2021 1:15 pm, Stefan Reiter wrote:
>> Includes list and restore calls.
>>
>> Requires VM.Backup and Datastore.Audit permissions, for the accessed
>> VM/CT and containing datastore respectively.
> 
> we require Datastore.AllocateSpace + VM.Backup for the owning guest,
> or Datastore.Allocate for the storage altogether for accessing backup
> archives otherwise, maybe this should have the same logic?
> 

sounds reasonable

>>
>> Signed-off-by: Stefan Reiter <s.reiter at proxmox.com>
>> ---
>>
>> Requires updated pve-common, pve-http-server.
>>
>>   PVE/API2/Storage/FileRestore.pm | 163 ++++++++++++++++++++++++++++++++
>>   PVE/API2/Storage/Makefile       |   2 +-
>>   PVE/API2/Storage/Status.pm      |   6 ++
>>   3 files changed, 170 insertions(+), 1 deletion(-)
>>   create mode 100644 PVE/API2/Storage/FileRestore.pm
>>
>> diff --git a/PVE/API2/Storage/FileRestore.pm b/PVE/API2/Storage/FileRestore.pm
>> new file mode 100644
>> index 0000000..a0b5e88
>> --- /dev/null
>> +++ b/PVE/API2/Storage/FileRestore.pm
>> @@ -0,0 +1,163 @@
>> +package PVE::API2::Storage::FileRestore;
>> +
>> +use strict;
>> +use warnings;
>> +
>> +use PVE::JSONSchema qw(get_standard_option);
>> +use PVE::PBSClient;
>> +use PVE::Storage;
>> +use PVE::Tools qw(extract_param);
>> +
>> +use PVE::RESTHandler;
>> +use base qw(PVE::RESTHandler);
>> +
>> +__PACKAGE__->register_method ({
>> +    name => 'list',
>> +    path => 'list',
>> +    method => 'GET',
>> +    proxyto => 'node',
>> +    permissions => {
>> +	description => "Requires 'VM.Backup' permission on the VM being accessed, and " .
>> +	    "'Datastore.Audit' on the datastore being restored from.",
>> +	user => 'all', # checked explicitly
>> +    },
>> +    description => "List files and directories for single file restore under the given path.",
>> +    protected => 1,
>> +    parameters => {
>> +	additionalProperties => 0,
>> +	properties => {
>> +	    node => get_standard_option('pve-node'),
>> +	    storage => get_standard_option('pve-storage-id'),
>> +	    snapshot => {
>> +		description => "Backup snapshot identifier.",
>> +		type => 'string',
>> +	    },
> 
> why not use a volume id here (instead of storage + snapshot ID), and
> then check inside whether it's a pbs backup? would allow easily
> extending this to VMA backups as well later on, completion by our usual
> volume ID helpers/selectors, ..
> 

I did it this way mostly because we get the 'storage' parameter here 
anyway - it's in the URL path, since this lives under 
'/nodes/{node}/storage/{storage}'. Thus the only thing missing was the 
snapshot.

Is there a format for the "latter part of a volume-id"? If there is, 
this would also just be a simple change later on, as it'd just replace 
the 'snapshot' param.

>> +	    filepath => {
>> +		description => 'base64-path to the directory or file being listed, or "/".',
>> +		type => 'string',
>> +	    },
>> +	},
>> +    },
>> +    returns => {
>> +	type => 'array',
>> +	items => {
>> +	    type => "object",
>> +	    properties => {
>> +		filepath => {
>> +		    description => "base64 path of the current entry",
>> +		    type => 'string',
>> +		},
>> +		type => {
>> +		    description => "Entry type.",
>> +		    type => 'string',
>> +		},
>> +		text => {
>> +		    description => "Entry display text.",
>> +		    type => 'string',
>> +		},
>> +		leaf => {
>> +		    description => "If this entry is a leaf in the directory graph.",
>> +		    type => 'any', # JSON::PP::Boolean gets passed through
>> +		},
>> +		size => {
>> +		    description => "Entry file size.",
>> +		    type => 'integer',
>> +		    optional => 1,
>> +		},
>> +		mtime => {
>> +		    description => "Entry last-modified time (unix timestamp).",
>> +		    type => 'integer',
>> +		    optional => 1,
>> +		},
>> +	    },
>> +	},
>> +    },
>> +    code => sub {
>> +	my ($param) = @_;
>> +
>> +	my $rpcenv = PVE::RPCEnvironment::get();
>> +	my $user = $rpcenv->get_user();
>> +
>> +	my $path = extract_param($param, 'filepath') || "/";
>> +	my $base64 = $path ne "/";
>> +	my $snap = extract_param($param, 'snapshot');
>> +	my $storeid = extract_param($param, 'storage');
>> +	my $cfg = PVE::Storage::config();
>> +	my $scfg = PVE::Storage::storage_config($cfg, $storeid);
>> +
>> +	my $volid = "$storeid:backup/$snap";
>> +	my (undef, undef, $ownervm) = PVE::Storage::parse_volname($cfg, $volid);
>> +	$rpcenv->check($user, "/storage/$storeid", ['Datastore.Audit']);
>> +	$rpcenv->check($user, "/vms/$ownervm", ['VM.Backup']);
> 
> see comment above, this could then become
> 'PVE::Storage::check_volume_access(..)
>> +
>> +	my $client = PVE::PBSClient->new($scfg, $storeid);
>> +	my $ret = $client->file_restore_list($snap, $path, $base64);
>> +
>> +	return $ret;
>> +    }});
>> +
>> +__PACKAGE__->register_method ({
>> +    name => 'download',
>> +    path => 'download',
>> +    method => 'GET',
>> +    proxyto => 'node',
>> +    permissions => {
>> +	description => "Requires 'VM.Backup' permission on the VM being accessed, and " .
>> +	    "'Datastore.Audit' on the datastore being restored from.",
>> +	user => 'all', # checked explicitly
>> +    },
>> +    description => "Extract a file or directory (as zip archive) from a PBS backup.",
>> +    parameters => {
>> +	additionalProperties => 0,
>> +	properties => {
>> +	    node => get_standard_option('pve-node'),
>> +	    storage => get_standard_option('pve-storage-id'),
>> +	    snapshot => {
>> +		description => "Backup snapshot identifier.",
>> +		type => 'string',
>> +	    },
> 
> same here as above
> 
>> +	    filepath => {
>> +		description => 'base64-path to the directory or file being listed.',
>> +		type => 'string',
>> +	    },
>> +	},
>> +    },
>> +    returns => {
>> +	type => 'any', # download
>> +    },
>> +    protected => 1,
>> +    code => sub {
>> +	my ($param) = @_;
>> +
>> +	my $rpcenv = PVE::RPCEnvironment::get();
>> +	my $user = $rpcenv->get_user();
>> +
>> +	my $path = extract_param($param, 'filepath');
>> +	my $snap = extract_param($param, 'snapshot');
>> +	my $storeid = extract_param($param, 'storage');
>> +	my $cfg = PVE::Storage::config();
>> +	my $scfg = PVE::Storage::storage_config($cfg, $storeid);
>> +
>> +	my $volid = "$storeid:backup/$snap";
>> +	my (undef, undef, $ownervm) = PVE::Storage::parse_volname($cfg, $volid);
>> +	$rpcenv->check($user, "/storage/$storeid", ['Datastore.Audit']);
>> +	$rpcenv->check($user, "/vms/$ownervm", ['VM.Backup']);
> 
> and here as well
> 
>> +
>> +	my $client = PVE::PBSClient->new($scfg, $storeid);
>> +	my $fifo = $client->file_restore_extract_prepare();
>> +
>> +	$rpcenv->fork_worker('pbs-download', undef, $user, sub {
>> +	    $client->file_restore_extract($fifo, $snap, $path, 1);
>> +	});
>> +
>> +	my $ret = {
>> +	    download => {
>> +		path => $fifo,
>> +		stream => 1,
>> +		'content-type' => 'application/octet-stream',
>> +	    },
>> +	};
>> +	return $ret;
>> +    }});
>> +
>> +1;
>> diff --git a/PVE/API2/Storage/Makefile b/PVE/API2/Storage/Makefile
>> index 690b437..1705080 100644
>> --- a/PVE/API2/Storage/Makefile
>> +++ b/PVE/API2/Storage/Makefile
>> @@ -1,5 +1,5 @@
>>   
>> -SOURCES= Content.pm Status.pm Config.pm PruneBackups.pm Scan.pm
>> +SOURCES= Content.pm Status.pm Config.pm PruneBackups.pm Scan.pm FileRestore.pm
>>   
>>   .PHONY: install
>>   install:
>> diff --git a/PVE/API2/Storage/Status.pm b/PVE/API2/Storage/Status.pm
>> index d12643f..897b4a7 100644
>> --- a/PVE/API2/Storage/Status.pm
>> +++ b/PVE/API2/Storage/Status.pm
>> @@ -12,6 +12,7 @@ use PVE::RRD;
>>   use PVE::Storage;
>>   use PVE::API2::Storage::Content;
>>   use PVE::API2::Storage::PruneBackups;
>> +use PVE::API2::Storage::FileRestore;
>>   use PVE::RESTHandler;
>>   use PVE::RPCEnvironment;
>>   use PVE::JSONSchema qw(get_standard_option);
>> @@ -32,6 +33,11 @@ __PACKAGE__->register_method ({
>>       path => '{storage}/content',
>>   });
>>   
>> +__PACKAGE__->register_method ({
>> +   subclass => "PVE::API2::Storage::FileRestore",
>> +   path => '{storage}/file-restore',
>> +});
>> +
>>   __PACKAGE__->register_method ({
>>       name => 'index',
>>       path => '',
>> -- 
>> 2.20.1
>>
>>
>>
>> _______________________________________________
>> pve-devel mailing list
>> pve-devel at lists.proxmox.com
>> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
>>
>>
>>
> 
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel at lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
> 
> 




More information about the pve-devel mailing list