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