X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPI2%2FStorage%2FStatus.pm;h=8a36aefb8c5d22d0c4b80eb0f805d78ce99f0a92;hb=ead6be934d37989105034d04e92c0229270389eb;hp=ddd2fa49b8da8451a9a4907e45fdf10ee76d1e34;hpb=83d7192ff979cc25135191e3adc97dd6a679508c;p=pve-storage.git diff --git a/PVE/API2/Storage/Status.pm b/PVE/API2/Storage/Status.pm index ddd2fa4..8a36aef 100644 --- a/PVE/API2/Storage/Status.pm +++ b/PVE/API2/Storage/Status.pm @@ -8,8 +8,11 @@ use File::Basename; use PVE::Tools; use PVE::INotify; use PVE::Cluster; +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); @@ -18,19 +21,29 @@ use PVE::Exception qw(raise_param_exc); use base qw(PVE::RESTHandler); __PACKAGE__->register_method ({ - subclass => "PVE::API2::Storage::Content", + subclass => "PVE::API2::Storage::PruneBackups", + path => '{storage}/prunebackups', +}); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Storage::Content", # set fragment delimiter (no subdirs) - we need that, because volume - # IDs may contain a slash '/' - fragmentDelimiter => '', + # IDs may contain a slash '/' + fragmentDelimiter => '', path => '{storage}/content', }); __PACKAGE__->register_method ({ - name => 'index', + subclass => "PVE::API2::Storage::FileRestore", + path => '{storage}/file-restore', +}); + +__PACKAGE__->register_method ({ + name => 'index', path => '', method => 'GET', description => "Get status for all datastores.", - permissions => { + permissions => { description => "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/'", user => 'all', }, @@ -45,7 +58,7 @@ __PACKAGE__->register_method ({ optional => 1, completion => \&PVE::Storage::complete_storage_enabled, }), - content => { + content => { description => "Only list stores which support this content type.", type => 'string', format => 'pve-storage-content-list', optional => 1, @@ -63,13 +76,68 @@ __PACKAGE__->register_method ({ optional => 1, completion => \&PVE::Cluster::get_nodelist, }), + 'format' => { + description => "Include information about formats", + type => 'boolean', + optional => 1, + default => 0, + }, }, }, returns => { type => 'array', items => { type => "object", - properties => { storage => { type => 'string' } }, + properties => { + storage => get_standard_option('pve-storage-id'), + type => { + description => "Storage type.", + type => 'string', + }, + content => { + description => "Allowed storage content types.", + type => 'string', format => 'pve-storage-content-list', + }, + enabled => { + description => "Set when storage is enabled (not disabled).", + type => 'boolean', + optional => 1, + }, + active => { + description => "Set when storage is accessible.", + type => 'boolean', + optional => 1, + }, + shared => { + description => "Shared flag from storage configuration.", + type => 'boolean', + optional => 1, + }, + total => { + description => "Total storage space in bytes.", + type => 'integer', + renderer => 'bytes', + optional => 1, + }, + used => { + description => "Used storage space in bytes.", + type => 'integer', + renderer => 'bytes', + optional => 1, + }, + avail => { + description => "Available storage space in bytes.", + type => 'integer', + renderer => 'bytes', + optional => 1, + }, + used_fraction => { + description => "Used fraction (used/total).", + type => 'number', + renderer => 'fraction_as_percentage', + optional => 1, + }, + }, }, links => [ { rel => 'child', href => "{storage}" } ], }, @@ -84,18 +152,19 @@ __PACKAGE__->register_method ({ my $target = $param->{target}; undef $target if $target && ($target eq $localnode || $target eq 'localhost'); - + my $cfg = PVE::Storage::config(); - my $info = PVE::Storage::storage_info($cfg, $param->{content}); + my $info = PVE::Storage::storage_info($cfg, $param->{content}, $param->{format}); raise_param_exc({ storage => "No such storage." }) if $param->{storage} && !defined($info->{$param->{storage}}); - + my $res = {}; my @sids = PVE::Storage::storage_ids($cfg); foreach my $storeid (@sids) { - next if !$info->{$storeid}; + my $data = $info->{$storeid}; + next if !$data; my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ]; next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1); next if $param->{storage} && $param->{storage} ne $storeid; @@ -103,7 +172,7 @@ __PACKAGE__->register_method ({ my $scfg = PVE::Storage::storage_config($cfg, $storeid); next if $param->{enabled} && $scfg->{disable}; - + if ($target) { # check if storage content is accessible on local node and specified target node # we use this on the Clone GUI @@ -113,7 +182,11 @@ __PACKAGE__->register_method ({ next if !PVE::Storage::storage_check_node($cfg, $storeid, $target, 1); } - $res->{$storeid} = $info->{$storeid}; + if ($data->{total}) { + $data->{used_fraction} = ($data->{used} // 0) / $data->{total}; + } + + $res->{$storeid} = $data; } return PVE::RESTHandler::hash_to_array($res, 'storage'); @@ -121,10 +194,10 @@ __PACKAGE__->register_method ({ __PACKAGE__->register_method ({ name => 'diridx', - path => '{storage}', + path => '{storage}', method => 'GET', description => "", - permissions => { + permissions => { check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], }, parameters => { @@ -148,22 +221,24 @@ __PACKAGE__->register_method ({ my ($param) = @_; my $res = [ - { subdir => 'status' }, { subdir => 'content' }, - { subdir => 'upload' }, + { subdir => 'file-restore' }, + { subdir => 'prunebackups' }, { subdir => 'rrd' }, { subdir => 'rrddata' }, - ]; - + { subdir => 'status' }, + { subdir => 'upload' }, + ]; + return $res; }}); __PACKAGE__->register_method ({ name => 'read_status', - path => '{storage}/status', + path => '{storage}/status', method => 'GET', description => "Read storage status.", - permissions => { + permissions => { check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], }, protected => 1, @@ -190,16 +265,16 @@ __PACKAGE__->register_method ({ raise_param_exc({ storage => "No such storage." }) if !defined($data); - + return $data; }}); __PACKAGE__->register_method ({ name => 'rrd', - path => '{storage}/rrd', + path => '{storage}/rrd', method => 'GET', description => "Read storage RRD statistics (returns PNG).", - permissions => { + permissions => { check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], }, protected => 1, @@ -235,18 +310,17 @@ __PACKAGE__->register_method ({ code => sub { my ($param) = @_; - return PVE::Cluster::create_rrd_graph( - "pve2-storage/$param->{node}/$param->{storage}", + return PVE::RRD::create_rrd_graph( + "pve2-storage/$param->{node}/$param->{storage}", $param->{timeframe}, $param->{ds}, $param->{cf}); - }}); __PACKAGE__->register_method ({ name => 'rrddata', - path => '{storage}/rrddata', + path => '{storage}/rrddata', method => 'GET', description => "Read storage RRD statistics.", - permissions => { + permissions => { check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], }, protected => 1, @@ -279,19 +353,19 @@ __PACKAGE__->register_method ({ code => sub { my ($param) = @_; - return PVE::Cluster::create_rrd_data( - "pve2-storage/$param->{node}/$param->{storage}", - $param->{timeframe}, $param->{cf}); + return PVE::RRD::create_rrd_data( + "pve2-storage/$param->{node}/$param->{storage}", + $param->{timeframe}, $param->{cf}); }}); -# makes no sense for big images and backup files (because it +# makes no sense for big images and backup files (because it # create a copy of the file). __PACKAGE__->register_method ({ name => 'upload', - path => '{storage}/upload', + path => '{storage}/upload', method => 'POST', description => "Upload templates and ISO images.", - permissions => { + permissions => { check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], }, protected => 1, @@ -300,16 +374,16 @@ __PACKAGE__->register_method ({ properties => { node => get_standard_option('pve-node'), storage => get_standard_option('pve-storage-id'), - content => { + content => { description => "Content type.", type => 'string', format => 'pve-storage-content', }, - filename => { + filename => { description => "The name of the file to create.", type => 'string', }, - tmpfilename => { - description => "The source file name. This parameter is usually set by the REST handler. You can only overwrite it when connecting to the trustet port on localhost.", + tmpfilename => { + description => "The source file name. This parameter is usually set by the REST handler. You can only overwrite it when connecting to the trusted port on localhost.", type => 'string', optional => 1, }, @@ -328,8 +402,8 @@ __PACKAGE__->register_method ({ my $node = $param->{node}; my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node); - die "cant upload to storage type '$scfg->{type}'\n" - if !($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs' || $scfg->{type} eq 'glusterfs'); + die "can't upload to storage type '$scfg->{type}'\n" + if !defined($scfg->{path}); my $content = $param->{content}; @@ -337,37 +411,40 @@ __PACKAGE__->register_method ({ die "missing temporary file name\n" if !$tmpfilename; my $size = -s $tmpfilename; - die "temporary file '$tmpfilename' does not exists\n" if !defined($size); + die "temporary file '$tmpfilename' does not exist\n" if !defined($size); my $filename = $param->{filename}; chomp $filename; $filename =~ s/^.*[\/\\]//; - $filename =~ s/[;:,=\s\x80-\xff]/_/g; + $filename =~ s/[^-a-zA-Z0-9_.]/_/g; my $path; if ($content eq 'iso') { - if ($filename !~ m![^/]+\.[Ii][Ss][Oo]$!) { - raise_param_exc({ filename => "missing '.iso' extension" }); + if ($filename !~ m![^/]+$PVE::Storage::iso_extension_re$!) { + raise_param_exc({ filename => "missing '.iso' or '.img' extension" }); } $path = PVE::Storage::get_iso_dir($cfg, $param->{storage}); } elsif ($content eq 'vztmpl') { - if ($filename !~ m![^/]+\.tar\.gz$!) { - raise_param_exc({ filename => "missing '.tar.gz' extension" }); + if ($filename !~ m![^/]+\.tar\.[gx]z$!) { + raise_param_exc({ filename => "missing '.tar.gz' or '.tar.xz' extension" }); } $path = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage}); } else { raise_param_exc({ content => "upload content type '$content' not allowed" }); } - die "storage '$param->{storage}' does not support '$content' content\n" + die "storage '$param->{storage}' does not support '$content' content\n" if !$scfg->{content}->{$content}; my $dest = "$path/$filename"; my $dirname = dirname($dest); - # we simply overwrite when destination when file already exists + # best effort to match apl_download behaviour + chmod 0644, $tmpfilename; + + # we simply overwrite the destination file if it already exists my $cmd; if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) { @@ -377,26 +454,26 @@ __PACKAGE__->register_method ({ my @remcmd = ('/usr/bin/ssh', @ssh_options, $remip, '--'); - eval { + eval { # activate remote storage - PVE::Tools::run_command([@remcmd, '/usr/sbin/pvesm', 'status', - '--storage', $param->{storage}]); + PVE::Tools::run_command([@remcmd, '/usr/sbin/pvesm', 'status', + '--storage', $param->{storage}]); }; - die "can't activate storage '$param->{storage}' on node '$node'\n" if $@; + die "can't activate storage '$param->{storage}' on node '$node': $@\n" if $@; PVE::Tools::run_command([@remcmd, '/bin/mkdir', '-p', '--', PVE::Tools::shell_quote($dirname)], errmsg => "mkdir failed"); - $cmd = ['/usr/bin/scp', @ssh_options, '--', $tmpfilename, "[$remip]:" . PVE::Tools::shell_quote($dest)]; + $cmd = ['/usr/bin/scp', @ssh_options, '-p', '--', $tmpfilename, "[$remip]:" . PVE::Tools::shell_quote($dest)]; } else { PVE::Storage::activate_storage($cfg, $param->{storage}); File::Path::make_path($dirname); $cmd = ['cp', '--', $tmpfilename, $dest]; } - my $worker = sub { + my $worker = sub { my $upid = shift; - + print "starting file import from: $tmpfilename\n"; print "target node: $node\n"; print "target file: $dest\n"; @@ -420,5 +497,5 @@ __PACKAGE__->register_method ({ return $upid; }}); - + 1;