]> git.proxmox.com Git - pve-storage.git/blame - PVE/API2/Storage/Content.pm
fix #3972: Remove the .notes file when a backup is deleted
[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 $@;
7f308575
ML
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});
37ba0aea 145 push @$res, $item;
b6cf0a66
DM
146 }
147
9148f5b3 148 return $res;
b6cf0a66
DM
149 }});
150
151__PACKAGE__->register_method ({
9148f5b3 152 name => 'create',
b6cf0a66
DM
153 path => '',
154 method => 'POST',
155 description => "Allocate disk images.",
9148f5b3 156 permissions => {
5f642f73
DM
157 check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
158 },
b6cf0a66
DM
159 protected => 1,
160 proxyto => 'node',
161 parameters => {
162 additionalProperties => 0,
9148f5b3 163 properties => {
b6cf0a66 164 node => get_standard_option('pve-node'),
f7621c01
DM
165 storage => get_standard_option('pve-storage-id', {
166 completion => \&PVE::Storage::complete_storage_enabled,
167 }),
9148f5b3 168 filename => {
03f03009 169 description => "The name of the file to create.",
b6cf0a66
DM
170 type => 'string',
171 },
f7621c01
DM
172 vmid => get_standard_option('pve-vmid', {
173 description => "Specify owner VM",
174 completion => \&PVE::Cluster::complete_vmid,
175 }),
b6cf0a66
DM
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',
1ccae449 183 enum => ['raw', 'qcow2', 'subvol'],
b6cf0a66
DM
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
8e87d6ee 212 if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
b6cf0a66
DM
213 my $fmt = $1;
214
9148f5b3 215 raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" })
b6cf0a66
DM
216 if $param->{format} && $param->{format} ne $fmt;
217
218 $param->{format} = $fmt;
219 }
220
83d7192f 221 my $cfg = PVE::Storage::config();
9148f5b3
TM
222
223 my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid},
224 $param->{format},
b6cf0a66
DM
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.
232my $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);
6035a5df 240 die "storage ID mismatch ($sid != $storeid)\n"
b6cf0a66
DM
241 if $storeid && $sid ne $storeid;
242 $volid = $volume;
b755bdb0 243 $storeid = $sid;
b6cf0a66 244 };
9148f5b3
TM
245 raise_param_exc({ volume => $@ }) if $@;
246
b6cf0a66 247 } else {
ffc31266 248 raise_param_exc({ volume => "no storage specified - incomplete volume ID" })
b6cf0a66 249 if !$storeid;
9148f5b3 250
b6cf0a66
DM
251 $volid = "$storeid:$volume";
252 }
253
b755bdb0 254 return wantarray ? ($volid, $storeid) : $volid;
b6cf0a66
DM
255};
256
257__PACKAGE__->register_method ({
258 name => 'info',
259 path => '{volume}',
260 method => 'GET',
261 description => "Get volume attributes",
9148f5b3 262 permissions => {
b8744249 263 description => "You need read access for the volume.",
b755bdb0 264 user => 'all',
5f642f73 265 },
b6cf0a66
DM
266 protected => 1,
267 proxyto => 'node',
268 parameters => {
269 additionalProperties => 0,
9148f5b3 270 properties => {
b6cf0a66
DM
271 node => get_standard_option('pve-node'),
272 storage => get_standard_option('pve-storage-id', { optional => 1 }),
273 volume => {
274 description => "Volume identifier",
9148f5b3 275 type => 'string',
b6cf0a66
DM
276 },
277 },
278 },
33696518
DC
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 },
e9991d26
DC
301 notes => {
302 description => "Optional notes.",
303 optional => 1,
304 type => 'string',
56897a92
FE
305 },
306 protected => {
307 description => "Protection status. Currently only supported for backups.",
308 type => 'boolean',
309 optional => 1,
310 },
33696518
DC
311 },
312 },
b6cf0a66
DM
313 code => sub {
314 my ($param) = @_;
315
b755bdb0
DM
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
83d7192f 321 my $cfg = PVE::Storage::config();
b6cf0a66 322
ec73c0ff 323 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
b8744249 324
b6cf0a66 325 my $path = PVE::Storage::path($cfg, $volid);
33696518
DC
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);
b6cf0a66 328
e9991d26 329 my $entry = {
b6cf0a66 330 path => $path,
7f308575
ML
331 size => int($size), # cast to integer in case it was changed to a string previously
332 used => int($used),
a18f7740 333 format => $format,
b6cf0a66 334 };
e9991d26 335
56897a92
FE
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 }
e9991d26
DC
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 },
56897a92
FE
373 protected => {
374 description => "Protection status. Currently only supported for backups.",
375 type => 'boolean',
376 optional => 1,
377 },
e9991d26
DC
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
56897a92
FE
393 for my $attr (qw(notes protected)) {
394 if (exists $param->{$attr}) {
395 PVE::Storage::update_volume_attribute($cfg, $volid, $attr, $param->{$attr});
396 }
e9991d26
DC
397 }
398
399 return undef;
b6cf0a66
DM
400 }});
401
402__PACKAGE__->register_method ({
403 name => 'delete',
404 path => '{volume}',
405 method => 'DELETE',
406 description => "Delete volume",
9148f5b3 407 permissions => {
df6b79c8 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).",
b755bdb0 409 user => 'all',
5f642f73 410 },
b6cf0a66
DM
411 protected => 1,
412 proxyto => 'node',
413 parameters => {
414 additionalProperties => 0,
9148f5b3 415 properties => {
b6cf0a66 416 node => get_standard_option('pve-node'),
f3bd890d
DM
417 storage => get_standard_option('pve-storage-id', {
418 optional => 1,
419 completion => \&PVE::Storage::complete_storage,
420 }),
b6cf0a66
DM
421 volume => {
422 description => "Volume identifier",
f3bd890d
DM
423 type => 'string',
424 completion => \&PVE::Storage::complete_volume,
b6cf0a66 425 },
1f56f6f8
DJ
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 },
b6cf0a66
DM
433 },
434 },
1f56f6f8 435 returns => { type => 'string', optional => 1, },
b6cf0a66
DM
436 code => sub {
437 my ($param) = @_;
438
b755bdb0
DM
439 my $rpcenv = PVE::RPCEnvironment::get();
440 my $authuser = $rpcenv->get_user();
441
83d7192f 442 my $cfg = PVE::Storage::config();
df6b79c8 443
b755bdb0 444 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
b755bdb0 445
df6b79c8
DM
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 }
b6cf0a66 453
1f56f6f8
DJ
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})[^\/]+$/) {
c3e2ff80
DT
459 # Remove log file #318 and notes file #3972 if they still exist
460 PVE::Storage::archive_auxiliaries_remove($path);
1f56f6f8
DJ
461 }
462 };
a2a04139 463
1f56f6f8
DJ
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);
f985f33a 478 chomp $status;
01533342 479 return undef if !PVE::Tools::upid_status_is_error($status);
f985f33a 480 die "$status\n";
1f56f6f8 481 }
b1f9d990 482 }
1f56f6f8 483 return $upid;
b6cf0a66
DM
484 }});
485
883eeea6
DM
486__PACKAGE__->register_method ({
487 name => 'copy',
488 path => '{volume}',
489 method => 'POST',
5f642f73 490 description => "Copy a volume. This is experimental code - do not use.",
883eeea6
DM
491 protected => 1,
492 proxyto => 'node',
493 parameters => {
494 additionalProperties => 0,
9148f5b3 495 properties => {
883eeea6
DM
496 node => get_standard_option('pve-node'),
497 storage => get_standard_option('pve-storage-id', { optional => 1}),
498 volume => {
499 description => "Source volume identifier",
9148f5b3 500 type => 'string',
883eeea6
DM
501 },
502 target => {
503 description => "Target volume identifier",
9148f5b3 504 type => 'string',
883eeea6 505 },
9148f5b3 506 target_node => get_standard_option('pve-node', {
883eeea6
DM
507 description => "Target node. Default is local node.",
508 optional => 1,
509 }),
510 },
511 },
9148f5b3 512 returns => {
883eeea6
DM
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
83d7192f 533 my $cfg = PVE::Storage::config();
883eeea6
DM
534
535 # do all parameter checks first
536
ffc31266 537 # then do all short running task (to raise errors before we go to background)
883eeea6
DM
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
65bb9859 550 my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node);
dc3655a1 551 PVE::Storage::storage_migrate($cfg, $src_volid, $sshinfo, $target_sid, {'target_volname' => $target_volname});
883eeea6
DM
552
553 print "DEBUG: end worker $upid\n";
554
555 };
556
557 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
558 }});
559
b6cf0a66 5601;