]> git.proxmox.com Git - pve-storage.git/blame - PVE/API2/Storage/Content.pm
add generalized functions to manage volume attributes
[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 },
b6cf0a66
DM
116 },
117 },
118 links => [ { rel => 'child', href => "{volid}" } ],
119 },
120 code => sub {
121 my ($param) = @_;
122
b8744249
DM
123 my $rpcenv = PVE::RPCEnvironment::get();
124
125 my $authuser = $rpcenv->get_user();
126
b6cf0a66
DM
127 my $storeid = $param->{storage};
128
83d7192f 129 my $cfg = PVE::Storage::config();
b6cf0a66 130
37ba0aea 131 my $vollist = PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
b6cf0a66
DM
132
133 my $res = [];
37ba0aea 134 foreach my $item (@$vollist) {
04a13668 135 eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); };
37ba0aea 136 next if $@;
83a9960c 137 $item->{vmid} = int($item->{vmid}) if (defined($item->{vmid}));
37ba0aea 138 push @$res, $item;
b6cf0a66
DM
139 }
140
9148f5b3 141 return $res;
b6cf0a66
DM
142 }});
143
144__PACKAGE__->register_method ({
9148f5b3 145 name => 'create',
b6cf0a66
DM
146 path => '',
147 method => 'POST',
148 description => "Allocate disk images.",
9148f5b3 149 permissions => {
5f642f73
DM
150 check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
151 },
b6cf0a66
DM
152 protected => 1,
153 proxyto => 'node',
154 parameters => {
155 additionalProperties => 0,
9148f5b3 156 properties => {
b6cf0a66 157 node => get_standard_option('pve-node'),
f7621c01
DM
158 storage => get_standard_option('pve-storage-id', {
159 completion => \&PVE::Storage::complete_storage_enabled,
160 }),
9148f5b3 161 filename => {
03f03009 162 description => "The name of the file to create.",
b6cf0a66
DM
163 type => 'string',
164 },
f7621c01
DM
165 vmid => get_standard_option('pve-vmid', {
166 description => "Specify owner VM",
167 completion => \&PVE::Cluster::complete_vmid,
168 }),
b6cf0a66
DM
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',
1ccae449 176 enum => ['raw', 'qcow2', 'subvol'],
b6cf0a66
DM
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
8e87d6ee 205 if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
b6cf0a66
DM
206 my $fmt = $1;
207
9148f5b3 208 raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" })
b6cf0a66
DM
209 if $param->{format} && $param->{format} ne $fmt;
210
211 $param->{format} = $fmt;
212 }
213
83d7192f 214 my $cfg = PVE::Storage::config();
9148f5b3
TM
215
216 my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid},
217 $param->{format},
b6cf0a66
DM
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.
225my $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);
6035a5df 233 die "storage ID mismatch ($sid != $storeid)\n"
b6cf0a66
DM
234 if $storeid && $sid ne $storeid;
235 $volid = $volume;
b755bdb0 236 $storeid = $sid;
b6cf0a66 237 };
9148f5b3
TM
238 raise_param_exc({ volume => $@ }) if $@;
239
b6cf0a66 240 } else {
ffc31266 241 raise_param_exc({ volume => "no storage specified - incomplete volume ID" })
b6cf0a66 242 if !$storeid;
9148f5b3 243
b6cf0a66
DM
244 $volid = "$storeid:$volume";
245 }
246
b755bdb0 247 return wantarray ? ($volid, $storeid) : $volid;
b6cf0a66
DM
248};
249
250__PACKAGE__->register_method ({
251 name => 'info',
252 path => '{volume}',
253 method => 'GET',
254 description => "Get volume attributes",
9148f5b3 255 permissions => {
b8744249 256 description => "You need read access for the volume.",
b755bdb0 257 user => 'all',
5f642f73 258 },
b6cf0a66
DM
259 protected => 1,
260 proxyto => 'node',
261 parameters => {
262 additionalProperties => 0,
9148f5b3 263 properties => {
b6cf0a66
DM
264 node => get_standard_option('pve-node'),
265 storage => get_standard_option('pve-storage-id', { optional => 1 }),
266 volume => {
267 description => "Volume identifier",
9148f5b3 268 type => 'string',
b6cf0a66
DM
269 },
270 },
271 },
33696518
DC
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 },
e9991d26
DC
294 notes => {
295 description => "Optional notes.",
296 optional => 1,
297 type => 'string',
298 }
33696518
DC
299 },
300 },
b6cf0a66
DM
301 code => sub {
302 my ($param) = @_;
303
b755bdb0
DM
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
83d7192f 309 my $cfg = PVE::Storage::config();
b6cf0a66 310
ec73c0ff 311 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
b8744249 312
b6cf0a66 313 my $path = PVE::Storage::path($cfg, $volid);
33696518
DC
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);
b6cf0a66 316
e9991d26 317 my $entry = {
b6cf0a66
DM
318 path => $path,
319 size => $size,
320 used => $used,
a18f7740 321 format => $format,
b6cf0a66 322 };
e9991d26 323
f1de8281 324 # keep going if fetching an optional attribute fails
e9991d26 325 eval {
f1de8281 326 my $notes = PVE::Storage::get_volume_attribute($cfg, $volid, 'notes');
e9991d26
DC
327 $entry->{notes} = $notes if defined($notes);
328 };
f1de8281 329 warn $@ if $@;
e9991d26
DC
330
331 return $entry;
332 }});
333
334__PACKAGE__->register_method ({
335 name => 'updateattributes',
336 path => '{volume}',
337 method => 'PUT',
338 description => "Update volume attributes",
339 permissions => {
340 description => "You need read access for the volume.",
341 user => 'all',
342 },
343 protected => 1,
344 proxyto => 'node',
345 parameters => {
346 additionalProperties => 0,
347 properties => {
348 node => get_standard_option('pve-node'),
349 storage => get_standard_option('pve-storage-id', { optional => 1 }),
350 volume => {
351 description => "Volume identifier",
352 type => 'string',
353 },
354 notes => {
355 description => "The new notes.",
356 type => 'string',
357 optional => 1,
358 },
359 },
360 },
361 returns => { type => 'null' },
362 code => sub {
363 my ($param) = @_;
364
365 my $rpcenv = PVE::RPCEnvironment::get();
366 my $authuser = $rpcenv->get_user();
367
368 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
369
370 my $cfg = PVE::Storage::config();
371
372 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
373
f244e2aa 374 if (exists $param->{notes}) {
f1de8281 375 PVE::Storage::update_volume_attribute($cfg, $volid, 'notes', $param->{notes});
e9991d26
DC
376 }
377
378 return undef;
b6cf0a66
DM
379 }});
380
381__PACKAGE__->register_method ({
382 name => 'delete',
383 path => '{volume}',
384 method => 'DELETE',
385 description => "Delete volume",
9148f5b3 386 permissions => {
df6b79c8 387 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 388 user => 'all',
5f642f73 389 },
b6cf0a66
DM
390 protected => 1,
391 proxyto => 'node',
392 parameters => {
393 additionalProperties => 0,
9148f5b3 394 properties => {
b6cf0a66 395 node => get_standard_option('pve-node'),
f3bd890d
DM
396 storage => get_standard_option('pve-storage-id', {
397 optional => 1,
398 completion => \&PVE::Storage::complete_storage,
399 }),
b6cf0a66
DM
400 volume => {
401 description => "Volume identifier",
f3bd890d
DM
402 type => 'string',
403 completion => \&PVE::Storage::complete_volume,
b6cf0a66 404 },
1f56f6f8
DJ
405 delay => {
406 type => 'integer',
407 description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
408 minimum => 1,
409 maximum => 30,
410 optional => 1,
411 },
b6cf0a66
DM
412 },
413 },
1f56f6f8 414 returns => { type => 'string', optional => 1, },
b6cf0a66
DM
415 code => sub {
416 my ($param) = @_;
417
b755bdb0
DM
418 my $rpcenv = PVE::RPCEnvironment::get();
419 my $authuser = $rpcenv->get_user();
420
83d7192f 421 my $cfg = PVE::Storage::config();
df6b79c8 422
b755bdb0 423 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
b755bdb0 424
df6b79c8
DM
425 my ($path, $ownervm, $vtype) = PVE::Storage::path($cfg, $volid);
426 if ($vtype eq 'backup' && $ownervm) {
427 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
428 $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
429 } else {
430 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
431 }
b6cf0a66 432
1f56f6f8
DJ
433 my $worker = sub {
434 PVE::Storage::vdisk_free ($cfg, $volid);
435 print "Removed volume '$volid'\n";
436 if ($vtype eq 'backup'
437 && $path =~ /(.*\/vzdump-\w+-\d+-\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2})[^\/]+$/) {
438 my $logpath = "$1.log";
ffc31266 439 # try to cleanup our backup log file too, if still existing, #318
1f56f6f8
DJ
440 unlink($logpath) if -e $logpath;
441 }
442 };
a2a04139 443
1f56f6f8
DJ
444 my $id = (defined $ownervm ? "$ownervm@" : '') . $storeid;
445 my $upid = $rpcenv->fork_worker('imgdel', $id, $authuser, $worker);
446 my $background_delay = $param->{delay};
447 if ($background_delay) {
448 my $end_time = time() + $background_delay;
449 my $currently_deleting; # not necessarily true, e.g. sequential api call from cli
450 do {
451 my $task = PVE::Tools::upid_decode($upid);
452 $currently_deleting = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
453 sleep 1 if $currently_deleting;
454 } while (time() < $end_time && $currently_deleting);
455
456 if (!$currently_deleting) {
457 my $status = PVE::Tools::upid_read_status($upid);
f985f33a 458 chomp $status;
01533342 459 return undef if !PVE::Tools::upid_status_is_error($status);
f985f33a 460 die "$status\n";
1f56f6f8 461 }
b1f9d990 462 }
1f56f6f8 463 return $upid;
b6cf0a66
DM
464 }});
465
883eeea6
DM
466__PACKAGE__->register_method ({
467 name => 'copy',
468 path => '{volume}',
469 method => 'POST',
5f642f73 470 description => "Copy a volume. This is experimental code - do not use.",
883eeea6
DM
471 protected => 1,
472 proxyto => 'node',
473 parameters => {
474 additionalProperties => 0,
9148f5b3 475 properties => {
883eeea6
DM
476 node => get_standard_option('pve-node'),
477 storage => get_standard_option('pve-storage-id', { optional => 1}),
478 volume => {
479 description => "Source volume identifier",
9148f5b3 480 type => 'string',
883eeea6
DM
481 },
482 target => {
483 description => "Target volume identifier",
9148f5b3 484 type => 'string',
883eeea6 485 },
9148f5b3 486 target_node => get_standard_option('pve-node', {
883eeea6
DM
487 description => "Target node. Default is local node.",
488 optional => 1,
489 }),
490 },
491 },
9148f5b3 492 returns => {
883eeea6
DM
493 type => 'string',
494 },
495 code => sub {
496 my ($param) = @_;
497
498 my $rpcenv = PVE::RPCEnvironment::get();
499
500 my $user = $rpcenv->get_user();
501
502 my $target_node = $param->{target_node} || PVE::INotify::nodename();
503 # pvesh examples
504 # cd /nodes/localhost/storage/local/content
505 # pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
506 # pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
507
508 my $src_volid = &$real_volume_id($param->{storage}, $param->{volume});
509 my $dst_volid = &$real_volume_id($param->{storage}, $param->{target});
510
511 print "DEBUG: COPY $src_volid TO $dst_volid\n";
512
83d7192f 513 my $cfg = PVE::Storage::config();
883eeea6
DM
514
515 # do all parameter checks first
516
ffc31266 517 # then do all short running task (to raise errors before we go to background)
883eeea6
DM
518
519 # then start the worker task
520 my $worker = sub {
521 my $upid = shift;
522
523 print "DEBUG: starting worker $upid\n";
524
525 my ($target_sid, $target_volname) = PVE::Storage::parse_volume_id($dst_volid);
526 #my $target_ip = PVE::Cluster::remote_node_ip($target_node);
527
528 # you need to get this working (fails currently, because storage_migrate() uses
529 # ssh to connect to local host (which is not needed
65bb9859 530 my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node);
dc3655a1 531 PVE::Storage::storage_migrate($cfg, $src_volid, $sshinfo, $target_sid, {'target_volname' => $target_volname});
883eeea6
DM
532
533 print "DEBUG: end worker $upid\n";
534
535 };
536
537 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
538 }});
539
b6cf0a66 5401;