]> git.proxmox.com Git - pve-storage.git/blame - PVE/API2/Storage/Status.pm
file size info: return early if we cannot parse json
[pve-storage.git] / PVE / API2 / Storage / Status.pm
CommitLineData
b6cf0a66
DM
1package PVE::API2::Storage::Status;
2
3use strict;
4use warnings;
5
7814e05f
DM
6use File::Path;
7use File::Basename;
8use PVE::Tools;
9use PVE::INotify;
83d7192f 10use PVE::Cluster;
0ce8cadd 11use PVE::RRD;
b6cf0a66
DM
12use PVE::Storage;
13use PVE::API2::Storage::Content;
25a95836 14use PVE::API2::Storage::PruneBackups;
f1a3ce3b 15use PVE::API2::Storage::FileRestore;
b6cf0a66
DM
16use PVE::RESTHandler;
17use PVE::RPCEnvironment;
18use PVE::JSONSchema qw(get_standard_option);
19use PVE::Exception qw(raise_param_exc);
20
21use base qw(PVE::RESTHandler);
22
25a95836
FE
23__PACKAGE__->register_method ({
24 subclass => "PVE::API2::Storage::PruneBackups",
25 path => '{storage}/prunebackups',
26});
27
b6cf0a66 28__PACKAGE__->register_method ({
7dd31e68 29 subclass => "PVE::API2::Storage::Content",
b6cf0a66 30 # set fragment delimiter (no subdirs) - we need that, because volume
7dd31e68
FE
31 # IDs may contain a slash '/'
32 fragmentDelimiter => '',
b6cf0a66
DM
33 path => '{storage}/content',
34});
35
f1a3ce3b
SR
36__PACKAGE__->register_method ({
37 subclass => "PVE::API2::Storage::FileRestore",
38 path => '{storage}/file-restore',
39});
40
b6cf0a66 41__PACKAGE__->register_method ({
7dd31e68 42 name => 'index',
b6cf0a66
DM
43 path => '',
44 method => 'GET',
45 description => "Get status for all datastores.",
7dd31e68 46 permissions => {
5f642f73
DM
47 description => "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
48 user => 'all',
49 },
b6cf0a66
DM
50 protected => 1,
51 proxyto => 'node',
52 parameters => {
53 additionalProperties => 0,
54 properties => {
55 node => get_standard_option('pve-node'),
f3bd890d
DM
56 storage => get_standard_option('pve-storage-id', {
57 description => "Only list status for specified storage",
58 optional => 1,
59 completion => \&PVE::Storage::complete_storage_enabled,
60 }),
7dd31e68 61 content => {
b6cf0a66 62 description => "Only list stores which support this content type.",
583c2802 63 type => 'string', format => 'pve-storage-content-list',
b6cf0a66 64 optional => 1,
98437f4c 65 completion => \&PVE::Storage::complete_content_type,
b6cf0a66 66 },
283608f3
DM
67 enabled => {
68 description => "Only list stores which are enabled (not disabled in config).",
69 type => 'boolean',
12c2fe32
DM
70 optional => 1,
71 default => 0,
283608f3
DM
72 },
73 target => get_standard_option('pve-node', {
74 description => "If target is different to 'node', we only lists shared storages which " .
75 "content is accessible on this 'node' and the specified 'target' node.",
76 optional => 1,
f3bd890d 77 completion => \&PVE::Cluster::get_nodelist,
283608f3 78 }),
d347322a 79 'format' => {
856c54bd
DC
80 description => "Include information about formats",
81 type => 'boolean',
82 optional => 1,
83 default => 0,
84 },
b6cf0a66
DM
85 },
86 },
87 returns => {
88 type => 'array',
89 items => {
90 type => "object",
d347322a
DM
91 properties => {
92 storage => get_standard_option('pve-storage-id'),
93 type => {
94 description => "Storage type.",
95 type => 'string',
96 },
97 content => {
98 description => "Allowed storage content types.",
99 type => 'string', format => 'pve-storage-content-list',
100 },
101 enabled => {
102 description => "Set when storage is enabled (not disabled).",
103 type => 'boolean',
104 optional => 1,
105 },
106 active => {
107 description => "Set when storage is accessible.",
108 type => 'boolean',
109 optional => 1,
110 },
111 shared => {
112 description => "Shared flag from storage configuration.",
113 type => 'boolean',
114 optional => 1,
115 },
116 total => {
117 description => "Total storage space in bytes.",
118 type => 'integer',
119 renderer => 'bytes',
120 optional => 1,
121 },
122 used => {
123 description => "Used storage space in bytes.",
124 type => 'integer',
125 renderer => 'bytes',
126 optional => 1,
127 },
128 avail => {
129 description => "Available storage space in bytes.",
130 type => 'integer',
131 renderer => 'bytes',
132 optional => 1,
133 },
134 used_fraction => {
135 description => "Used fraction (used/total).",
136 type => 'number',
137 renderer => 'fraction_as_percentage',
138 optional => 1,
139 },
140 },
b6cf0a66
DM
141 },
142 links => [ { rel => 'child', href => "{storage}" } ],
143 },
144 code => sub {
145 my ($param) = @_;
146
5f642f73
DM
147 my $rpcenv = PVE::RPCEnvironment::get();
148 my $authuser = $rpcenv->get_user();
149
283608f3
DM
150 my $localnode = PVE::INotify::nodename();
151
152 my $target = $param->{target};
153
154 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
f6c03d36 155
83d7192f 156 my $cfg = PVE::Storage::config();
b6cf0a66 157
856c54bd 158 my $info = PVE::Storage::storage_info($cfg, $param->{content}, $param->{format});
b6cf0a66 159
5f642f73
DM
160 raise_param_exc({ storage => "No such storage." })
161 if $param->{storage} && !defined($info->{$param->{storage}});
f6c03d36 162
5f642f73
DM
163 my $res = {};
164 my @sids = PVE::Storage::storage_ids($cfg);
165 foreach my $storeid (@sids) {
d347322a
DM
166 my $data = $info->{$storeid};
167 next if !$data;
5f642f73
DM
168 my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ];
169 next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
170 next if $param->{storage} && $param->{storage} ne $storeid;
283608f3
DM
171
172 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
173
174 next if $param->{enabled} && $scfg->{disable};
f6c03d36 175
283608f3
DM
176 if ($target) {
177 # check if storage content is accessible on local node and specified target node
178 # we use this on the Clone GUI
179
180 next if !$scfg->{shared};
181 next if !PVE::Storage::storage_check_node($cfg, $storeid, undef, 1);
182 next if !PVE::Storage::storage_check_node($cfg, $storeid, $target, 1);
183 }
184
d347322a
DM
185 if ($data->{total}) {
186 $data->{used_fraction} = ($data->{used} // 0) / $data->{total};
187 }
188
189 $res->{$storeid} = $data;
b6cf0a66 190 }
5f642f73
DM
191
192 return PVE::RESTHandler::hash_to_array($res, 'storage');
b6cf0a66
DM
193 }});
194
195__PACKAGE__->register_method ({
196 name => 'diridx',
7dd31e68 197 path => '{storage}',
b6cf0a66
DM
198 method => 'GET',
199 description => "",
7dd31e68 200 permissions => {
5f642f73
DM
201 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
202 },
b6cf0a66
DM
203 parameters => {
204 additionalProperties => 0,
205 properties => {
206 node => get_standard_option('pve-node'),
207 storage => get_standard_option('pve-storage-id'),
208 },
209 },
210 returns => {
211 type => 'array',
212 items => {
213 type => "object",
214 properties => {
215 subdir => { type => 'string' },
216 },
217 },
218 links => [ { rel => 'child', href => "{subdir}" } ],
219 },
220 code => sub {
221 my ($param) = @_;
222
223 my $res = [
b6cf0a66 224 { subdir => 'content' },
ead6be93
TL
225 { subdir => 'file-restore' },
226 { subdir => 'prunebackups' },
b6cf0a66
DM
227 { subdir => 'rrd' },
228 { subdir => 'rrddata' },
ead6be93
TL
229 { subdir => 'status' },
230 { subdir => 'upload' },
7dd31e68
FE
231 ];
232
b6cf0a66
DM
233 return $res;
234 }});
235
236__PACKAGE__->register_method ({
237 name => 'read_status',
7dd31e68 238 path => '{storage}/status',
b6cf0a66
DM
239 method => 'GET',
240 description => "Read storage status.",
7dd31e68 241 permissions => {
5f642f73
DM
242 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
243 },
b6cf0a66
DM
244 protected => 1,
245 proxyto => 'node',
246 parameters => {
247 additionalProperties => 0,
248 properties => {
249 node => get_standard_option('pve-node'),
250 storage => get_standard_option('pve-storage-id'),
251 },
252 },
253 returns => {
254 type => "object",
255 properties => {},
256 },
257 code => sub {
258 my ($param) = @_;
259
83d7192f 260 my $cfg = PVE::Storage::config();
b6cf0a66
DM
261
262 my $info = PVE::Storage::storage_info($cfg, $param->{content});
263
264 my $data = $info->{$param->{storage}};
265
266 raise_param_exc({ storage => "No such storage." })
267 if !defined($data);
7dd31e68 268
b6cf0a66
DM
269 return $data;
270 }});
271
272__PACKAGE__->register_method ({
273 name => 'rrd',
7dd31e68 274 path => '{storage}/rrd',
b6cf0a66
DM
275 method => 'GET',
276 description => "Read storage RRD statistics (returns PNG).",
7dd31e68 277 permissions => {
5f642f73
DM
278 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
279 },
b6cf0a66
DM
280 protected => 1,
281 proxyto => 'node',
282 parameters => {
283 additionalProperties => 0,
284 properties => {
285 node => get_standard_option('pve-node'),
286 storage => get_standard_option('pve-storage-id'),
287 timeframe => {
288 description => "Specify the time frame you are interested in.",
289 type => 'string',
290 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
291 },
292 ds => {
293 description => "The list of datasources you want to display.",
294 type => 'string', format => 'pve-configid-list',
295 },
296 cf => {
297 description => "The RRD consolidation function",
298 type => 'string',
299 enum => [ 'AVERAGE', 'MAX' ],
300 optional => 1,
301 },
302 },
303 },
304 returns => {
305 type => "object",
306 properties => {
307 filename => { type => 'string' },
308 },
309 },
310 code => sub {
311 my ($param) = @_;
312
0ce8cadd 313 return PVE::RRD::create_rrd_graph(
7dd31e68 314 "pve2-storage/$param->{node}/$param->{storage}",
b6cf0a66 315 $param->{timeframe}, $param->{ds}, $param->{cf});
b6cf0a66
DM
316 }});
317
318__PACKAGE__->register_method ({
319 name => 'rrddata',
7dd31e68 320 path => '{storage}/rrddata',
b6cf0a66
DM
321 method => 'GET',
322 description => "Read storage RRD statistics.",
7dd31e68 323 permissions => {
5f642f73
DM
324 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
325 },
b6cf0a66
DM
326 protected => 1,
327 proxyto => 'node',
328 parameters => {
329 additionalProperties => 0,
330 properties => {
331 node => get_standard_option('pve-node'),
332 storage => get_standard_option('pve-storage-id'),
333 timeframe => {
334 description => "Specify the time frame you are interested in.",
335 type => 'string',
336 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
337 },
338 cf => {
339 description => "The RRD consolidation function",
340 type => 'string',
341 enum => [ 'AVERAGE', 'MAX' ],
342 optional => 1,
343 },
344 },
345 },
346 returns => {
347 type => "array",
348 items => {
349 type => "object",
350 properties => {},
351 },
352 },
353 code => sub {
354 my ($param) = @_;
355
0ce8cadd 356 return PVE::RRD::create_rrd_data(
7dd31e68
FE
357 "pve2-storage/$param->{node}/$param->{storage}",
358 $param->{timeframe}, $param->{cf});
b6cf0a66 359 }});
7814e05f 360
7dd31e68 361# makes no sense for big images and backup files (because it
1f6610f3 362# create a copy of the file).
7814e05f
DM
363__PACKAGE__->register_method ({
364 name => 'upload',
7dd31e68 365 path => '{storage}/upload',
7814e05f 366 method => 'POST',
1f6610f3 367 description => "Upload templates and ISO images.",
7dd31e68 368 permissions => {
1f6610f3 369 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
5f642f73 370 },
7814e05f
DM
371 protected => 1,
372 parameters => {
373 additionalProperties => 0,
374 properties => {
375 node => get_standard_option('pve-node'),
376 storage => get_standard_option('pve-storage-id'),
7dd31e68 377 content => {
7814e05f
DM
378 description => "Content type.",
379 type => 'string', format => 'pve-storage-content',
380 },
7dd31e68 381 filename => {
7814e05f
DM
382 description => "The name of the file to create.",
383 type => 'string',
384 },
7dd31e68 385 tmpfilename => {
5c0720d6 386 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.",
7814e05f
DM
387 type => 'string',
388 optional => 1,
389 },
390 },
391 },
392 returns => { type => "string" },
393 code => sub {
394 my ($param) = @_;
395
396 my $rpcenv = PVE::RPCEnvironment::get();
397
398 my $user = $rpcenv->get_user();
399
83d7192f 400 my $cfg = PVE::Storage::config();
7814e05f
DM
401
402 my $node = $param->{node};
403 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
404
d68c7bca 405 die "can't upload to storage type '$scfg->{type}'\n"
ee8c176d 406 if !defined($scfg->{path});
7814e05f
DM
407
408 my $content = $param->{content};
409
410 my $tmpfilename = $param->{tmpfilename};
411 die "missing temporary file name\n" if !$tmpfilename;
412
413 my $size = -s $tmpfilename;
481f6177 414 die "temporary file '$tmpfilename' does not exist\n" if !defined($size);
7814e05f
DM
415
416 my $filename = $param->{filename};
417
418 chomp $filename;
419 $filename =~ s/^.*[\/\\]//;
38e1eb3d 420 $filename =~ s/[^-a-zA-Z0-9_.]/_/g;
7814e05f
DM
421
422 my $path;
423
424 if ($content eq 'iso') {
4c693491
SR
425 if ($filename !~ m![^/]+$PVE::Storage::iso_extension_re$!) {
426 raise_param_exc({ filename => "missing '.iso' or '.img' extension" });
7814e05f
DM
427 }
428 $path = PVE::Storage::get_iso_dir($cfg, $param->{storage});
429 } elsif ($content eq 'vztmpl') {
030f6e1a
EK
430 if ($filename !~ m![^/]+\.tar\.[gx]z$!) {
431 raise_param_exc({ filename => "missing '.tar.gz' or '.tar.xz' extension" });
7814e05f 432 }
4ea5bca4 433 $path = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
7814e05f 434 } else {
1f6610f3 435 raise_param_exc({ content => "upload content type '$content' not allowed" });
7814e05f
DM
436 }
437
d68c7bca 438 die "storage '$param->{storage}' does not support '$content' content\n"
7814e05f
DM
439 if !$scfg->{content}->{$content};
440
441 my $dest = "$path/$filename";
442 my $dirname = dirname($dest);
443
5c0720d6
SR
444 # best effort to match apl_download behaviour
445 chmod 0644, $tmpfilename;
446
243f413a 447 # we simply overwrite the destination file if it already exists
7814e05f
DM
448
449 my $cmd;
450 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
451 my $remip = PVE::Cluster::remote_node_ip($node);
452
45c2ee35 453 my @ssh_options = ('-o', 'BatchMode=yes');
7814e05f 454
53ec90e2 455 my @remcmd = ('/usr/bin/ssh', @ssh_options, $remip, '--');
7814e05f 456
7dd31e68 457 eval {
7814e05f 458 # activate remote storage
7dd31e68
FE
459 PVE::Tools::run_command([@remcmd, '/usr/sbin/pvesm', 'status',
460 '--storage', $param->{storage}]);
7814e05f 461 };
4af77132 462 die "can't activate storage '$param->{storage}' on node '$node': $@\n" if $@;
7814e05f 463
53ec90e2 464 PVE::Tools::run_command([@remcmd, '/bin/mkdir', '-p', '--', PVE::Tools::shell_quote($dirname)],
7814e05f
DM
465 errmsg => "mkdir failed");
466
5c0720d6 467 $cmd = ['/usr/bin/scp', @ssh_options, '-p', '--', $tmpfilename, "[$remip]:" . PVE::Tools::shell_quote($dest)];
7814e05f
DM
468 } else {
469 PVE::Storage::activate_storage($cfg, $param->{storage});
4ea5bca4 470 File::Path::make_path($dirname);
53ec90e2 471 $cmd = ['cp', '--', $tmpfilename, $dest];
7814e05f
DM
472 }
473
7dd31e68 474 my $worker = sub {
7814e05f 475 my $upid = shift;
7dd31e68 476
7814e05f
DM
477 print "starting file import from: $tmpfilename\n";
478 print "target node: $node\n";
479 print "target file: $dest\n";
480 print "file size is: $size\n";
481 print "command: " . join(' ', @$cmd) . "\n";
482
483 eval { PVE::Tools::run_command($cmd, errmsg => 'import failed'); };
484 if (my $err = $@) {
485 unlink $dest;
486 die $err;
487 }
488 print "finished file import successfully\n";
489 };
490
d6c9dc34
DM
491 my $upid = $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
492
493 # apache removes the temporary file on return, so we need
494 # to wait here to make sure the worker process starts and
495 # opens the file before it gets removed.
496 sleep(1);
497
498 return $upid;
7814e05f 499 }});
7dd31e68 500
b6cf0a66 5011;