]> git.proxmox.com Git - pve-storage.git/blame - PVE/API2/Storage/Content.pm
api: storage/content: add ctime to return schema
[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);
65bb9859 15use PVE::SSHInfo;
b6cf0a66
DM
16
17use base qw(PVE::RESTHandler);
18
b6cf0a66 19__PACKAGE__->register_method ({
9148f5b3 20 name => 'index',
b6cf0a66
DM
21 path => '',
22 method => 'GET',
23 description => "List storage content.",
9148f5b3 24 permissions => {
5f642f73
DM
25 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
26 },
b6cf0a66
DM
27 protected => 1,
28 proxyto => 'node',
29 parameters => {
30 additionalProperties => 0,
9148f5b3 31 properties => {
b6cf0a66 32 node => get_standard_option('pve-node'),
f3bd890d
DM
33 storage => get_standard_option('pve-storage-id', {
34 completion => \&PVE::Storage::complete_storage_enabled,
35 }),
9148f5b3 36 content => {
b6cf0a66
DM
37 description => "Only list content of this type.",
38 type => 'string', format => 'pve-storage-content',
39 optional => 1,
98437f4c 40 completion => \&PVE::Storage::complete_content_type,
b6cf0a66 41 },
f3bd890d
DM
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 }),
b6cf0a66
DM
47 },
48 },
49 returns => {
50 type => 'array',
51 items => {
52 type => "object",
9148f5b3 53 properties => {
26549428
DM
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 },
654a987a
DM
84 ctime => {
85 description => "Creation time (Unix epoch). Currently only set for backup volumes.",
86 type => 'integer',
87 minimum => 0,
88 optional => 1,
89 },
b6cf0a66
DM
90 },
91 },
92 links => [ { rel => 'child', href => "{volid}" } ],
93 },
94 code => sub {
95 my ($param) = @_;
96
b8744249
DM
97 my $rpcenv = PVE::RPCEnvironment::get();
98
99 my $authuser = $rpcenv->get_user();
100
b6cf0a66
DM
101 my $storeid = $param->{storage};
102
83d7192f 103 my $cfg = PVE::Storage::config();
b6cf0a66 104
37ba0aea 105 my $vollist = PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
b6cf0a66
DM
106
107 my $res = [];
37ba0aea 108 foreach my $item (@$vollist) {
04a13668 109 eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); };
37ba0aea 110 next if $@;
83a9960c 111 $item->{vmid} = int($item->{vmid}) if (defined($item->{vmid}));
37ba0aea 112 push @$res, $item;
b6cf0a66
DM
113 }
114
9148f5b3 115 return $res;
b6cf0a66
DM
116 }});
117
118__PACKAGE__->register_method ({
9148f5b3 119 name => 'create',
b6cf0a66
DM
120 path => '',
121 method => 'POST',
122 description => "Allocate disk images.",
9148f5b3 123 permissions => {
5f642f73
DM
124 check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
125 },
b6cf0a66
DM
126 protected => 1,
127 proxyto => 'node',
128 parameters => {
129 additionalProperties => 0,
9148f5b3 130 properties => {
b6cf0a66 131 node => get_standard_option('pve-node'),
f7621c01
DM
132 storage => get_standard_option('pve-storage-id', {
133 completion => \&PVE::Storage::complete_storage_enabled,
134 }),
9148f5b3 135 filename => {
03f03009 136 description => "The name of the file to create.",
b6cf0a66
DM
137 type => 'string',
138 },
f7621c01
DM
139 vmid => get_standard_option('pve-vmid', {
140 description => "Specify owner VM",
141 completion => \&PVE::Cluster::complete_vmid,
142 }),
b6cf0a66
DM
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',
1ccae449 150 enum => ['raw', 'qcow2', 'subvol'],
b6cf0a66
DM
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
8e87d6ee 179 if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
b6cf0a66
DM
180 my $fmt = $1;
181
9148f5b3 182 raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" })
b6cf0a66
DM
183 if $param->{format} && $param->{format} ne $fmt;
184
185 $param->{format} = $fmt;
186 }
187
83d7192f 188 my $cfg = PVE::Storage::config();
9148f5b3
TM
189
190 my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid},
191 $param->{format},
b6cf0a66
DM
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.
199my $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);
5f25af2f 207 die "storage ID missmatch ($sid != $storeid)\n"
b6cf0a66
DM
208 if $storeid && $sid ne $storeid;
209 $volid = $volume;
b755bdb0 210 $storeid = $sid;
b6cf0a66 211 };
9148f5b3
TM
212 raise_param_exc({ volume => $@ }) if $@;
213
b6cf0a66 214 } else {
9148f5b3 215 raise_param_exc({ volume => "no storage speficied - incomplete volume ID" })
b6cf0a66 216 if !$storeid;
9148f5b3 217
b6cf0a66
DM
218 $volid = "$storeid:$volume";
219 }
220
b755bdb0 221 return wantarray ? ($volid, $storeid) : $volid;
b6cf0a66
DM
222};
223
224__PACKAGE__->register_method ({
225 name => 'info',
226 path => '{volume}',
227 method => 'GET',
228 description => "Get volume attributes",
9148f5b3 229 permissions => {
b8744249 230 description => "You need read access for the volume.",
b755bdb0 231 user => 'all',
5f642f73 232 },
b6cf0a66
DM
233 protected => 1,
234 proxyto => 'node',
235 parameters => {
236 additionalProperties => 0,
9148f5b3 237 properties => {
b6cf0a66
DM
238 node => get_standard_option('pve-node'),
239 storage => get_standard_option('pve-storage-id', { optional => 1 }),
240 volume => {
241 description => "Volume identifier",
9148f5b3 242 type => 'string',
b6cf0a66
DM
243 },
244 },
245 },
246 returns => { type => 'object' },
247 code => sub {
248 my ($param) = @_;
249
b755bdb0
DM
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
83d7192f 255 my $cfg = PVE::Storage::config();
b6cf0a66 256
ec73c0ff 257 PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
b8744249 258
b6cf0a66 259 my $path = PVE::Storage::path($cfg, $volid);
a18f7740
DM
260 my ($size, $format, $used, $parent) = PVE::Storage::file_size_info($path);
261 die "file_size_info on '$volid' failed\n" if !($format && $size);
b6cf0a66
DM
262
263 # fixme: return more attributes?
264 return {
265 path => $path,
266 size => $size,
267 used => $used,
a18f7740 268 format => $format,
b6cf0a66
DM
269 };
270 }});
271
272__PACKAGE__->register_method ({
273 name => 'delete',
274 path => '{volume}',
275 method => 'DELETE',
276 description => "Delete volume",
9148f5b3 277 permissions => {
df6b79c8 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).",
b755bdb0 279 user => 'all',
5f642f73 280 },
b6cf0a66
DM
281 protected => 1,
282 proxyto => 'node',
283 parameters => {
284 additionalProperties => 0,
9148f5b3 285 properties => {
b6cf0a66 286 node => get_standard_option('pve-node'),
f3bd890d
DM
287 storage => get_standard_option('pve-storage-id', {
288 optional => 1,
289 completion => \&PVE::Storage::complete_storage,
290 }),
b6cf0a66
DM
291 volume => {
292 description => "Volume identifier",
f3bd890d
DM
293 type => 'string',
294 completion => \&PVE::Storage::complete_volume,
b6cf0a66 295 },
1f56f6f8
DJ
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 },
b6cf0a66
DM
303 },
304 },
1f56f6f8 305 returns => { type => 'string', optional => 1, },
b6cf0a66
DM
306 code => sub {
307 my ($param) = @_;
308
b755bdb0
DM
309 my $rpcenv = PVE::RPCEnvironment::get();
310 my $authuser = $rpcenv->get_user();
311
83d7192f 312 my $cfg = PVE::Storage::config();
df6b79c8 313
b755bdb0 314 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
b755bdb0 315
df6b79c8
DM
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 }
b6cf0a66 323
1f56f6f8
DJ
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 };
a2a04139 334
1f56f6f8
DJ
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 }
b1f9d990 352 }
1f56f6f8 353 return $upid;
b6cf0a66
DM
354 }});
355
883eeea6
DM
356__PACKAGE__->register_method ({
357 name => 'copy',
358 path => '{volume}',
359 method => 'POST',
5f642f73 360 description => "Copy a volume. This is experimental code - do not use.",
883eeea6
DM
361 protected => 1,
362 proxyto => 'node',
363 parameters => {
364 additionalProperties => 0,
9148f5b3 365 properties => {
883eeea6
DM
366 node => get_standard_option('pve-node'),
367 storage => get_standard_option('pve-storage-id', { optional => 1}),
368 volume => {
369 description => "Source volume identifier",
9148f5b3 370 type => 'string',
883eeea6
DM
371 },
372 target => {
373 description => "Target volume identifier",
9148f5b3 374 type => 'string',
883eeea6 375 },
9148f5b3 376 target_node => get_standard_option('pve-node', {
883eeea6
DM
377 description => "Target node. Default is local node.",
378 optional => 1,
379 }),
380 },
381 },
9148f5b3 382 returns => {
883eeea6
DM
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
83d7192f 403 my $cfg = PVE::Storage::config();
883eeea6
DM
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
65bb9859 420 my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node);
acd27197 421 PVE::Storage::storage_migrate($cfg, $src_volid, $sshinfo, $target_sid, $target_volname);
883eeea6
DM
422
423 print "DEBUG: end worker $upid\n";
424
425 };
426
427 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
428 }});
429
b6cf0a66 4301;