]> git.proxmox.com Git - pve-storage.git/blob - src/PVE/API2/Storage/Content.pm
separate packaging and source build system
[pve-storage.git] / src / 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 protected => {
117 description => "Protection status. Currently only supported for backups.",
118 type => 'boolean',
119 optional => 1,
120 },
121 },
122 },
123 links => [ { rel => 'child', href => "{volid}" } ],
124 },
125 code => sub {
126 my ($param) = @_;
127
128 my $rpcenv = PVE::RPCEnvironment::get();
129
130 my $authuser = $rpcenv->get_user();
131
132 my $storeid = $param->{storage};
133
134 my $cfg = PVE::Storage::config();
135
136 my $vollist = PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
137
138 my $res = [];
139 foreach my $item (@$vollist) {
140 eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); };
141 next if $@;
142 $item->{vmid} = int($item->{vmid}) if defined($item->{vmid});
143 $item->{size} = int($item->{size}) if defined($item->{size});
144 $item->{used} = int($item->{used}) if defined($item->{used});
145 push @$res, $item;
146 }
147
148 return $res;
149 }});
150
151 __PACKAGE__->register_method ({
152 name => 'create',
153 path => '',
154 method => 'POST',
155 description => "Allocate disk images.",
156 permissions => {
157 check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
158 },
159 protected => 1,
160 proxyto => 'node',
161 parameters => {
162 additionalProperties => 0,
163 properties => {
164 node => get_standard_option('pve-node'),
165 storage => get_standard_option('pve-storage-id', {
166 completion => \&PVE::Storage::complete_storage_enabled,
167 }),
168 filename => {
169 description => "The name of the file to create.",
170 type => 'string',
171 },
172 vmid => get_standard_option('pve-vmid', {
173 description => "Specify owner VM",
174 completion => \&PVE::Cluster::complete_vmid,
175 }),
176 size => {
177 description => "Size in kilobyte (1024 bytes). Optional suffixes 'M' (megabyte, 1024K) and 'G' (gigabyte, 1024M)",
178 type => 'string',
179 pattern => '\d+[MG]?',
180 },
181 'format' => {
182 type => 'string',
183 enum => ['raw', 'qcow2', 'subvol'],
184 requires => 'size',
185 optional => 1,
186 },
187 },
188 },
189 returns => {
190 description => "Volume identifier",
191 type => 'string',
192 },
193 code => sub {
194 my ($param) = @_;
195
196 my $storeid = $param->{storage};
197 my $name = $param->{filename};
198 my $sizestr = $param->{size};
199
200 my $size;
201 if ($sizestr =~ m/^\d+$/) {
202 $size = $sizestr;
203 } elsif ($sizestr =~ m/^(\d+)M$/) {
204 $size = $1 * 1024;
205 } elsif ($sizestr =~ m/^(\d+)G$/) {
206 $size = $1 * 1024 * 1024;
207 } else {
208 raise_param_exc({ size => "unable to parse size '$sizestr'" });
209 }
210
211 # extract FORMAT from name
212 if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
213 my $fmt = $1;
214
215 raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" })
216 if $param->{format} && $param->{format} ne $fmt;
217
218 $param->{format} = $fmt;
219 }
220
221 my $cfg = PVE::Storage::config();
222
223 my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid},
224 $param->{format},
225 $name, $size);
226
227 return $volid;
228 }});
229
230 # we allow to pass volume names (without storage prefix) if the storage
231 # is specified as separate parameter.
232 my $real_volume_id = sub {
233 my ($storeid, $volume) = @_;
234
235 my $volid;
236
237 if ($volume =~ m/:/) {
238 eval {
239 my ($sid, $volname) = PVE::Storage::parse_volume_id ($volume);
240 die "storage ID mismatch ($sid != $storeid)\n"
241 if $storeid && $sid ne $storeid;
242 $volid = $volume;
243 $storeid = $sid;
244 };
245 raise_param_exc({ volume => $@ }) if $@;
246
247 } else {
248 raise_param_exc({ volume => "no storage specified - incomplete volume ID" })
249 if !$storeid;
250
251 $volid = "$storeid:$volume";
252 }
253
254 return wantarray ? ($volid, $storeid) : $volid;
255 };
256
257 __PACKAGE__->register_method ({
258 name => 'info',
259 path => '{volume}',
260 method => 'GET',
261 description => "Get volume attributes",
262 permissions => {
263 description => "You need read access for the volume.",
264 user => 'all',
265 },
266 protected => 1,
267 proxyto => 'node',
268 parameters => {
269 additionalProperties => 0,
270 properties => {
271 node => get_standard_option('pve-node'),
272 storage => get_standard_option('pve-storage-id', { optional => 1 }),
273 volume => {
274 description => "Volume identifier",
275 type => 'string',
276 },
277 },
278 },
279 returns => {
280 type => 'object',
281 properties => {
282 path => {
283 description => "The Path",
284 type => 'string',
285 },
286 size => {
287 description => "Volume size in bytes.",
288 type => 'integer',
289 renderer => 'bytes',
290 },
291 used => {
292 description => "Used space. Please note that most storage plugins " .
293 "do not report anything useful here.",
294 type => 'integer',
295 renderer => 'bytes',
296 },
297 format => {
298 description => "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
299 type => 'string',
300 },
301 notes => {
302 description => "Optional notes.",
303 optional => 1,
304 type => 'string',
305 },
306 protected => {
307 description => "Protection status. Currently only supported for backups.",
308 type => 'boolean',
309 optional => 1,
310 },
311 },
312 },
313 code => sub {
314 my ($param) = @_;
315
316 my $rpcenv = PVE::RPCEnvironment::get();
317 my $authuser = $rpcenv->get_user();
318
319 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
320
321 my $cfg = PVE::Storage::config();
322
323 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
324
325 my $path = PVE::Storage::path($cfg, $volid);
326 my ($size, $format, $used, $parent) = PVE::Storage::volume_size_info($cfg, $volid);
327 die "volume_size_info on '$volid' failed\n" if !($format && $size);
328
329 my $entry = {
330 path => $path,
331 size => int($size), # cast to integer in case it was changed to a string previously
332 used => int($used),
333 format => $format,
334 };
335
336 for my $attribute (qw(notes protected)) {
337 # keep going if fetching an optional attribute fails
338 eval {
339 my $value = PVE::Storage::get_volume_attribute($cfg, $volid, $attribute);
340 $entry->{$attribute} = $value if defined($value);
341 };
342 warn $@ if $@;
343 }
344
345 return $entry;
346 }});
347
348 __PACKAGE__->register_method ({
349 name => 'updateattributes',
350 path => '{volume}',
351 method => 'PUT',
352 description => "Update volume attributes",
353 permissions => {
354 description => "You need read access for the volume.",
355 user => 'all',
356 },
357 protected => 1,
358 proxyto => 'node',
359 parameters => {
360 additionalProperties => 0,
361 properties => {
362 node => get_standard_option('pve-node'),
363 storage => get_standard_option('pve-storage-id', { optional => 1 }),
364 volume => {
365 description => "Volume identifier",
366 type => 'string',
367 },
368 notes => {
369 description => "The new notes.",
370 type => 'string',
371 optional => 1,
372 },
373 protected => {
374 description => "Protection status. Currently only supported for backups.",
375 type => 'boolean',
376 optional => 1,
377 },
378 },
379 },
380 returns => { type => 'null' },
381 code => sub {
382 my ($param) = @_;
383
384 my $rpcenv = PVE::RPCEnvironment::get();
385 my $authuser = $rpcenv->get_user();
386
387 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
388
389 my $cfg = PVE::Storage::config();
390
391 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
392
393 for my $attr (qw(notes protected)) {
394 if (exists $param->{$attr}) {
395 PVE::Storage::update_volume_attribute($cfg, $volid, $attr, $param->{$attr});
396 }
397 }
398
399 return undef;
400 }});
401
402 __PACKAGE__->register_method ({
403 name => 'delete',
404 path => '{volume}',
405 method => 'DELETE',
406 description => "Delete volume",
407 permissions => {
408 description => "You need 'Datastore.Allocate' privilege on the storage (or 'Datastore.AllocateSpace' for backup volumes if you have VM.Backup privilege on the VM).",
409 user => 'all',
410 },
411 protected => 1,
412 proxyto => 'node',
413 parameters => {
414 additionalProperties => 0,
415 properties => {
416 node => get_standard_option('pve-node'),
417 storage => get_standard_option('pve-storage-id', {
418 optional => 1,
419 completion => \&PVE::Storage::complete_storage,
420 }),
421 volume => {
422 description => "Volume identifier",
423 type => 'string',
424 completion => \&PVE::Storage::complete_volume,
425 },
426 delay => {
427 type => 'integer',
428 description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
429 minimum => 1,
430 maximum => 30,
431 optional => 1,
432 },
433 },
434 },
435 returns => { type => 'string', optional => 1, },
436 code => sub {
437 my ($param) = @_;
438
439 my $rpcenv = PVE::RPCEnvironment::get();
440 my $authuser = $rpcenv->get_user();
441
442 my $cfg = PVE::Storage::config();
443
444 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
445
446 my ($path, $ownervm, $vtype) = PVE::Storage::path($cfg, $volid);
447 if ($vtype eq 'backup' && $ownervm) {
448 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
449 $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
450 } else {
451 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
452 }
453
454 my $worker = sub {
455 PVE::Storage::vdisk_free ($cfg, $volid);
456 print "Removed volume '$volid'\n";
457 if ($vtype eq 'backup'
458 && $path =~ /(.*\/vzdump-\w+-\d+-\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2})[^\/]+$/) {
459 # Remove log file #318 and notes file #3972 if they still exist
460 PVE::Storage::archive_auxiliaries_remove($path);
461 }
462 };
463
464 my $id = (defined $ownervm ? "$ownervm@" : '') . $storeid;
465 my $upid = $rpcenv->fork_worker('imgdel', $id, $authuser, $worker);
466 my $background_delay = $param->{delay};
467 if ($background_delay) {
468 my $end_time = time() + $background_delay;
469 my $currently_deleting; # not necessarily true, e.g. sequential api call from cli
470 do {
471 my $task = PVE::Tools::upid_decode($upid);
472 $currently_deleting = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
473 sleep 1 if $currently_deleting;
474 } while (time() < $end_time && $currently_deleting);
475
476 if (!$currently_deleting) {
477 my $status = PVE::Tools::upid_read_status($upid);
478 chomp $status;
479 return undef if !PVE::Tools::upid_status_is_error($status);
480 die "$status\n";
481 }
482 }
483 return $upid;
484 }});
485
486 __PACKAGE__->register_method ({
487 name => 'copy',
488 path => '{volume}',
489 method => 'POST',
490 description => "Copy a volume. This is experimental code - do not use.",
491 protected => 1,
492 proxyto => 'node',
493 parameters => {
494 additionalProperties => 0,
495 properties => {
496 node => get_standard_option('pve-node'),
497 storage => get_standard_option('pve-storage-id', { optional => 1}),
498 volume => {
499 description => "Source volume identifier",
500 type => 'string',
501 },
502 target => {
503 description => "Target volume identifier",
504 type => 'string',
505 },
506 target_node => get_standard_option('pve-node', {
507 description => "Target node. Default is local node.",
508 optional => 1,
509 }),
510 },
511 },
512 returns => {
513 type => 'string',
514 },
515 code => sub {
516 my ($param) = @_;
517
518 my $rpcenv = PVE::RPCEnvironment::get();
519
520 my $user = $rpcenv->get_user();
521
522 my $target_node = $param->{target_node} || PVE::INotify::nodename();
523 # pvesh examples
524 # cd /nodes/localhost/storage/local/content
525 # pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
526 # pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
527
528 my $src_volid = &$real_volume_id($param->{storage}, $param->{volume});
529 my $dst_volid = &$real_volume_id($param->{storage}, $param->{target});
530
531 print "DEBUG: COPY $src_volid TO $dst_volid\n";
532
533 my $cfg = PVE::Storage::config();
534
535 # do all parameter checks first
536
537 # then do all short running task (to raise errors before we go to background)
538
539 # then start the worker task
540 my $worker = sub {
541 my $upid = shift;
542
543 print "DEBUG: starting worker $upid\n";
544
545 my ($target_sid, $target_volname) = PVE::Storage::parse_volume_id($dst_volid);
546 #my $target_ip = PVE::Cluster::remote_node_ip($target_node);
547
548 # you need to get this working (fails currently, because storage_migrate() uses
549 # ssh to connect to local host (which is not needed
550 my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node);
551 PVE::Storage::storage_migrate($cfg, $src_volid, $sshinfo, $target_sid, {'target_volname' => $target_volname});
552
553 print "DEBUG: end worker $upid\n";
554
555 };
556
557 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
558 }});
559
560 1;