]> git.proxmox.com Git - pve-storage.git/blame - PVE/API2/Storage/Status.pm
add 'format' parameter to storage list call
[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 }),
856c54bd
DC
66 format => {
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",
78 properties => { storage => { type => 'string' } },
79 },
80 links => [ { rel => 'child', href => "{storage}" } ],
81 },
82 code => sub {
83 my ($param) = @_;
84
5f642f73
DM
85 my $rpcenv = PVE::RPCEnvironment::get();
86 my $authuser = $rpcenv->get_user();
87
283608f3
DM
88 my $localnode = PVE::INotify::nodename();
89
90 my $target = $param->{target};
91
92 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
93
83d7192f 94 my $cfg = PVE::Storage::config();
b6cf0a66 95
856c54bd 96 my $info = PVE::Storage::storage_info($cfg, $param->{content}, $param->{format});
b6cf0a66 97
5f642f73
DM
98 raise_param_exc({ storage => "No such storage." })
99 if $param->{storage} && !defined($info->{$param->{storage}});
100
101 my $res = {};
102 my @sids = PVE::Storage::storage_ids($cfg);
103 foreach my $storeid (@sids) {
0c1473f9 104 next if !$info->{$storeid};
5f642f73
DM
105 my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ];
106 next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
107 next if $param->{storage} && $param->{storage} ne $storeid;
283608f3
DM
108
109 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
110
111 next if $param->{enabled} && $scfg->{disable};
112
113 if ($target) {
114 # check if storage content is accessible on local node and specified target node
115 # we use this on the Clone GUI
116
117 next if !$scfg->{shared};
118 next if !PVE::Storage::storage_check_node($cfg, $storeid, undef, 1);
119 next if !PVE::Storage::storage_check_node($cfg, $storeid, $target, 1);
120 }
121
5f642f73 122 $res->{$storeid} = $info->{$storeid};
b6cf0a66 123 }
5f642f73
DM
124
125 return PVE::RESTHandler::hash_to_array($res, 'storage');
b6cf0a66
DM
126 }});
127
128__PACKAGE__->register_method ({
129 name => 'diridx',
130 path => '{storage}',
131 method => 'GET',
132 description => "",
5f642f73
DM
133 permissions => {
134 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
135 },
b6cf0a66
DM
136 parameters => {
137 additionalProperties => 0,
138 properties => {
139 node => get_standard_option('pve-node'),
140 storage => get_standard_option('pve-storage-id'),
141 },
142 },
143 returns => {
144 type => 'array',
145 items => {
146 type => "object",
147 properties => {
148 subdir => { type => 'string' },
149 },
150 },
151 links => [ { rel => 'child', href => "{subdir}" } ],
152 },
153 code => sub {
154 my ($param) = @_;
155
156 my $res = [
157 { subdir => 'status' },
158 { subdir => 'content' },
7814e05f 159 { subdir => 'upload' },
b6cf0a66
DM
160 { subdir => 'rrd' },
161 { subdir => 'rrddata' },
162 ];
163
164 return $res;
165 }});
166
167__PACKAGE__->register_method ({
168 name => 'read_status',
169 path => '{storage}/status',
170 method => 'GET',
171 description => "Read storage status.",
5f642f73
DM
172 permissions => {
173 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
174 },
b6cf0a66
DM
175 protected => 1,
176 proxyto => 'node',
177 parameters => {
178 additionalProperties => 0,
179 properties => {
180 node => get_standard_option('pve-node'),
181 storage => get_standard_option('pve-storage-id'),
182 },
183 },
184 returns => {
185 type => "object",
186 properties => {},
187 },
188 code => sub {
189 my ($param) = @_;
190
83d7192f 191 my $cfg = PVE::Storage::config();
b6cf0a66
DM
192
193 my $info = PVE::Storage::storage_info($cfg, $param->{content});
194
195 my $data = $info->{$param->{storage}};
196
197 raise_param_exc({ storage => "No such storage." })
198 if !defined($data);
199
200 return $data;
201 }});
202
203__PACKAGE__->register_method ({
204 name => 'rrd',
205 path => '{storage}/rrd',
206 method => 'GET',
207 description => "Read storage RRD statistics (returns PNG).",
5f642f73
DM
208 permissions => {
209 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
210 },
b6cf0a66
DM
211 protected => 1,
212 proxyto => 'node',
213 parameters => {
214 additionalProperties => 0,
215 properties => {
216 node => get_standard_option('pve-node'),
217 storage => get_standard_option('pve-storage-id'),
218 timeframe => {
219 description => "Specify the time frame you are interested in.",
220 type => 'string',
221 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
222 },
223 ds => {
224 description => "The list of datasources you want to display.",
225 type => 'string', format => 'pve-configid-list',
226 },
227 cf => {
228 description => "The RRD consolidation function",
229 type => 'string',
230 enum => [ 'AVERAGE', 'MAX' ],
231 optional => 1,
232 },
233 },
234 },
235 returns => {
236 type => "object",
237 properties => {
238 filename => { type => 'string' },
239 },
240 },
241 code => sub {
242 my ($param) = @_;
243
244 return PVE::Cluster::create_rrd_graph(
245 "pve2-storage/$param->{node}/$param->{storage}",
246 $param->{timeframe}, $param->{ds}, $param->{cf});
247
248 }});
249
250__PACKAGE__->register_method ({
251 name => 'rrddata',
252 path => '{storage}/rrddata',
253 method => 'GET',
254 description => "Read storage RRD statistics.",
5f642f73
DM
255 permissions => {
256 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
257 },
b6cf0a66
DM
258 protected => 1,
259 proxyto => 'node',
260 parameters => {
261 additionalProperties => 0,
262 properties => {
263 node => get_standard_option('pve-node'),
264 storage => get_standard_option('pve-storage-id'),
265 timeframe => {
266 description => "Specify the time frame you are interested in.",
267 type => 'string',
268 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
269 },
270 cf => {
271 description => "The RRD consolidation function",
272 type => 'string',
273 enum => [ 'AVERAGE', 'MAX' ],
274 optional => 1,
275 },
276 },
277 },
278 returns => {
279 type => "array",
280 items => {
281 type => "object",
282 properties => {},
283 },
284 },
285 code => sub {
286 my ($param) = @_;
287
288 return PVE::Cluster::create_rrd_data(
289 "pve2-storage/$param->{node}/$param->{storage}",
290 $param->{timeframe}, $param->{cf});
291 }});
7814e05f 292
1f6610f3
DM
293# makes no sense for big images and backup files (because it
294# create a copy of the file).
7814e05f
DM
295__PACKAGE__->register_method ({
296 name => 'upload',
297 path => '{storage}/upload',
298 method => 'POST',
1f6610f3 299 description => "Upload templates and ISO images.",
5f642f73 300 permissions => {
1f6610f3 301 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
5f642f73 302 },
7814e05f
DM
303 protected => 1,
304 parameters => {
305 additionalProperties => 0,
306 properties => {
307 node => get_standard_option('pve-node'),
308 storage => get_standard_option('pve-storage-id'),
309 content => {
310 description => "Content type.",
311 type => 'string', format => 'pve-storage-content',
312 },
313 filename => {
314 description => "The name of the file to create.",
315 type => 'string',
316 },
317 tmpfilename => {
318 description => "The source file name. This parameter is usually set by the REST handler. You can only overwrite it when connecting to the trustet port on localhost.",
319 type => 'string',
320 optional => 1,
321 },
322 },
323 },
324 returns => { type => "string" },
325 code => sub {
326 my ($param) = @_;
327
328 my $rpcenv = PVE::RPCEnvironment::get();
329
330 my $user = $rpcenv->get_user();
331
83d7192f 332 my $cfg = PVE::Storage::config();
7814e05f
DM
333
334 my $node = $param->{node};
335 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
336
571abaa6 337 die "cant upload to storage type '$scfg->{type}'\n"
e384c898 338 if !($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs' || $scfg->{type} eq 'glusterfs');
7814e05f
DM
339
340 my $content = $param->{content};
341
342 my $tmpfilename = $param->{tmpfilename};
343 die "missing temporary file name\n" if !$tmpfilename;
344
345 my $size = -s $tmpfilename;
346 die "temporary file '$tmpfilename' does not exists\n" if !defined($size);
347
348 my $filename = $param->{filename};
349
350 chomp $filename;
351 $filename =~ s/^.*[\/\\]//;
38e1eb3d 352 $filename =~ s/[^-a-zA-Z0-9_.]/_/g;
7814e05f
DM
353
354 my $path;
355
356 if ($content eq 'iso') {
357 if ($filename !~ m![^/]+\.[Ii][Ss][Oo]$!) {
358 raise_param_exc({ filename => "missing '.iso' extension" });
359 }
360 $path = PVE::Storage::get_iso_dir($cfg, $param->{storage});
361 } elsif ($content eq 'vztmpl') {
030f6e1a
EK
362 if ($filename !~ m![^/]+\.tar\.[gx]z$!) {
363 raise_param_exc({ filename => "missing '.tar.gz' or '.tar.xz' extension" });
7814e05f 364 }
4ea5bca4 365 $path = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
7814e05f 366 } else {
1f6610f3 367 raise_param_exc({ content => "upload content type '$content' not allowed" });
7814e05f
DM
368 }
369
370 die "storage '$param->{storage}' does not support '$content' content\n"
371 if !$scfg->{content}->{$content};
372
373 my $dest = "$path/$filename";
374 my $dirname = dirname($dest);
375
376 # we simply overwrite when destination when file already exists
377
378 my $cmd;
379 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
380 my $remip = PVE::Cluster::remote_node_ip($node);
381
45c2ee35 382 my @ssh_options = ('-o', 'BatchMode=yes');
7814e05f 383
53ec90e2 384 my @remcmd = ('/usr/bin/ssh', @ssh_options, $remip, '--');
7814e05f
DM
385
386 eval {
387 # activate remote storage
388 PVE::Tools::run_command([@remcmd, '/usr/sbin/pvesm', 'status',
389 '--storage', $param->{storage}]);
390 };
391 die "can't activate storage '$param->{storage}' on node '$node'\n" if $@;
392
53ec90e2 393 PVE::Tools::run_command([@remcmd, '/bin/mkdir', '-p', '--', PVE::Tools::shell_quote($dirname)],
7814e05f
DM
394 errmsg => "mkdir failed");
395
53ec90e2 396 $cmd = ['/usr/bin/scp', @ssh_options, '--', $tmpfilename, "[$remip]:" . PVE::Tools::shell_quote($dest)];
7814e05f
DM
397 } else {
398 PVE::Storage::activate_storage($cfg, $param->{storage});
4ea5bca4 399 File::Path::make_path($dirname);
53ec90e2 400 $cmd = ['cp', '--', $tmpfilename, $dest];
7814e05f
DM
401 }
402
403 my $worker = sub {
404 my $upid = shift;
405
406 print "starting file import from: $tmpfilename\n";
407 print "target node: $node\n";
408 print "target file: $dest\n";
409 print "file size is: $size\n";
410 print "command: " . join(' ', @$cmd) . "\n";
411
412 eval { PVE::Tools::run_command($cmd, errmsg => 'import failed'); };
413 if (my $err = $@) {
414 unlink $dest;
415 die $err;
416 }
417 print "finished file import successfully\n";
418 };
419
d6c9dc34
DM
420 my $upid = $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
421
422 # apache removes the temporary file on return, so we need
423 # to wait here to make sure the worker process starts and
424 # opens the file before it gets removed.
425 sleep(1);
426
427 return $upid;
7814e05f 428 }});
b6cf0a66
DM
429
4301;