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