1 package PVE
::API2
::Storage
::Status
;
10 use PVE
::Exception
qw(raise_param_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
14 use PVE
::RPCEnvironment
;
18 use PVE
::API2
::Storage
::Content
;
19 use PVE
::API2
::Storage
::FileRestore
;
20 use PVE
::API2
::Storage
::PruneBackups
;
23 use base
qw(PVE::RESTHandler);
25 __PACKAGE__-
>register_method ({
26 subclass
=> "PVE::API2::Storage::PruneBackups",
27 path
=> '{storage}/prunebackups',
30 __PACKAGE__-
>register_method ({
31 subclass
=> "PVE::API2::Storage::Content",
32 # set fragment delimiter (no subdirs) - we need that, because volume
33 # IDs may contain a slash '/'
34 fragmentDelimiter
=> '',
35 path
=> '{storage}/content',
38 __PACKAGE__-
>register_method ({
39 subclass
=> "PVE::API2::Storage::FileRestore",
40 path
=> '{storage}/file-restore',
43 __PACKAGE__-
>register_method ({
47 description
=> "Get status for all datastores.",
49 description
=> "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
55 additionalProperties
=> 0,
57 node
=> get_standard_option
('pve-node'),
58 storage
=> get_standard_option
('pve-storage-id', {
59 description
=> "Only list status for specified storage",
61 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
64 description
=> "Only list stores which support this content type.",
65 type
=> 'string', format
=> 'pve-storage-content-list',
67 completion
=> \
&PVE
::Storage
::complete_content_type
,
70 description
=> "Only list stores which are enabled (not disabled in config).",
75 target
=> get_standard_option
('pve-node', {
76 description
=> "If target is different to 'node', we only lists shared storages which " .
77 "content is accessible on this 'node' and the specified 'target' node.",
79 completion
=> \
&PVE
::Cluster
::get_nodelist
,
82 description
=> "Include information about formats",
94 storage
=> get_standard_option
('pve-storage-id'),
96 description
=> "Storage type.",
100 description
=> "Allowed storage content types.",
101 type
=> 'string', format
=> 'pve-storage-content-list',
104 description
=> "Set when storage is enabled (not disabled).",
109 description
=> "Set when storage is accessible.",
114 description
=> "Shared flag from storage configuration.",
119 description
=> "Total storage space in bytes.",
125 description
=> "Used storage space in bytes.",
131 description
=> "Available storage space in bytes.",
137 description
=> "Used fraction (used/total).",
139 renderer
=> 'fraction_as_percentage',
144 links
=> [ { rel
=> 'child', href
=> "{storage}" } ],
149 my $rpcenv = PVE
::RPCEnvironment
::get
();
150 my $authuser = $rpcenv->get_user();
152 my $localnode = PVE
::INotify
::nodename
();
154 my $target = $param->{target
};
156 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
158 my $cfg = PVE
::Storage
::config
();
160 my $info = PVE
::Storage
::storage_info
($cfg, $param->{content
}, $param->{format
});
162 raise_param_exc
({ storage
=> "No such storage." })
163 if $param->{storage
} && !defined($info->{$param->{storage
}});
166 my @sids = PVE
::Storage
::storage_ids
($cfg);
167 foreach my $storeid (@sids) {
168 my $data = $info->{$storeid};
170 my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ];
171 next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
172 next if $param->{storage
} && $param->{storage
} ne $storeid;
174 my $scfg = PVE
::Storage
::storage_config
($cfg, $storeid);
176 next if $param->{enabled
} && $scfg->{disable
};
179 # check if storage content is accessible on local node and specified target node
180 # we use this on the Clone GUI
182 next if !$scfg->{shared
};
183 next if !PVE
::Storage
::storage_check_node
($cfg, $storeid, undef, 1);
184 next if !PVE
::Storage
::storage_check_node
($cfg, $storeid, $target, 1);
187 if ($data->{total
}) {
188 $data->{used_fraction
} = ($data->{used
} // 0) / $data->{total
};
191 $res->{$storeid} = $data;
194 return PVE
::RESTHandler
::hash_to_array
($res, 'storage');
197 __PACKAGE__-
>register_method ({
203 check
=> ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any
=> 1],
206 additionalProperties
=> 0,
208 node
=> get_standard_option
('pve-node'),
209 storage
=> get_standard_option
('pve-storage-id'),
217 subdir
=> { type
=> 'string' },
220 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
226 { subdir
=> 'content' },
227 { subdir
=> 'download-url' },
228 { subdir
=> 'file-restore' },
229 { subdir
=> 'prunebackups' },
231 { subdir
=> 'rrddata' },
232 { subdir
=> 'status' },
233 { subdir
=> 'upload' },
239 __PACKAGE__-
>register_method ({
240 name
=> 'read_status',
241 path
=> '{storage}/status',
243 description
=> "Read storage status.",
245 check
=> ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any
=> 1],
250 additionalProperties
=> 0,
252 node
=> get_standard_option
('pve-node'),
253 storage
=> get_standard_option
('pve-storage-id'),
263 my $cfg = PVE
::Storage
::config
();
265 my $info = PVE
::Storage
::storage_info
($cfg, $param->{content
});
267 my $data = $info->{$param->{storage
}};
269 raise_param_exc
({ storage
=> "No such storage." })
275 __PACKAGE__-
>register_method ({
277 path
=> '{storage}/rrd',
279 description
=> "Read storage RRD statistics (returns PNG).",
281 check
=> ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any
=> 1],
286 additionalProperties
=> 0,
288 node
=> get_standard_option
('pve-node'),
289 storage
=> get_standard_option
('pve-storage-id'),
291 description
=> "Specify the time frame you are interested in.",
293 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
296 description
=> "The list of datasources you want to display.",
297 type
=> 'string', format
=> 'pve-configid-list',
300 description
=> "The RRD consolidation function",
302 enum
=> [ 'AVERAGE', 'MAX' ],
310 filename
=> { type
=> 'string' },
316 return PVE
::RRD
::create_rrd_graph
(
317 "pve2-storage/$param->{node}/$param->{storage}",
318 $param->{timeframe
}, $param->{ds
}, $param->{cf
});
321 __PACKAGE__-
>register_method ({
323 path
=> '{storage}/rrddata',
325 description
=> "Read storage RRD statistics.",
327 check
=> ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any
=> 1],
332 additionalProperties
=> 0,
334 node
=> get_standard_option
('pve-node'),
335 storage
=> get_standard_option
('pve-storage-id'),
337 description
=> "Specify the time frame you are interested in.",
339 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
342 description
=> "The RRD consolidation function",
344 enum
=> [ 'AVERAGE', 'MAX' ],
359 return PVE
::RRD
::create_rrd_data
(
360 "pve2-storage/$param->{node}/$param->{storage}",
361 $param->{timeframe
}, $param->{cf
});
364 # makes no sense for big images and backup files (because it
365 # create a copy of the file).
366 __PACKAGE__-
>register_method ({
368 path
=> '{storage}/upload',
370 description
=> "Upload templates and ISO images.",
372 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
376 additionalProperties
=> 0,
378 node
=> get_standard_option
('pve-node'),
379 storage
=> get_standard_option
('pve-storage-id'),
381 description
=> "Content type.",
382 type
=> 'string', format
=> 'pve-storage-content',
383 enum
=> ['iso', 'vztmpl'],
386 description
=> "The name of the file to create. Caution: This will be normalized!",
391 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.",
397 returns
=> { type
=> "string" },
401 my $rpcenv = PVE
::RPCEnvironment
::get
();
403 my $user = $rpcenv->get_user();
405 my $cfg = PVE
::Storage
::config
();
407 my $node = $param->{node
};
408 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
410 die "can't upload to storage type '$scfg->{type}'\n"
411 if !defined($scfg->{path
});
413 my $content = $param->{content
};
415 my $tmpfilename = $param->{tmpfilename
};
416 die "missing temporary file name\n" if !$tmpfilename;
418 my $size = -s
$tmpfilename;
419 die "temporary file '$tmpfilename' does not exist\n" if !defined($size);
421 my $filename = PVE
::Storage
::normalize_content_filename
($param->{filename
});
425 if ($content eq 'iso') {
426 if ($filename !~ m![^/]+$PVE::Storage::iso_extension_re$!) {
427 raise_param_exc
({ filename
=> "wrong file extension" });
429 $path = PVE
::Storage
::get_iso_dir
($cfg, $param->{storage
});
430 } elsif ($content eq 'vztmpl') {
431 if ($filename !~ m![^/]+$PVE::Storage::vztmpl_extension_re$!) {
432 raise_param_exc
({ filename
=> "wrong file extension" });
434 $path = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
436 raise_param_exc
({ content
=> "upload content type '$content' not allowed" });
439 die "storage '$param->{storage}' does not support '$content' content\n"
440 if !$scfg->{content
}->{$content};
442 my $dest = "$path/$filename";
443 my $dirname = dirname
($dest);
445 # best effort to match apl_download behaviour
446 chmod 0644, $tmpfilename;
448 # we simply overwrite the destination file if it already exists
451 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
452 my $remip = PVE
::Cluster
::remote_node_ip
($node);
454 my @ssh_options = ('-o', 'BatchMode=yes');
456 my @remcmd = ('/usr/bin/ssh', @ssh_options, $remip, '--');
459 # activate remote storage
460 PVE
::Tools
::run_command
([@remcmd, '/usr/sbin/pvesm', 'status',
461 '--storage', $param->{storage
}]);
463 die "can't activate storage '$param->{storage}' on node '$node': $@\n" if $@;
465 PVE
::Tools
::run_command
([@remcmd, '/bin/mkdir', '-p', '--', PVE
::Tools
::shell_quote
($dirname)],
466 errmsg
=> "mkdir failed");
468 $cmd = ['/usr/bin/scp', @ssh_options, '-p', '--', $tmpfilename, "[$remip]:" . PVE
::Tools
::shell_quote
($dest)];
470 PVE
::Storage
::activate_storage
($cfg, $param->{storage
});
471 File
::Path
::make_path
($dirname);
472 $cmd = ['cp', '--', $tmpfilename, $dest];
478 print "starting file import from: $tmpfilename\n";
479 print "target node: $node\n";
480 print "target file: $dest\n";
481 print "file size is: $size\n";
482 print "command: " . join(' ', @$cmd) . "\n";
484 eval { PVE
::Tools
::run_command
($cmd, errmsg
=> 'import failed'); };
489 print "finished file import successfully\n";
492 my $upid = $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
494 # apache removes the temporary file on return, so we need
495 # to wait here to make sure the worker process starts and
496 # opens the file before it gets removed.
502 __PACKAGE__-
>register_method({
503 name
=> 'download_url',
504 path
=> '{storage}/download-url',
506 description
=> "Download templates and ISO images by using an URL.",
510 ['perm', '/storage/{storage}', [ 'Datastore.AllocateTemplate' ]],
511 ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]],
516 additionalProperties
=> 0,
518 node
=> get_standard_option
('pve-node'),
519 storage
=> get_standard_option
('pve-storage-id'),
521 description
=> "The URL to download the file from.",
523 pattern
=> 'https?://.*',
526 description
=> "Content type.", # TODO: could be optional & detected in most cases
527 type
=> 'string', format
=> 'pve-storage-content',
528 enum
=> ['iso', 'vztmpl'],
531 description
=> "The name of the file to create. Caution: This will be normalized!",
536 description
=> "The expected checksum of the file.",
538 requires
=> 'checksum-algorithm',
541 'checksum-algorithm' => {
542 description
=> "The algorithm to calculate the checksum of the file.",
544 enum
=> ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'],
545 requires
=> 'checksum',
548 'verify-certificates' => {
549 description
=> "If false, no SSL/TLS certificates will be verified.",
562 my $rpcenv = PVE
::RPCEnvironment
::get
();
563 my $user = $rpcenv->get_user();
565 my $cfg = PVE
::Storage
::config
();
567 my ($node, $storage) = $param->@{'node', 'storage'};
568 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $storage, $node);
570 die "can't upload to storage type '$scfg->{type}', not a file based storage!\n"
571 if !defined($scfg->{path
});
573 my ($content, $url) = $param->@{'content', 'url'};
575 die "storage '$storage' is not configured for content-type '$content'\n"
576 if !$scfg->{content
}->{$content};
578 my $filename = PVE
::Storage
::normalize_content_filename
($param->{filename
});
581 if ($content eq 'iso') {
582 if ($filename !~ m![^/]+$PVE::Storage::iso_extension_re$!) {
583 raise_param_exc
({ filename
=> "wrong file extension" });
585 $path = PVE
::Storage
::get_iso_dir
($cfg, $storage);
586 } elsif ($content eq 'vztmpl') {
587 if ($filename !~ m![^/]+$PVE::Storage::vztmpl_extension_re$!) {
588 raise_param_exc
({ filename
=> "wrong file extension" });
590 $path = PVE
::Storage
::get_vztmpl_dir
($cfg, $storage);
592 raise_param_exc
({ content
=> "upload content-type '$content' is not allowed" });
595 PVE
::Storage
::activate_storage
($cfg, $storage);
596 File
::Path
::make_path
($path);
598 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
601 verify_certificates
=> $param->{'verify-certificates'} // 1,
602 http_proxy
=> $dccfg->{http_proxy
},
605 my ($checksum, $checksum_algorithm) = $param->@{'checksum', 'checksum-algorithm'};
607 $opts->{"${checksum_algorithm}sum"} = $checksum;
608 $opts->{hash_required
} = 1;
612 PVE
::Tools
::download_file_from_url
("$path/$filename", $url, $opts);
615 my $worker_id = PVE
::Tools
::encode_text
($filename); # must not pass : or the like as w-ID
617 return $rpcenv->fork_worker('download', $worker_id, $user, $worker);