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