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