]> git.proxmox.com Git - pve-storage.git/blob - PVE/API2/Storage/Content.pm
api: storage/content: add ctime to return schema
[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 "does not report anything useful here.",
80 type => 'integer',
81 renderer => 'bytes',
82 optional => 1,
83 },
84 ctime => {
85 description => "Creation time (Unix epoch). Currently only set for backup volumes.",
86 type => 'integer',
87 minimum => 0,
88 optional => 1,
89 },
90 },
91 },
92 links => [ { rel => 'child', href => "{volid}" } ],
93 },
94 code => sub {
95 my ($param) = @_;
96
97 my $rpcenv = PVE::RPCEnvironment::get();
98
99 my $authuser = $rpcenv->get_user();
100
101 my $storeid = $param->{storage};
102
103 my $cfg = PVE::Storage::config();
104
105 my $vollist = PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
106
107 my $res = [];
108 foreach my $item (@$vollist) {
109 eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); };
110 next if $@;
111 $item->{vmid} = int($item->{vmid}) if (defined($item->{vmid}));
112 push @$res, $item;
113 }
114
115 return $res;
116 }});
117
118 __PACKAGE__->register_method ({
119 name => 'create',
120 path => '',
121 method => 'POST',
122 description => "Allocate disk images.",
123 permissions => {
124 check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
125 },
126 protected => 1,
127 proxyto => 'node',
128 parameters => {
129 additionalProperties => 0,
130 properties => {
131 node => get_standard_option('pve-node'),
132 storage => get_standard_option('pve-storage-id', {
133 completion => \&PVE::Storage::complete_storage_enabled,
134 }),
135 filename => {
136 description => "The name of the file to create.",
137 type => 'string',
138 },
139 vmid => get_standard_option('pve-vmid', {
140 description => "Specify owner VM",
141 completion => \&PVE::Cluster::complete_vmid,
142 }),
143 size => {
144 description => "Size in kilobyte (1024 bytes). Optional suffixes 'M' (megabyte, 1024K) and 'G' (gigabyte, 1024M)",
145 type => 'string',
146 pattern => '\d+[MG]?',
147 },
148 'format' => {
149 type => 'string',
150 enum => ['raw', 'qcow2', 'subvol'],
151 requires => 'size',
152 optional => 1,
153 },
154 },
155 },
156 returns => {
157 description => "Volume identifier",
158 type => 'string',
159 },
160 code => sub {
161 my ($param) = @_;
162
163 my $storeid = $param->{storage};
164 my $name = $param->{filename};
165 my $sizestr = $param->{size};
166
167 my $size;
168 if ($sizestr =~ m/^\d+$/) {
169 $size = $sizestr;
170 } elsif ($sizestr =~ m/^(\d+)M$/) {
171 $size = $1 * 1024;
172 } elsif ($sizestr =~ m/^(\d+)G$/) {
173 $size = $1 * 1024 * 1024;
174 } else {
175 raise_param_exc({ size => "unable to parse size '$sizestr'" });
176 }
177
178 # extract FORMAT from name
179 if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
180 my $fmt = $1;
181
182 raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" })
183 if $param->{format} && $param->{format} ne $fmt;
184
185 $param->{format} = $fmt;
186 }
187
188 my $cfg = PVE::Storage::config();
189
190 my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid},
191 $param->{format},
192 $name, $size);
193
194 return $volid;
195 }});
196
197 # we allow to pass volume names (without storage prefix) if the storage
198 # is specified as separate parameter.
199 my $real_volume_id = sub {
200 my ($storeid, $volume) = @_;
201
202 my $volid;
203
204 if ($volume =~ m/:/) {
205 eval {
206 my ($sid, $volname) = PVE::Storage::parse_volume_id ($volume);
207 die "storage ID missmatch ($sid != $storeid)\n"
208 if $storeid && $sid ne $storeid;
209 $volid = $volume;
210 $storeid = $sid;
211 };
212 raise_param_exc({ volume => $@ }) if $@;
213
214 } else {
215 raise_param_exc({ volume => "no storage speficied - incomplete volume ID" })
216 if !$storeid;
217
218 $volid = "$storeid:$volume";
219 }
220
221 return wantarray ? ($volid, $storeid) : $volid;
222 };
223
224 __PACKAGE__->register_method ({
225 name => 'info',
226 path => '{volume}',
227 method => 'GET',
228 description => "Get volume attributes",
229 permissions => {
230 description => "You need read access for the volume.",
231 user => 'all',
232 },
233 protected => 1,
234 proxyto => 'node',
235 parameters => {
236 additionalProperties => 0,
237 properties => {
238 node => get_standard_option('pve-node'),
239 storage => get_standard_option('pve-storage-id', { optional => 1 }),
240 volume => {
241 description => "Volume identifier",
242 type => 'string',
243 },
244 },
245 },
246 returns => { type => 'object' },
247 code => sub {
248 my ($param) = @_;
249
250 my $rpcenv = PVE::RPCEnvironment::get();
251 my $authuser = $rpcenv->get_user();
252
253 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
254
255 my $cfg = PVE::Storage::config();
256
257 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
258
259 my $path = PVE::Storage::path($cfg, $volid);
260 my ($size, $format, $used, $parent) = PVE::Storage::file_size_info($path);
261 die "file_size_info on '$volid' failed\n" if !($format && $size);
262
263 # fixme: return more attributes?
264 return {
265 path => $path,
266 size => $size,
267 used => $used,
268 format => $format,
269 };
270 }});
271
272 __PACKAGE__->register_method ({
273 name => 'delete',
274 path => '{volume}',
275 method => 'DELETE',
276 description => "Delete volume",
277 permissions => {
278 description => "You need 'Datastore.Allocate' privilege on the storage (or 'Datastore.AllocateSpace' for backup volumes if you have VM.Backup privilege on the VM).",
279 user => 'all',
280 },
281 protected => 1,
282 proxyto => 'node',
283 parameters => {
284 additionalProperties => 0,
285 properties => {
286 node => get_standard_option('pve-node'),
287 storage => get_standard_option('pve-storage-id', {
288 optional => 1,
289 completion => \&PVE::Storage::complete_storage,
290 }),
291 volume => {
292 description => "Volume identifier",
293 type => 'string',
294 completion => \&PVE::Storage::complete_volume,
295 },
296 delay => {
297 type => 'integer',
298 description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
299 minimum => 1,
300 maximum => 30,
301 optional => 1,
302 },
303 },
304 },
305 returns => { type => 'string', optional => 1, },
306 code => sub {
307 my ($param) = @_;
308
309 my $rpcenv = PVE::RPCEnvironment::get();
310 my $authuser = $rpcenv->get_user();
311
312 my $cfg = PVE::Storage::config();
313
314 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
315
316 my ($path, $ownervm, $vtype) = PVE::Storage::path($cfg, $volid);
317 if ($vtype eq 'backup' && $ownervm) {
318 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
319 $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
320 } else {
321 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
322 }
323
324 my $worker = sub {
325 PVE::Storage::vdisk_free ($cfg, $volid);
326 print "Removed volume '$volid'\n";
327 if ($vtype eq 'backup'
328 && $path =~ /(.*\/vzdump-\w+-\d+-\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2})[^\/]+$/) {
329 my $logpath = "$1.log";
330 # try to cleanup our backup log file too, if still exisiting, #318
331 unlink($logpath) if -e $logpath;
332 }
333 };
334
335 my $id = (defined $ownervm ? "$ownervm@" : '') . $storeid;
336 my $upid = $rpcenv->fork_worker('imgdel', $id, $authuser, $worker);
337 my $background_delay = $param->{delay};
338 if ($background_delay) {
339 my $end_time = time() + $background_delay;
340 my $currently_deleting; # not necessarily true, e.g. sequential api call from cli
341 do {
342 my $task = PVE::Tools::upid_decode($upid);
343 $currently_deleting = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
344 sleep 1 if $currently_deleting;
345 } while (time() < $end_time && $currently_deleting);
346
347 if (!$currently_deleting) {
348 my $status = PVE::Tools::upid_read_status($upid);
349 return undef if $status eq 'OK';
350 die $status;
351 }
352 }
353 return $upid;
354 }});
355
356 __PACKAGE__->register_method ({
357 name => 'copy',
358 path => '{volume}',
359 method => 'POST',
360 description => "Copy a volume. This is experimental code - do not use.",
361 protected => 1,
362 proxyto => 'node',
363 parameters => {
364 additionalProperties => 0,
365 properties => {
366 node => get_standard_option('pve-node'),
367 storage => get_standard_option('pve-storage-id', { optional => 1}),
368 volume => {
369 description => "Source volume identifier",
370 type => 'string',
371 },
372 target => {
373 description => "Target volume identifier",
374 type => 'string',
375 },
376 target_node => get_standard_option('pve-node', {
377 description => "Target node. Default is local node.",
378 optional => 1,
379 }),
380 },
381 },
382 returns => {
383 type => 'string',
384 },
385 code => sub {
386 my ($param) = @_;
387
388 my $rpcenv = PVE::RPCEnvironment::get();
389
390 my $user = $rpcenv->get_user();
391
392 my $target_node = $param->{target_node} || PVE::INotify::nodename();
393 # pvesh examples
394 # cd /nodes/localhost/storage/local/content
395 # pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
396 # pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
397
398 my $src_volid = &$real_volume_id($param->{storage}, $param->{volume});
399 my $dst_volid = &$real_volume_id($param->{storage}, $param->{target});
400
401 print "DEBUG: COPY $src_volid TO $dst_volid\n";
402
403 my $cfg = PVE::Storage::config();
404
405 # do all parameter checks first
406
407 # then do all short running task (to raise errors befor we go to background)
408
409 # then start the worker task
410 my $worker = sub {
411 my $upid = shift;
412
413 print "DEBUG: starting worker $upid\n";
414
415 my ($target_sid, $target_volname) = PVE::Storage::parse_volume_id($dst_volid);
416 #my $target_ip = PVE::Cluster::remote_node_ip($target_node);
417
418 # you need to get this working (fails currently, because storage_migrate() uses
419 # ssh to connect to local host (which is not needed
420 my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node);
421 PVE::Storage::storage_migrate($cfg, $src_volid, $sshinfo, $target_sid, $target_volname);
422
423 print "DEBUG: end worker $upid\n";
424
425 };
426
427 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
428 }});
429
430 1;