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