]> git.proxmox.com Git - pve-storage.git/blob - PVE/API2/Storage/Content.pm
349231df9fc71d1b6e4eafb1259dfdb5fee4e124
[pve-storage.git] / PVE / API2 / Storage / Content.pm
1 package PVE::API2::Storage::Content;
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
6
7 use PVE::SafeSyslog;
8 use PVE::Cluster;
9 use PVE::Storage;
10 use PVE::INotify;
11 use PVE::Exception qw(raise_param_exc);
12 use PVE::RPCEnvironment;
13 use PVE::RESTHandler;
14 use PVE::JSONSchema qw(get_standard_option);
15 use PVE::SSHInfo;
16
17 use base qw(PVE::RESTHandler);
18
19 __PACKAGE__->register_method ({
20 name => 'index',
21 path => '',
22 method => 'GET',
23 description => "List storage content.",
24 permissions => {
25 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
26 },
27 protected => 1,
28 proxyto => 'node',
29 parameters => {
30 additionalProperties => 0,
31 properties => {
32 node => get_standard_option('pve-node'),
33 storage => get_standard_option('pve-storage-id', {
34 completion => \&PVE::Storage::complete_storage_enabled,
35 }),
36 content => {
37 description => "Only list content of this type.",
38 type => 'string', format => 'pve-storage-content',
39 optional => 1,
40 completion => \&PVE::Storage::complete_content_type,
41 },
42 vmid => get_standard_option('pve-vmid', {
43 description => "Only list images for this VM",
44 optional => 1,
45 completion => \&PVE::Cluster::complete_vmid,
46 }),
47 },
48 },
49 returns => {
50 type => 'array',
51 items => {
52 type => "object",
53 properties => {
54 volid => {
55 description => "Volume identifier.",
56 type => 'string',
57 },
58 vmid => {
59 description => "Associated Owner VMID.",
60 type => 'integer',
61 optional => 1,
62 },
63 parent => {
64 description => "Volume identifier of parent (for linked cloned).",
65 type => 'string',
66 optional => 1,
67 },
68 'format' => {
69 description => "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
70 type => 'string',
71 },
72 size => {
73 description => "Volume size in bytes.",
74 type => 'integer',
75 renderer => 'bytes',
76 },
77 used => {
78 description => "Used space. Please note that most storage plugins " .
79 "do not report anything useful here.",
80 type => 'integer',
81 renderer => 'bytes',
82 optional => 1,
83 },
84 ctime => {
85 description => "Creation time (seconds since the UNIX Epoch).",
86 type => 'integer',
87 minimum => 0,
88 optional => 1,
89 },
90 notes => {
91 description => "Optional notes. If they contain multiple lines, only the first one is returned here.",
92 type => 'string',
93 optional => 1,
94 },
95 encrypted => {
96 description => "If whole backup is encrypted, value is the fingerprint or '1' "
97 ." if encrypted. Only useful for the Proxmox Backup Server storage type.",
98 type => 'string',
99 optional => 1,
100 },
101 verification => {
102 description => "Last backup verification result, only useful for PBS storages.",
103 type => 'object',
104 properties => {
105 state => {
106 description => "Last backup verification state.",
107 type => 'string',
108 },
109 upid => {
110 description => "Last backup verification UPID.",
111 type => 'string',
112 },
113 },
114 optional => 1,
115 },
116 },
117 },
118 links => [ { rel => 'child', href => "{volid}" } ],
119 },
120 code => sub {
121 my ($param) = @_;
122
123 my $rpcenv = PVE::RPCEnvironment::get();
124
125 my $authuser = $rpcenv->get_user();
126
127 my $storeid = $param->{storage};
128
129 my $cfg = PVE::Storage::config();
130
131 my $vollist = PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
132
133 my $res = [];
134 foreach my $item (@$vollist) {
135 eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); };
136 next if $@;
137 $item->{vmid} = int($item->{vmid}) if (defined($item->{vmid}));
138 push @$res, $item;
139 }
140
141 return $res;
142 }});
143
144 __PACKAGE__->register_method ({
145 name => 'create',
146 path => '',
147 method => 'POST',
148 description => "Allocate disk images.",
149 permissions => {
150 check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
151 },
152 protected => 1,
153 proxyto => 'node',
154 parameters => {
155 additionalProperties => 0,
156 properties => {
157 node => get_standard_option('pve-node'),
158 storage => get_standard_option('pve-storage-id', {
159 completion => \&PVE::Storage::complete_storage_enabled,
160 }),
161 filename => {
162 description => "The name of the file to create.",
163 type => 'string',
164 },
165 vmid => get_standard_option('pve-vmid', {
166 description => "Specify owner VM",
167 completion => \&PVE::Cluster::complete_vmid,
168 }),
169 size => {
170 description => "Size in kilobyte (1024 bytes). Optional suffixes 'M' (megabyte, 1024K) and 'G' (gigabyte, 1024M)",
171 type => 'string',
172 pattern => '\d+[MG]?',
173 },
174 'format' => {
175 type => 'string',
176 enum => ['raw', 'qcow2', 'subvol'],
177 requires => 'size',
178 optional => 1,
179 },
180 },
181 },
182 returns => {
183 description => "Volume identifier",
184 type => 'string',
185 },
186 code => sub {
187 my ($param) = @_;
188
189 my $storeid = $param->{storage};
190 my $name = $param->{filename};
191 my $sizestr = $param->{size};
192
193 my $size;
194 if ($sizestr =~ m/^\d+$/) {
195 $size = $sizestr;
196 } elsif ($sizestr =~ m/^(\d+)M$/) {
197 $size = $1 * 1024;
198 } elsif ($sizestr =~ m/^(\d+)G$/) {
199 $size = $1 * 1024 * 1024;
200 } else {
201 raise_param_exc({ size => "unable to parse size '$sizestr'" });
202 }
203
204 # extract FORMAT from name
205 if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
206 my $fmt = $1;
207
208 raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" })
209 if $param->{format} && $param->{format} ne $fmt;
210
211 $param->{format} = $fmt;
212 }
213
214 my $cfg = PVE::Storage::config();
215
216 my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid},
217 $param->{format},
218 $name, $size);
219
220 return $volid;
221 }});
222
223 # we allow to pass volume names (without storage prefix) if the storage
224 # is specified as separate parameter.
225 my $real_volume_id = sub {
226 my ($storeid, $volume) = @_;
227
228 my $volid;
229
230 if ($volume =~ m/:/) {
231 eval {
232 my ($sid, $volname) = PVE::Storage::parse_volume_id ($volume);
233 die "storage ID missmatch ($sid != $storeid)\n"
234 if $storeid && $sid ne $storeid;
235 $volid = $volume;
236 $storeid = $sid;
237 };
238 raise_param_exc({ volume => $@ }) if $@;
239
240 } else {
241 raise_param_exc({ volume => "no storage speficied - incomplete volume ID" })
242 if !$storeid;
243
244 $volid = "$storeid:$volume";
245 }
246
247 return wantarray ? ($volid, $storeid) : $volid;
248 };
249
250 __PACKAGE__->register_method ({
251 name => 'info',
252 path => '{volume}',
253 method => 'GET',
254 description => "Get volume attributes",
255 permissions => {
256 description => "You need read access for the volume.",
257 user => 'all',
258 },
259 protected => 1,
260 proxyto => 'node',
261 parameters => {
262 additionalProperties => 0,
263 properties => {
264 node => get_standard_option('pve-node'),
265 storage => get_standard_option('pve-storage-id', { optional => 1 }),
266 volume => {
267 description => "Volume identifier",
268 type => 'string',
269 },
270 },
271 },
272 returns => {
273 type => 'object',
274 properties => {
275 path => {
276 description => "The Path",
277 type => 'string',
278 },
279 size => {
280 description => "Volume size in bytes.",
281 type => 'integer',
282 renderer => 'bytes',
283 },
284 used => {
285 description => "Used space. Please note that most storage plugins " .
286 "do not report anything useful here.",
287 type => 'integer',
288 renderer => 'bytes',
289 },
290 format => {
291 description => "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
292 type => 'string',
293 },
294 notes => {
295 description => "Optional notes.",
296 optional => 1,
297 type => 'string',
298 }
299 },
300 },
301 code => sub {
302 my ($param) = @_;
303
304 my $rpcenv = PVE::RPCEnvironment::get();
305 my $authuser = $rpcenv->get_user();
306
307 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
308
309 my $cfg = PVE::Storage::config();
310
311 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
312
313 my $path = PVE::Storage::path($cfg, $volid);
314 my ($size, $format, $used, $parent) = PVE::Storage::volume_size_info($cfg, $volid);
315 die "volume_size_info on '$volid' failed\n" if !($format && $size);
316
317 my $entry = {
318 path => $path,
319 size => $size,
320 used => $used,
321 format => $format,
322 };
323
324 # not all storages/types support notes, so ignore errors here
325 eval {
326 my $notes = PVE::Storage::get_volume_notes($cfg, $volid);
327 $entry->{notes} = $notes if defined($notes);
328 };
329
330 return $entry;
331 }});
332
333 __PACKAGE__->register_method ({
334 name => 'updateattributes',
335 path => '{volume}',
336 method => 'PUT',
337 description => "Update volume attributes",
338 permissions => {
339 description => "You need read access for the volume.",
340 user => 'all',
341 },
342 protected => 1,
343 proxyto => 'node',
344 parameters => {
345 additionalProperties => 0,
346 properties => {
347 node => get_standard_option('pve-node'),
348 storage => get_standard_option('pve-storage-id', { optional => 1 }),
349 volume => {
350 description => "Volume identifier",
351 type => 'string',
352 },
353 notes => {
354 description => "The new notes.",
355 type => 'string',
356 optional => 1,
357 },
358 },
359 },
360 returns => { type => 'null' },
361 code => sub {
362 my ($param) = @_;
363
364 my $rpcenv = PVE::RPCEnvironment::get();
365 my $authuser = $rpcenv->get_user();
366
367 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
368
369 my $cfg = PVE::Storage::config();
370
371 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
372
373 if (my $notes = $param->{notes}) {
374 PVE::Storage::update_volume_notes($cfg, $volid, $notes);
375 }
376
377 return undef;
378 }});
379
380 __PACKAGE__->register_method ({
381 name => 'delete',
382 path => '{volume}',
383 method => 'DELETE',
384 description => "Delete volume",
385 permissions => {
386 description => "You need 'Datastore.Allocate' privilege on the storage (or 'Datastore.AllocateSpace' for backup volumes if you have VM.Backup privilege on the VM).",
387 user => 'all',
388 },
389 protected => 1,
390 proxyto => 'node',
391 parameters => {
392 additionalProperties => 0,
393 properties => {
394 node => get_standard_option('pve-node'),
395 storage => get_standard_option('pve-storage-id', {
396 optional => 1,
397 completion => \&PVE::Storage::complete_storage,
398 }),
399 volume => {
400 description => "Volume identifier",
401 type => 'string',
402 completion => \&PVE::Storage::complete_volume,
403 },
404 delay => {
405 type => 'integer',
406 description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
407 minimum => 1,
408 maximum => 30,
409 optional => 1,
410 },
411 },
412 },
413 returns => { type => 'string', optional => 1, },
414 code => sub {
415 my ($param) = @_;
416
417 my $rpcenv = PVE::RPCEnvironment::get();
418 my $authuser = $rpcenv->get_user();
419
420 my $cfg = PVE::Storage::config();
421
422 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
423
424 my ($path, $ownervm, $vtype) = PVE::Storage::path($cfg, $volid);
425 if ($vtype eq 'backup' && $ownervm) {
426 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
427 $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
428 } else {
429 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
430 }
431
432 my $worker = sub {
433 PVE::Storage::vdisk_free ($cfg, $volid);
434 print "Removed volume '$volid'\n";
435 if ($vtype eq 'backup'
436 && $path =~ /(.*\/vzdump-\w+-\d+-\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2})[^\/]+$/) {
437 my $logpath = "$1.log";
438 # try to cleanup our backup log file too, if still exisiting, #318
439 unlink($logpath) if -e $logpath;
440 }
441 };
442
443 my $id = (defined $ownervm ? "$ownervm@" : '') . $storeid;
444 my $upid = $rpcenv->fork_worker('imgdel', $id, $authuser, $worker);
445 my $background_delay = $param->{delay};
446 if ($background_delay) {
447 my $end_time = time() + $background_delay;
448 my $currently_deleting; # not necessarily true, e.g. sequential api call from cli
449 do {
450 my $task = PVE::Tools::upid_decode($upid);
451 $currently_deleting = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
452 sleep 1 if $currently_deleting;
453 } while (time() < $end_time && $currently_deleting);
454
455 if (!$currently_deleting) {
456 my $status = PVE::Tools::upid_read_status($upid);
457 return undef if $status eq 'OK';
458 die $status;
459 }
460 }
461 return $upid;
462 }});
463
464 __PACKAGE__->register_method ({
465 name => 'copy',
466 path => '{volume}',
467 method => 'POST',
468 description => "Copy a volume. This is experimental code - do not use.",
469 protected => 1,
470 proxyto => 'node',
471 parameters => {
472 additionalProperties => 0,
473 properties => {
474 node => get_standard_option('pve-node'),
475 storage => get_standard_option('pve-storage-id', { optional => 1}),
476 volume => {
477 description => "Source volume identifier",
478 type => 'string',
479 },
480 target => {
481 description => "Target volume identifier",
482 type => 'string',
483 },
484 target_node => get_standard_option('pve-node', {
485 description => "Target node. Default is local node.",
486 optional => 1,
487 }),
488 },
489 },
490 returns => {
491 type => 'string',
492 },
493 code => sub {
494 my ($param) = @_;
495
496 my $rpcenv = PVE::RPCEnvironment::get();
497
498 my $user = $rpcenv->get_user();
499
500 my $target_node = $param->{target_node} || PVE::INotify::nodename();
501 # pvesh examples
502 # cd /nodes/localhost/storage/local/content
503 # pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
504 # pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
505
506 my $src_volid = &$real_volume_id($param->{storage}, $param->{volume});
507 my $dst_volid = &$real_volume_id($param->{storage}, $param->{target});
508
509 print "DEBUG: COPY $src_volid TO $dst_volid\n";
510
511 my $cfg = PVE::Storage::config();
512
513 # do all parameter checks first
514
515 # then do all short running task (to raise errors befor we go to background)
516
517 # then start the worker task
518 my $worker = sub {
519 my $upid = shift;
520
521 print "DEBUG: starting worker $upid\n";
522
523 my ($target_sid, $target_volname) = PVE::Storage::parse_volume_id($dst_volid);
524 #my $target_ip = PVE::Cluster::remote_node_ip($target_node);
525
526 # you need to get this working (fails currently, because storage_migrate() uses
527 # ssh to connect to local host (which is not needed
528 my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node);
529 PVE::Storage::storage_migrate($cfg, $src_volid, $sshinfo, $target_sid, {'target_volname' => $target_volname});
530
531 print "DEBUG: end worker $upid\n";
532
533 };
534
535 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
536 }});
537
538 1;