1 package PVE
::API2
::Storage
::Status
;
11 use PVE
::Exception
qw(raise_param_exc);
13 use PVE
::JSONSchema
qw(get_standard_option);
15 use PVE
::RPCEnvironment
;
17 use PVE
::Tools
qw(run_command);
19 use PVE
::API2
::Storage
::Content
;
20 use PVE
::API2
::Storage
::FileRestore
;
21 use PVE
::API2
::Storage
::PruneBackups
;
24 use base
qw(PVE::RESTHandler);
26 __PACKAGE__-
>register_method ({
27 subclass
=> "PVE::API2::Storage::PruneBackups",
28 path
=> '{storage}/prunebackups',
31 __PACKAGE__-
>register_method ({
32 subclass
=> "PVE::API2::Storage::Content",
33 # set fragment delimiter (no subdirs) - we need that, because volume
34 # IDs may contain a slash '/'
35 fragmentDelimiter
=> '',
36 path
=> '{storage}/content',
39 __PACKAGE__-
>register_method ({
40 subclass
=> "PVE::API2::Storage::FileRestore",
41 path
=> '{storage}/file-restore',
44 __PACKAGE__-
>register_method ({
48 description
=> "Get status for all datastores.",
50 description
=> "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
56 additionalProperties
=> 0,
58 node
=> get_standard_option
('pve-node'),
59 storage
=> get_standard_option
('pve-storage-id', {
60 description
=> "Only list status for specified storage",
62 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
65 description
=> "Only list stores which support this content type.",
66 type
=> 'string', format
=> 'pve-storage-content-list',
68 completion
=> \
&PVE
::Storage
::complete_content_type
,
71 description
=> "Only list stores which are enabled (not disabled in config).",
76 target
=> get_standard_option
('pve-node', {
77 description
=> "If target is different to 'node', we only lists shared storages which " .
78 "content is accessible on this 'node' and the specified 'target' node.",
80 completion
=> \
&PVE
::Cluster
::get_nodelist
,
83 description
=> "Include information about formats",
95 storage
=> get_standard_option
('pve-storage-id'),
97 description
=> "Storage type.",
101 description
=> "Allowed storage content types.",
102 type
=> 'string', format
=> 'pve-storage-content-list',
105 description
=> "Set when storage is enabled (not disabled).",
110 description
=> "Set when storage is accessible.",
115 description
=> "Shared flag from storage configuration.",
120 description
=> "Total storage space in bytes.",
126 description
=> "Used storage space in bytes.",
132 description
=> "Available storage space in bytes.",
138 description
=> "Used fraction (used/total).",
140 renderer
=> 'fraction_as_percentage',
145 links
=> [ { rel
=> 'child', href
=> "{storage}" } ],
150 my $rpcenv = PVE
::RPCEnvironment
::get
();
151 my $authuser = $rpcenv->get_user();
153 my $localnode = PVE
::INotify
::nodename
();
155 my $target = $param->{target
};
157 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
159 my $cfg = PVE
::Storage
::config
();
161 my $info = PVE
::Storage
::storage_info
($cfg, $param->{content
}, $param->{format
});
163 raise_param_exc
({ storage
=> "No such storage." })
164 if $param->{storage
} && !defined($info->{$param->{storage
}});
167 my @sids = PVE
::Storage
::storage_ids
($cfg);
168 foreach my $storeid (@sids) {
169 my $data = $info->{$storeid};
171 my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ];
172 next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
173 next if $param->{storage
} && $param->{storage
} ne $storeid;
175 my $scfg = PVE
::Storage
::storage_config
($cfg, $storeid);
177 next if $param->{enabled
} && $scfg->{disable
};
180 # check if storage content is accessible on local node and specified target node
181 # we use this on the Clone GUI
183 next if !$scfg->{shared
};
184 next if !PVE
::Storage
::storage_check_node
($cfg, $storeid, undef, 1);
185 next if !PVE
::Storage
::storage_check_node
($cfg, $storeid, $target, 1);
188 if ($data->{total
}) {
189 $data->{used_fraction
} = ($data->{used
} // 0) / $data->{total
};
192 $res->{$storeid} = $data;
195 return PVE
::RESTHandler
::hash_to_array
($res, 'storage');
198 __PACKAGE__-
>register_method ({
204 check
=> ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any
=> 1],
207 additionalProperties
=> 0,
209 node
=> get_standard_option
('pve-node'),
210 storage
=> get_standard_option
('pve-storage-id'),
218 subdir
=> { type
=> 'string' },
221 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
227 { subdir
=> 'content' },
228 { subdir
=> 'download-url' },
229 { subdir
=> 'file-restore' },
230 { subdir
=> 'import-metadata' },
231 { subdir
=> 'prunebackups' },
233 { subdir
=> 'rrddata' },
234 { subdir
=> 'status' },
235 { subdir
=> 'upload' },
241 __PACKAGE__-
>register_method ({
242 name
=> 'read_status',
243 path
=> '{storage}/status',
245 description
=> "Read storage status.",
247 check
=> ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any
=> 1],
252 additionalProperties
=> 0,
254 node
=> get_standard_option
('pve-node'),
255 storage
=> get_standard_option
('pve-storage-id'),
265 my $cfg = PVE
::Storage
::config
();
267 my $info = PVE
::Storage
::storage_info
($cfg, $param->{content
});
269 my $data = $info->{$param->{storage
}};
271 raise_param_exc
({ storage
=> "No such storage." })
277 __PACKAGE__-
>register_method ({
279 path
=> '{storage}/rrd',
281 description
=> "Read storage RRD statistics (returns PNG).",
283 check
=> ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any
=> 1],
288 additionalProperties
=> 0,
290 node
=> get_standard_option
('pve-node'),
291 storage
=> get_standard_option
('pve-storage-id'),
293 description
=> "Specify the time frame you are interested in.",
295 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
298 description
=> "The list of datasources you want to display.",
299 type
=> 'string', format
=> 'pve-configid-list',
302 description
=> "The RRD consolidation function",
304 enum
=> [ 'AVERAGE', 'MAX' ],
312 filename
=> { type
=> 'string' },
318 return PVE
::RRD
::create_rrd_graph
(
319 "pve2-storage/$param->{node}/$param->{storage}",
320 $param->{timeframe
}, $param->{ds
}, $param->{cf
});
323 __PACKAGE__-
>register_method ({
325 path
=> '{storage}/rrddata',
327 description
=> "Read storage RRD statistics.",
329 check
=> ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any
=> 1],
334 additionalProperties
=> 0,
336 node
=> get_standard_option
('pve-node'),
337 storage
=> get_standard_option
('pve-storage-id'),
339 description
=> "Specify the time frame you are interested in.",
341 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
344 description
=> "The RRD consolidation function",
346 enum
=> [ 'AVERAGE', 'MAX' ],
361 return PVE
::RRD
::create_rrd_data
(
362 "pve2-storage/$param->{node}/$param->{storage}",
363 $param->{timeframe
}, $param->{cf
});
366 # makes no sense for big images and backup files (because it
367 # create a copy of the file).
368 __PACKAGE__-
>register_method ({
370 path
=> '{storage}/upload',
372 description
=> "Upload templates and ISO images.",
374 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
378 additionalProperties
=> 0,
380 node
=> get_standard_option
('pve-node'),
381 storage
=> get_standard_option
('pve-storage-id'),
383 description
=> "Content type.",
384 type
=> 'string', format
=> 'pve-storage-content',
385 enum
=> ['iso', 'vztmpl'],
388 description
=> "The name of the file to create. Caution: This will be normalized!",
393 description
=> "The expected checksum of the file.",
395 requires
=> 'checksum-algorithm',
398 'checksum-algorithm' => {
399 description
=> "The algorithm to calculate the checksum of the file.",
401 enum
=> ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'],
402 requires
=> 'checksum',
406 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.",
409 pattern
=> '/var/tmp/pveupload-[0-9a-f]+',
413 returns
=> { type
=> "string" },
417 my $rpcenv = PVE
::RPCEnvironment
::get
();
419 my $user = $rpcenv->get_user();
421 my $cfg = PVE
::Storage
::config
();
423 my $node = $param->{node
};
424 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
426 die "can't upload to storage type '$scfg->{type}'\n"
427 if !defined($scfg->{path
});
429 my $content = $param->{content
};
431 my $tmpfilename = $param->{tmpfilename
};
432 die "missing temporary file name\n" if !$tmpfilename;
434 my $size = -s
$tmpfilename;
435 die "temporary file '$tmpfilename' does not exist\n" if !defined($size);
437 my $filename = PVE
::Storage
::normalize_content_filename
($param->{filename
});
441 if ($content eq 'iso') {
442 if ($filename !~ m![^/]+$PVE::Storage::ISO_EXT_RE_0$!) {
443 raise_param_exc
({ filename
=> "wrong file extension" });
445 $path = PVE
::Storage
::get_iso_dir
($cfg, $param->{storage
});
446 } elsif ($content eq 'vztmpl') {
447 if ($filename !~ m![^/]+$PVE::Storage::VZTMPL_EXT_RE_1$!) {
448 raise_param_exc
({ filename
=> "wrong file extension" });
450 $path = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
452 raise_param_exc
({ content
=> "upload content type '$content' not allowed" });
455 die "storage '$param->{storage}' does not support '$content' content\n"
456 if !$scfg->{content
}->{$content};
458 my $dest = "$path/$filename";
459 my $dirname = dirname
($dest);
461 # best effort to match apl_download behaviour
462 chmod 0644, $tmpfilename;
464 my $err_cleanup = sub { unlink $dest; die "cleanup failed: $!\n" if $! && $! != ENOENT
};
467 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
468 my $remip = PVE
::Cluster
::remote_node_ip
($node);
470 my @ssh_options = ('-o', 'BatchMode=yes');
472 my @remcmd = ('/usr/bin/ssh', @ssh_options, $remip, '--');
474 eval { # activate remote storage
475 run_command
([@remcmd, '/usr/sbin/pvesm', 'status', '--storage', $param->{storage
}]);
477 die "can't activate storage '$param->{storage}' on node '$node': $@\n" if $@;
480 [@remcmd, '/bin/mkdir', '-p', '--', PVE
::Tools
::shell_quote
($dirname)],
481 errmsg
=> "mkdir failed",
484 $cmd = ['/usr/bin/scp', @ssh_options, '-p', '--', $tmpfilename, "[$remip]:" . PVE
::Tools
::shell_quote
($dest)];
486 $err_cleanup = sub { run_command
([@remcmd, 'rm', '-f', '--', $dest]) };
488 PVE
::Storage
::activate_storage
($cfg, $param->{storage
});
489 File
::Path
::make_path
($dirname);
490 $cmd = ['cp', '--', $tmpfilename, $dest];
493 # NOTE: we simply overwrite the destination file if it already exists
497 print "starting file import from: $tmpfilename\n";
500 my ($checksum, $checksum_algorithm) = $param->@{'checksum', 'checksum-algorithm'};
501 if ($checksum_algorithm) {
502 print "calculating checksum...";
504 my $checksum_got = PVE
::Tools
::get_file_hash
($checksum_algorithm, $tmpfilename);
506 if (lc($checksum_got) eq lc($checksum)) {
507 print "OK, checksum verified\n";
509 print "\n"; # the front end expects the error to reside at the last line without any noise
510 die "checksum mismatch: got '$checksum_got' != expect '$checksum'\n";
515 # unlinks only the temporary file from the http server
517 warn "unable to clean up temporory file '$tmpfilename' - $!\n"
518 if $! && $! != ENOENT
;
522 print "target node: $node\n";
523 print "target file: $dest\n";
524 print "file size is: $size\n";
525 print "command: " . join(' ', @$cmd) . "\n";
527 eval { run_command
($cmd, errmsg
=> 'import failed'); };
529 unlink $tmpfilename; # the temporary file got only uploaded locally, no need to rm remote
530 warn "unable to clean up temporary file '$tmpfilename' - $!\n" if $! && $! != ENOENT
;
533 eval { $err_cleanup->() };
537 print "finished file import successfully\n";
540 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
543 __PACKAGE__-
>register_method({
544 name
=> 'download_url',
545 path
=> '{storage}/download-url',
547 description
=> "Download templates and ISO images by using an URL.",
550 description
=> 'Requires allocation access on the storage and as this allows one to probe'
551 .' the (local!) host network indirectly it also requires one of Sys.Modify on / (for'
552 .' backwards compatibility) or the newer Sys.AccessNetwork privilege on the node.',
554 ['perm', '/storage/{storage}', [ 'Datastore.AllocateTemplate' ]],
556 ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]],
557 ['perm', '/nodes/{node}', [ 'Sys.AccessNetwork' ]],
563 additionalProperties
=> 0,
565 node
=> get_standard_option
('pve-node'),
566 storage
=> get_standard_option
('pve-storage-id'),
568 description
=> "The URL to download the file from.",
570 pattern
=> 'https?://.*',
573 description
=> "Content type.", # TODO: could be optional & detected in most cases
574 type
=> 'string', format
=> 'pve-storage-content',
575 enum
=> ['iso', 'vztmpl'],
578 description
=> "The name of the file to create. Caution: This will be normalized!",
583 description
=> "The expected checksum of the file.",
585 requires
=> 'checksum-algorithm',
590 "Decompress the downloaded file using the specified compression algorithm.",
592 enum
=> $PVE::Storage
::Plugin
::KNOWN_COMPRESSION_FORMATS
,
595 'checksum-algorithm' => {
596 description
=> "The algorithm to calculate the checksum of the file.",
598 enum
=> ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'],
599 requires
=> 'checksum',
602 'verify-certificates' => {
603 description
=> "If false, no SSL/TLS certificates will be verified.",
616 my $rpcenv = PVE
::RPCEnvironment
::get
();
617 my $user = $rpcenv->get_user();
619 my $cfg = PVE
::Storage
::config
();
621 my ($node, $storage, $compression) = $param->@{qw(node storage compression)};
622 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $storage, $node);
624 die "can't upload to storage type '$scfg->{type}', not a file based storage!\n"
625 if !defined($scfg->{path
});
627 my ($content, $url) = $param->@{'content', 'url'};
629 die "storage '$storage' is not configured for content-type '$content'\n"
630 if !$scfg->{content
}->{$content};
632 my $filename = PVE
::Storage
::normalize_content_filename
($param->{filename
});
635 if ($content eq 'iso') {
636 if ($filename !~ m![^/]+$PVE::Storage::ISO_EXT_RE_0$!) {
637 raise_param_exc
({ filename
=> "wrong file extension" });
639 $path = PVE
::Storage
::get_iso_dir
($cfg, $storage);
640 } elsif ($content eq 'vztmpl') {
641 if ($filename !~ m![^/]+$PVE::Storage::VZTMPL_EXT_RE_1$!) {
642 raise_param_exc
({ filename
=> "wrong file extension" });
644 $path = PVE
::Storage
::get_vztmpl_dir
($cfg, $storage);
646 raise_param_exc
({ content
=> "upload content-type '$content' is not allowed" });
649 PVE
::Storage
::activate_storage
($cfg, $storage);
650 File
::Path
::make_path
($path);
652 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
655 verify_certificates
=> $param->{'verify-certificates'} // 1,
656 http_proxy
=> $dccfg->{http_proxy
},
659 my ($checksum, $checksum_algorithm) = $param->@{'checksum', 'checksum-algorithm'};
661 $opts->{"${checksum_algorithm}sum"} = $checksum;
662 $opts->{hash_required
} = 1;
667 die "decompression not supported for $content\n" if $content ne 'iso';
668 my $info = PVE
::Storage
::decompressor_info
('iso', $compression);
669 die "no decompression method found\n" if !$info->{decompressor
};
670 $opts->{decompression_command
} = $info->{decompressor
};
672 PVE
::Tools
::download_file_from_url
("$path/$filename", $url, $opts);
675 my $worker_id = PVE
::Tools
::encode_text
($filename); # must not pass : or the like as w-ID
677 return $rpcenv->fork_worker('download', $worker_id, $user, $worker);
680 __PACKAGE__-
>register_method({
681 name
=> 'get_import_metadata',
682 path
=> '{storage}/import-metadata',
685 "Get the base parameters for creating a guest which imports data from a foreign importable"
686 ." guest, like an ESXi VM",
689 description
=> "You need read access for the volume.",
694 additionalProperties
=> 0,
696 node
=> get_standard_option
('pve-node'),
697 storage
=> get_standard_option
('pve-storage-id'),
699 description
=> "Volume identifier for the guest archive/entry.",
706 description
=> 'Information about how to import a guest.',
707 additionalProperties
=> 0,
712 description
=> 'The type of guest this is going to produce.',
717 description
=> 'The type of the import-source of this guest volume.',
721 additionalProperties
=> 1,
722 description
=> 'Parameters which can be used in a call to create a VM or container.',
726 additionalProperties
=> 1,
728 description
=> 'Recognised disk volumes as `$bus$id` => `$storeid:$path` map.',
732 additionalProperties
=> 1,
734 description
=> 'Recognised network interfaces as `net$id` => { ...params } object.',
738 description
=> 'List of known issues that can affect the import of a guest.'
739 .' Note that lack of warning does not imply that there cannot be any problems.',
743 additionalProperties
=> 1,
746 description
=> 'What this warning is about.',
748 'cdrom-image-ignored',
751 'ovmf-with-lsi-unsupported',
752 'serial-port-socket-only',
757 description
=> 'Related subject (config) key of warning.',
762 description
=> 'Related subject (config) value of warning.',
774 my $rpcenv = PVE
::RPCEnvironment
::get
();
775 my $authuser = $rpcenv->get_user();
777 my ($storeid, $volume) = $param->@{qw(storage volume)};
778 my $volid = "$storeid:$volume";
780 my $cfg = PVE
::Storage
::config
();
782 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $cfg, undef, $volid);
784 return PVE
::Tools
::run_with_timeout
(30, sub {
785 my $import = PVE
::Storage
::get_import_metadata
($cfg, $volid);
786 return $import->get_create_args();