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