]> git.proxmox.com Git - pve-storage.git/blob - PVE/API2/Storage/Content.pm
api: content listing: add comment and verification fields
[pve-storage.git] / 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 comment => {
91 description => "An optional Comment.",
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 },
110 },
111 },
112 links => [ { rel => 'child', href => "{volid}" } ],
113 },
114 code => sub {
115 my ($param) = @_;
116
117 my $rpcenv = PVE::RPCEnvironment::get();
118
119 my $authuser = $rpcenv->get_user();
120
121 my $storeid = $param->{storage};
122
123 my $cfg = PVE::Storage::config();
124
125 my $vollist = PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
126
127 my $res = [];
128 foreach my $item (@$vollist) {
129 eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); };
130 next if $@;
131 $item->{vmid} = int($item->{vmid}) if (defined($item->{vmid}));
132 push @$res, $item;
133 }
134
135 return $res;
136 }});
137
138 __PACKAGE__->register_method ({
139 name => 'create',
140 path => '',
141 method => 'POST',
142 description => "Allocate disk images.",
143 permissions => {
144 check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
145 },
146 protected => 1,
147 proxyto => 'node',
148 parameters => {
149 additionalProperties => 0,
150 properties => {
151 node => get_standard_option('pve-node'),
152 storage => get_standard_option('pve-storage-id', {
153 completion => \&PVE::Storage::complete_storage_enabled,
154 }),
155 filename => {
156 description => "The name of the file to create.",
157 type => 'string',
158 },
159 vmid => get_standard_option('pve-vmid', {
160 description => "Specify owner VM",
161 completion => \&PVE::Cluster::complete_vmid,
162 }),
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',
170 enum => ['raw', 'qcow2', 'subvol'],
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
199 if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
200 my $fmt = $1;
201
202 raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" })
203 if $param->{format} && $param->{format} ne $fmt;
204
205 $param->{format} = $fmt;
206 }
207
208 my $cfg = PVE::Storage::config();
209
210 my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid},
211 $param->{format},
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.
219 my $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);
227 die "storage ID missmatch ($sid != $storeid)\n"
228 if $storeid && $sid ne $storeid;
229 $volid = $volume;
230 $storeid = $sid;
231 };
232 raise_param_exc({ volume => $@ }) if $@;
233
234 } else {
235 raise_param_exc({ volume => "no storage speficied - incomplete volume ID" })
236 if !$storeid;
237
238 $volid = "$storeid:$volume";
239 }
240
241 return wantarray ? ($volid, $storeid) : $volid;
242 };
243
244 __PACKAGE__->register_method ({
245 name => 'info',
246 path => '{volume}',
247 method => 'GET',
248 description => "Get volume attributes",
249 permissions => {
250 description => "You need read access for the volume.",
251 user => 'all',
252 },
253 protected => 1,
254 proxyto => 'node',
255 parameters => {
256 additionalProperties => 0,
257 properties => {
258 node => get_standard_option('pve-node'),
259 storage => get_standard_option('pve-storage-id', { optional => 1 }),
260 volume => {
261 description => "Volume identifier",
262 type => 'string',
263 },
264 },
265 },
266 returns => { type => 'object' },
267 code => sub {
268 my ($param) = @_;
269
270 my $rpcenv = PVE::RPCEnvironment::get();
271 my $authuser = $rpcenv->get_user();
272
273 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
274
275 my $cfg = PVE::Storage::config();
276
277 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
278
279 my $path = PVE::Storage::path($cfg, $volid);
280 my ($size, $format, $used, $parent) = PVE::Storage::file_size_info($path);
281 die "file_size_info on '$volid' failed\n" if !($format && $size);
282
283 # fixme: return more attributes?
284 return {
285 path => $path,
286 size => $size,
287 used => $used,
288 format => $format,
289 };
290 }});
291
292 __PACKAGE__->register_method ({
293 name => 'delete',
294 path => '{volume}',
295 method => 'DELETE',
296 description => "Delete volume",
297 permissions => {
298 description => "You need 'Datastore.Allocate' privilege on the storage (or 'Datastore.AllocateSpace' for backup volumes if you have VM.Backup privilege on the VM).",
299 user => 'all',
300 },
301 protected => 1,
302 proxyto => 'node',
303 parameters => {
304 additionalProperties => 0,
305 properties => {
306 node => get_standard_option('pve-node'),
307 storage => get_standard_option('pve-storage-id', {
308 optional => 1,
309 completion => \&PVE::Storage::complete_storage,
310 }),
311 volume => {
312 description => "Volume identifier",
313 type => 'string',
314 completion => \&PVE::Storage::complete_volume,
315 },
316 delay => {
317 type => 'integer',
318 description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
319 minimum => 1,
320 maximum => 30,
321 optional => 1,
322 },
323 },
324 },
325 returns => { type => 'string', optional => 1, },
326 code => sub {
327 my ($param) = @_;
328
329 my $rpcenv = PVE::RPCEnvironment::get();
330 my $authuser = $rpcenv->get_user();
331
332 my $cfg = PVE::Storage::config();
333
334 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
335
336 my ($path, $ownervm, $vtype) = PVE::Storage::path($cfg, $volid);
337 if ($vtype eq 'backup' && $ownervm) {
338 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
339 $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
340 } else {
341 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
342 }
343
344 my $worker = sub {
345 PVE::Storage::vdisk_free ($cfg, $volid);
346 print "Removed volume '$volid'\n";
347 if ($vtype eq 'backup'
348 && $path =~ /(.*\/vzdump-\w+-\d+-\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2})[^\/]+$/) {
349 my $logpath = "$1.log";
350 # try to cleanup our backup log file too, if still exisiting, #318
351 unlink($logpath) if -e $logpath;
352 }
353 };
354
355 my $id = (defined $ownervm ? "$ownervm@" : '') . $storeid;
356 my $upid = $rpcenv->fork_worker('imgdel', $id, $authuser, $worker);
357 my $background_delay = $param->{delay};
358 if ($background_delay) {
359 my $end_time = time() + $background_delay;
360 my $currently_deleting; # not necessarily true, e.g. sequential api call from cli
361 do {
362 my $task = PVE::Tools::upid_decode($upid);
363 $currently_deleting = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
364 sleep 1 if $currently_deleting;
365 } while (time() < $end_time && $currently_deleting);
366
367 if (!$currently_deleting) {
368 my $status = PVE::Tools::upid_read_status($upid);
369 return undef if $status eq 'OK';
370 die $status;
371 }
372 }
373 return $upid;
374 }});
375
376 __PACKAGE__->register_method ({
377 name => 'copy',
378 path => '{volume}',
379 method => 'POST',
380 description => "Copy a volume. This is experimental code - do not use.",
381 protected => 1,
382 proxyto => 'node',
383 parameters => {
384 additionalProperties => 0,
385 properties => {
386 node => get_standard_option('pve-node'),
387 storage => get_standard_option('pve-storage-id', { optional => 1}),
388 volume => {
389 description => "Source volume identifier",
390 type => 'string',
391 },
392 target => {
393 description => "Target volume identifier",
394 type => 'string',
395 },
396 target_node => get_standard_option('pve-node', {
397 description => "Target node. Default is local node.",
398 optional => 1,
399 }),
400 },
401 },
402 returns => {
403 type => 'string',
404 },
405 code => sub {
406 my ($param) = @_;
407
408 my $rpcenv = PVE::RPCEnvironment::get();
409
410 my $user = $rpcenv->get_user();
411
412 my $target_node = $param->{target_node} || PVE::INotify::nodename();
413 # pvesh examples
414 # cd /nodes/localhost/storage/local/content
415 # pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
416 # pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
417
418 my $src_volid = &$real_volume_id($param->{storage}, $param->{volume});
419 my $dst_volid = &$real_volume_id($param->{storage}, $param->{target});
420
421 print "DEBUG: COPY $src_volid TO $dst_volid\n";
422
423 my $cfg = PVE::Storage::config();
424
425 # do all parameter checks first
426
427 # then do all short running task (to raise errors befor we go to background)
428
429 # then start the worker task
430 my $worker = sub {
431 my $upid = shift;
432
433 print "DEBUG: starting worker $upid\n";
434
435 my ($target_sid, $target_volname) = PVE::Storage::parse_volume_id($dst_volid);
436 #my $target_ip = PVE::Cluster::remote_node_ip($target_node);
437
438 # you need to get this working (fails currently, because storage_migrate() uses
439 # ssh to connect to local host (which is not needed
440 my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node);
441 PVE::Storage::storage_migrate($cfg, $src_volid, $sshinfo, $target_sid, {'target_volname' => $target_volname});
442
443 print "DEBUG: end worker $upid\n";
444
445 };
446
447 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
448 }});
449
450 1;