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