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