]> git.proxmox.com Git - pve-storage.git/blob - PVE/API2/Storage/Content.pm
volume_list: moved code from PVE::API2::Storage::Content
[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 qw(cfs_read_file);
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 content => {
34 description => "Only list content of this type.",
35 type => 'string', format => 'pve-storage-content',
36 optional => 1,
37 },
38 vmid => get_standard_option
39 ('pve-vmid', {
40 description => "Only list images for this VM",
41 optional => 1,
42 }),
43 },
44 },
45 returns => {
46 type => 'array',
47 items => {
48 type => "object",
49 properties => {
50 volid => {
51 type => 'string'
52 }
53 },
54 },
55 links => [ { rel => 'child', href => "{volid}" } ],
56 },
57 code => sub {
58 my ($param) = @_;
59
60 my $rpcenv = PVE::RPCEnvironment::get();
61
62 my $authuser = $rpcenv->get_user();
63
64 my $storeid = $param->{storage};
65
66 my $cfg = cfs_read_file("storage.cfg");
67
68 my $vollist = PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
69
70 my $res = [];
71 foreach my $item (@$vollist) {
72 eval { $rpcenv->check_volume_access($authuser, $cfg, undef, $item->{volid}); };
73 next if $@;
74 push @$res, $item;
75 }
76
77 return $res;
78 }});
79
80 __PACKAGE__->register_method ({
81 name => 'create',
82 path => '',
83 method => 'POST',
84 description => "Allocate disk images.",
85 permissions => {
86 check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
87 },
88 protected => 1,
89 proxyto => 'node',
90 parameters => {
91 additionalProperties => 0,
92 properties => {
93 node => get_standard_option('pve-node'),
94 storage => get_standard_option('pve-storage-id', {
95 completion => \&PVE::Storage::complete_storage_enabled,
96 }),
97 filename => {
98 description => "The name of the file to create.",
99 type => 'string',
100 },
101 vmid => get_standard_option('pve-vmid', {
102 description => "Specify owner VM",
103 completion => \&PVE::Cluster::complete_vmid,
104 }),
105 size => {
106 description => "Size in kilobyte (1024 bytes). Optional suffixes 'M' (megabyte, 1024K) and 'G' (gigabyte, 1024M)",
107 type => 'string',
108 pattern => '\d+[MG]?',
109 },
110 'format' => {
111 type => 'string',
112 enum => ['raw', 'qcow2', 'subvol'],
113 requires => 'size',
114 optional => 1,
115 },
116 },
117 },
118 returns => {
119 description => "Volume identifier",
120 type => 'string',
121 },
122 code => sub {
123 my ($param) = @_;
124
125 my $storeid = $param->{storage};
126 my $name = $param->{filename};
127 my $sizestr = $param->{size};
128
129 my $size;
130 if ($sizestr =~ m/^\d+$/) {
131 $size = $sizestr;
132 } elsif ($sizestr =~ m/^(\d+)M$/) {
133 $size = $1 * 1024;
134 } elsif ($sizestr =~ m/^(\d+)G$/) {
135 $size = $1 * 1024 * 1024;
136 } else {
137 raise_param_exc({ size => "unable to parse size '$sizestr'" });
138 }
139
140 # extract FORMAT from name
141 if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
142 my $fmt = $1;
143
144 raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" })
145 if $param->{format} && $param->{format} ne $fmt;
146
147 $param->{format} = $fmt;
148 }
149
150 my $cfg = cfs_read_file('storage.cfg');
151
152 my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid},
153 $param->{format},
154 $name, $size);
155
156 return $volid;
157 }});
158
159 # we allow to pass volume names (without storage prefix) if the storage
160 # is specified as separate parameter.
161 my $real_volume_id = sub {
162 my ($storeid, $volume) = @_;
163
164 my $volid;
165
166 if ($volume =~ m/:/) {
167 eval {
168 my ($sid, $volname) = PVE::Storage::parse_volume_id ($volume);
169 die "storage ID missmatch ($sid != $storeid)\n"
170 if $storeid && $sid ne $storeid;
171 $volid = $volume;
172 $storeid = $sid;
173 };
174 raise_param_exc({ volume => $@ }) if $@;
175
176 } else {
177 raise_param_exc({ volume => "no storage speficied - incomplete volume ID" })
178 if !$storeid;
179
180 $volid = "$storeid:$volume";
181 }
182
183 return wantarray ? ($volid, $storeid) : $volid;
184 };
185
186 __PACKAGE__->register_method ({
187 name => 'info',
188 path => '{volume}',
189 method => 'GET',
190 description => "Get volume attributes",
191 permissions => {
192 description => "You need read access for the volume.",
193 user => 'all',
194 },
195 protected => 1,
196 proxyto => 'node',
197 parameters => {
198 additionalProperties => 0,
199 properties => {
200 node => get_standard_option('pve-node'),
201 storage => get_standard_option('pve-storage-id', { optional => 1 }),
202 volume => {
203 description => "Volume identifier",
204 type => 'string',
205 },
206 },
207 },
208 returns => { type => 'object' },
209 code => sub {
210 my ($param) = @_;
211
212 my $rpcenv = PVE::RPCEnvironment::get();
213 my $authuser = $rpcenv->get_user();
214
215 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
216
217 my $cfg = cfs_read_file('storage.cfg');
218
219 $rpcenv->check_volume_access($authuser, $cfg, undef, $volid);
220
221 my $path = PVE::Storage::path($cfg, $volid);
222 my ($size, $format, $used, $parent) = PVE::Storage::file_size_info($path);
223 die "file_size_info on '$volid' failed\n" if !($format && $size);
224
225 # fixme: return more attributes?
226 return {
227 path => $path,
228 size => $size,
229 used => $used,
230 format => $format,
231 };
232 }});
233
234 __PACKAGE__->register_method ({
235 name => 'delete',
236 path => '{volume}',
237 method => 'DELETE',
238 description => "Delete volume",
239 permissions => {
240 description => "You need 'Datastore.Allocate' privilege on the storage (or 'Datastore.AllocateSpace' for backup volumes if you have VM.Backup privilege on the VM).",
241 user => 'all',
242 },
243 protected => 1,
244 proxyto => 'node',
245 parameters => {
246 additionalProperties => 0,
247 properties => {
248 node => get_standard_option('pve-node'),
249 storage => get_standard_option('pve-storage-id', { optional => 1}),
250 volume => {
251 description => "Volume identifier",
252 type => 'string',
253 },
254 },
255 },
256 returns => { type => 'null' },
257 code => sub {
258 my ($param) = @_;
259
260 my $rpcenv = PVE::RPCEnvironment::get();
261 my $authuser = $rpcenv->get_user();
262
263 my $cfg = cfs_read_file('storage.cfg');
264
265 my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
266
267 my ($path, $ownervm, $vtype) = PVE::Storage::path($cfg, $volid);
268 if ($vtype eq 'backup' && $ownervm) {
269 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
270 $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
271 } else {
272 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
273 }
274
275 PVE::Storage::vdisk_free ($cfg, $volid);
276
277 return undef;
278 }});
279
280 __PACKAGE__->register_method ({
281 name => 'copy',
282 path => '{volume}',
283 method => 'POST',
284 description => "Copy a volume. This is experimental code - do not use.",
285 protected => 1,
286 proxyto => 'node',
287 parameters => {
288 additionalProperties => 0,
289 properties => {
290 node => get_standard_option('pve-node'),
291 storage => get_standard_option('pve-storage-id', { optional => 1}),
292 volume => {
293 description => "Source volume identifier",
294 type => 'string',
295 },
296 target => {
297 description => "Target volume identifier",
298 type => 'string',
299 },
300 target_node => get_standard_option('pve-node', {
301 description => "Target node. Default is local node.",
302 optional => 1,
303 }),
304 },
305 },
306 returns => {
307 type => 'string',
308 },
309 code => sub {
310 my ($param) = @_;
311
312 my $rpcenv = PVE::RPCEnvironment::get();
313
314 my $user = $rpcenv->get_user();
315
316 my $target_node = $param->{target_node} || PVE::INotify::nodename();
317 # pvesh examples
318 # cd /nodes/localhost/storage/local/content
319 # pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
320 # pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
321
322 my $src_volid = &$real_volume_id($param->{storage}, $param->{volume});
323 my $dst_volid = &$real_volume_id($param->{storage}, $param->{target});
324
325 print "DEBUG: COPY $src_volid TO $dst_volid\n";
326
327 my $cfg = cfs_read_file('storage.cfg');
328
329 # do all parameter checks first
330
331 # then do all short running task (to raise errors befor we go to background)
332
333 # then start the worker task
334 my $worker = sub {
335 my $upid = shift;
336
337 print "DEBUG: starting worker $upid\n";
338
339 my ($target_sid, $target_volname) = PVE::Storage::parse_volume_id($dst_volid);
340 #my $target_ip = PVE::Cluster::remote_node_ip($target_node);
341
342 # you need to get this working (fails currently, because storage_migrate() uses
343 # ssh to connect to local host (which is not needed
344 PVE::Storage::storage_migrate($cfg, $src_volid, $target_node, $target_sid, $target_volname);
345
346 print "DEBUG: end worker $upid\n";
347
348 };
349
350 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
351 }});
352
353 1;