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