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