]> git.proxmox.com Git - pve-storage.git/blob - PVE/API2/Storage/Status.pm
rbd: use add/delete 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;
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 format => {
67 description => "Include information about formats",
68 type => 'boolean',
69 optional => 1,
70 default => 0,
71 },
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
85 my $rpcenv = PVE::RPCEnvironment::get();
86 my $authuser = $rpcenv->get_user();
87
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
94 my $cfg = PVE::Storage::config();
95
96 my $info = PVE::Storage::storage_info($cfg, $param->{content}, $param->{format});
97
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) {
104 next if !$info->{$storeid};
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;
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
122 $res->{$storeid} = $info->{$storeid};
123 }
124
125 return PVE::RESTHandler::hash_to_array($res, 'storage');
126 }});
127
128 __PACKAGE__->register_method ({
129 name => 'diridx',
130 path => '{storage}',
131 method => 'GET',
132 description => "",
133 permissions => {
134 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
135 },
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' },
159 { subdir => 'upload' },
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.",
172 permissions => {
173 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
174 },
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
191 my $cfg = PVE::Storage::config();
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).",
208 permissions => {
209 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
210 },
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.",
255 permissions => {
256 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
257 },
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 }});
292
293 # makes no sense for big images and backup files (because it
294 # create a copy of the file).
295 __PACKAGE__->register_method ({
296 name => 'upload',
297 path => '{storage}/upload',
298 method => 'POST',
299 description => "Upload templates and ISO images.",
300 permissions => {
301 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
302 },
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
332 my $cfg = PVE::Storage::config();
333
334 my $node = $param->{node};
335 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
336
337 die "can't upload to storage type '$scfg->{type}'\n"
338 if !defined($scfg->{path});
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/^.*[\/\\]//;
352 $filename =~ s/[^-a-zA-Z0-9_.]/_/g;
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') {
362 if ($filename !~ m![^/]+\.tar\.[gx]z$!) {
363 raise_param_exc({ filename => "missing '.tar.gz' or '.tar.xz' extension" });
364 }
365 $path = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
366 } else {
367 raise_param_exc({ content => "upload content type '$content' not allowed" });
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
382 my @ssh_options = ('-o', 'BatchMode=yes');
383
384 my @remcmd = ('/usr/bin/ssh', @ssh_options, $remip, '--');
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
393 PVE::Tools::run_command([@remcmd, '/bin/mkdir', '-p', '--', PVE::Tools::shell_quote($dirname)],
394 errmsg => "mkdir failed");
395
396 $cmd = ['/usr/bin/scp', @ssh_options, '--', $tmpfilename, "[$remip]:" . PVE::Tools::shell_quote($dest)];
397 } else {
398 PVE::Storage::activate_storage($cfg, $param->{storage});
399 File::Path::make_path($dirname);
400 $cmd = ['cp', '--', $tmpfilename, $dest];
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
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;
428 }});
429
430 1;