]> git.proxmox.com Git - pve-storage.git/blob - src/PVE/API2/Storage/Config.pm
fix #4785: avoid overly noisy mkdir deprecation warning for now
[pve-storage.git] / src / PVE / API2 / Storage / Config.pm
1 package PVE::API2::Storage::Config;
2
3 use strict;
4 use warnings;
5
6 use PVE::SafeSyslog;
7 use PVE::Tools qw(extract_param extract_sensitive_params);
8 use PVE::Cluster qw(cfs_read_file cfs_write_file);
9 use PVE::Storage;
10 use PVE::Storage::Plugin;
11 use PVE::Storage::LVMPlugin;
12 use PVE::Storage::CIFSPlugin;
13 use HTTP::Status qw(:constants);
14 use Storable qw(dclone);
15 use PVE::JSONSchema qw(get_standard_option);
16 use PVE::RPCEnvironment;
17
18 use PVE::RESTHandler;
19
20 use base qw(PVE::RESTHandler);
21
22 my @ctypes = qw(images vztmpl iso backup);
23
24 my $storage_type_enum = PVE::Storage::Plugin->lookup_types();
25
26 my $api_storage_config = sub {
27 my ($cfg, $storeid) = @_;
28
29 my $scfg = dclone(PVE::Storage::storage_config($cfg, $storeid));
30 $scfg->{storage} = $storeid;
31 $scfg->{digest} = $cfg->{digest};
32 $scfg->{content} = PVE::Storage::Plugin->encode_value($scfg->{type}, 'content', $scfg->{content});
33
34 if ($scfg->{nodes}) {
35 $scfg->{nodes} = PVE::Storage::Plugin->encode_value($scfg->{type}, 'nodes', $scfg->{nodes});
36 }
37
38 return $scfg;
39 };
40
41 # For storages that $match->($scfg), update node restrictions to not include $node anymore and
42 # in case no node remains, remove the storage altogether.
43 sub cleanup_storages_for_node {
44 my ($self, $match, $node) = @_;
45
46 my $config = PVE::Storage::config();
47 my $cluster_nodes = PVE::Cluster::get_nodelist();
48
49 for my $storeid (keys $config->{ids}->%*) {
50 my $scfg = PVE::Storage::storage_config($config, $storeid);
51 next if !$match->($scfg);
52
53 my $nodes = $scfg->{nodes} || { map { $_ => 1 } $cluster_nodes->@* };
54 next if !$nodes->{$node}; # not configured on $node, so nothing to do
55 delete $nodes->{$node};
56
57 if (scalar(keys $nodes->%*) > 0) {
58 $self->update({
59 nodes => join(',', sort keys $nodes->%*),
60 storage => $storeid,
61 });
62 } else {
63 $self->delete({storage => $storeid});
64 }
65 }
66 }
67
68 # Decides if a storage needs to be created or updated. An update is needed, if
69 # the storage has a node list configured, then the current node will be added.
70 # The verify_params parameter is an array of parameter names that need to match
71 # if there already is a storage config of the same name present. This is
72 # mainly intended for local storage types as certain parameters need to be the
73 # same. For exmaple 'pool' for ZFS, 'vg_name' for LVM, ...
74 # Set the dryrun parameter, to only verify the parameters without updating or
75 # creating the storage.
76 sub create_or_update {
77 my ($self, $sid, $node, $storage_params, $verify_params, $dryrun) = @_;
78
79 my $cfg = PVE::Storage::config();
80 my $scfg = PVE::Storage::storage_config($cfg, $sid, 1);
81
82 if ($scfg) {
83 die "storage config for '${sid}' exists but no parameters to verify were provided\n"
84 if !$verify_params;
85
86 $node = PVE::INotify::nodename() if !$node || ($node eq 'localhost');
87 die "Storage ID '${sid}' already exists on node ${node}\n"
88 if !defined($scfg->{nodes}) || $scfg->{nodes}->{$node};
89
90 # check for type mismatch first to get a clear error
91 for my $key ('type', $verify_params->@*) {
92 if (!defined($scfg->{$key})) {
93 die "Option '${key}' is not configured for storage '$sid', "
94 ."expected it to be '$storage_params->{$key}'";
95 }
96 if ($storage_params->{$key} ne $scfg->{$key}) {
97 die "Option '${key}' ($storage_params->{$key}) does not match "
98 ."existing storage configuration '$scfg->{$key}'\n";
99 }
100 }
101 }
102
103 if (!$dryrun) {
104 if ($scfg) {
105 if ($scfg->{nodes}) {
106 $scfg->{nodes}->{$node} = 1;
107 $self->update({
108 nodes => join(',', sort keys $scfg->{nodes}->%*),
109 storage => $sid,
110 });
111 print "Added '${node}' to nodes for storage '${sid}'\n";
112 }
113 } else {
114 $self->create($storage_params);
115 }
116 }
117 }
118
119 __PACKAGE__->register_method ({
120 name => 'index',
121 path => '',
122 method => 'GET',
123 description => "Storage index.",
124 permissions => {
125 description => "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
126 user => 'all',
127 },
128 parameters => {
129 additionalProperties => 0,
130 properties => {
131 type => {
132 description => "Only list storage of specific type",
133 type => 'string',
134 enum => $storage_type_enum,
135 optional => 1,
136 },
137 },
138 },
139 returns => {
140 type => 'array',
141 items => {
142 type => "object",
143 properties => { storage => { type => 'string'} },
144 },
145 links => [ { rel => 'child', href => "{storage}" } ],
146 },
147 code => sub {
148 my ($param) = @_;
149
150 my $rpcenv = PVE::RPCEnvironment::get();
151 my $authuser = $rpcenv->get_user();
152
153 my $cfg = PVE::Storage::config();
154
155 my @sids = PVE::Storage::storage_ids($cfg);
156
157 my $res = [];
158 foreach my $storeid (@sids) {
159 my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ];
160 next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
161
162 my $scfg = &$api_storage_config($cfg, $storeid);
163 next if $param->{type} && $param->{type} ne $scfg->{type};
164 push @$res, $scfg;
165 }
166
167 return $res;
168 }});
169
170 __PACKAGE__->register_method ({
171 name => 'read',
172 path => '{storage}',
173 method => 'GET',
174 description => "Read storage configuration.",
175 permissions => {
176 check => ['perm', '/storage/{storage}', ['Datastore.Allocate']],
177 },
178 parameters => {
179 additionalProperties => 0,
180 properties => {
181 storage => get_standard_option('pve-storage-id'),
182 },
183 },
184 returns => { type => 'object' },
185 code => sub {
186 my ($param) = @_;
187
188 my $cfg = PVE::Storage::config();
189
190 return &$api_storage_config($cfg, $param->{storage});
191 }});
192
193 my $sensitive_params = [qw(password encryption-key master-pubkey keyring)];
194
195 __PACKAGE__->register_method ({
196 name => 'create',
197 protected => 1,
198 path => '',
199 method => 'POST',
200 description => "Create a new storage.",
201 permissions => {
202 check => ['perm', '/storage', ['Datastore.Allocate']],
203 },
204 parameters => PVE::Storage::Plugin->createSchema(),
205 returns => {
206 type => 'object',
207 properties => {
208 storage => {
209 description => "The ID of the created storage.",
210 type => 'string',
211 },
212 type => {
213 description => "The type of the created storage.",
214 type => 'string',
215 enum => $storage_type_enum,
216 },
217 config => {
218 description => "Partial, possible server generated, configuration properties.",
219 type => 'object',
220 optional => 1,
221 additionalProperties => 1,
222 properties => {
223 'encryption-key' => {
224 description => "The, possible auto-generated, encryption-key.",
225 optional => 1,
226 type => 'string',
227 },
228 },
229 },
230 },
231 },
232 code => sub {
233 my ($param) = @_;
234
235 my $type = extract_param($param, 'type');
236 my $storeid = extract_param($param, 'storage');
237
238 # revent an empty nodelist.
239 # fix me in section config create never need an empty entity.
240 delete $param->{nodes} if !$param->{nodes};
241
242 my $sensitive = extract_sensitive_params($param, $sensitive_params, []);
243
244 my $plugin = PVE::Storage::Plugin->lookup($type);
245 my $opts = $plugin->check_config($storeid, $param, 1, 1);
246
247 my $returned_config;
248 PVE::Storage::lock_storage_config(sub {
249 my $cfg = PVE::Storage::config();
250
251 if (my $scfg = PVE::Storage::storage_config($cfg, $storeid, 1)) {
252 die "storage ID '$storeid' already defined\n";
253 }
254
255 $cfg->{ids}->{$storeid} = $opts;
256
257 $returned_config = $plugin->on_add_hook($storeid, $opts, %$sensitive);
258
259 if (defined($opts->{mkdir})) { # TODO: remove complete option in Proxmox VE 9
260 warn "NOTE: The 'mkdir' option set for '${storeid}' is deprecated and will be removed"
261 ." in Proxmox VE 9. Use 'create-base-path' or 'create-subdirs' instead.\n"
262 }
263
264 eval {
265 # try to activate if enabled on local node,
266 # we only do this to detect errors/problems sooner
267 if (PVE::Storage::storage_check_enabled($cfg, $storeid, undef, 1)) {
268 PVE::Storage::activate_storage($cfg, $storeid);
269 }
270 };
271 if (my $err = $@) {
272 eval { $plugin->on_delete_hook($storeid, $opts) };
273 warn "$@\n" if $@;
274 die $err;
275 }
276
277 PVE::Storage::write_config($cfg);
278
279 }, "create storage failed");
280
281 my $res = {
282 storage => $storeid,
283 type => $type,
284 };
285 $res->{config} = $returned_config if $returned_config;
286 return $res;
287 }});
288
289 __PACKAGE__->register_method ({
290 name => 'update',
291 protected => 1,
292 path => '{storage}',
293 method => 'PUT',
294 description => "Update storage configuration.",
295 permissions => {
296 check => ['perm', '/storage', ['Datastore.Allocate']],
297 },
298 parameters => PVE::Storage::Plugin->updateSchema(),
299 returns => {
300 type => 'object',
301 properties => {
302 storage => {
303 description => "The ID of the created storage.",
304 type => 'string',
305 },
306 type => {
307 description => "The type of the created storage.",
308 type => 'string',
309 enum => $storage_type_enum,
310 },
311 config => {
312 description => "Partial, possible server generated, configuration properties.",
313 type => 'object',
314 optional => 1,
315 additionalProperties => 1,
316 properties => {
317 'encryption-key' => {
318 description => "The, possible auto-generated, encryption-key.",
319 optional => 1,
320 type => 'string',
321 },
322 },
323 },
324 },
325 },
326 code => sub {
327 my ($param) = @_;
328
329 my $storeid = extract_param($param, 'storage');
330 my $digest = extract_param($param, 'digest');
331 my $delete = extract_param($param, 'delete');
332 my $type;
333
334 if ($delete) {
335 $delete = [ PVE::Tools::split_list($delete) ];
336 }
337
338 my $returned_config;
339 PVE::Storage::lock_storage_config(sub {
340 my $cfg = PVE::Storage::config();
341
342 PVE::SectionConfig::assert_if_modified($cfg, $digest);
343
344 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
345 $type = $scfg->{type};
346
347 my $sensitive = extract_sensitive_params($param, $sensitive_params, $delete);
348
349 my $plugin = PVE::Storage::Plugin->lookup($type);
350 my $opts = $plugin->check_config($storeid, $param, 0, 1);
351
352 if ($delete) {
353 my $options = $plugin->private()->{options}->{$type};
354 foreach my $k (@$delete) {
355 my $d = $options->{$k} || die "no such option '$k'\n";
356 die "unable to delete required option '$k'\n" if !$d->{optional};
357 die "unable to delete fixed option '$k'\n" if $d->{fixed};
358 die "cannot set and delete property '$k' at the same time!\n"
359 if defined($opts->{$k});
360
361 delete $scfg->{$k};
362 }
363 }
364
365 $returned_config = $plugin->on_update_hook($storeid, $opts, %$sensitive);
366
367 for my $k (keys %$opts) {
368 $scfg->{$k} = $opts->{$k};
369 }
370
371 if (defined($scfg->{mkdir})) { # TODO: remove complete option in Proxmox VE 9
372 warn "NOTE: The 'mkdir' option set for '${storeid}' is deprecated and will be removed"
373 ." in Proxmox VE 9. Use 'create-base-path' or 'create-subdirs' instead.\n"
374 }
375
376 PVE::Storage::write_config($cfg);
377
378 }, "update storage failed");
379
380 my $res = {
381 storage => $storeid,
382 type => $type,
383 };
384 $res->{config} = $returned_config if $returned_config;
385 return $res;
386 }});
387
388 __PACKAGE__->register_method ({
389 name => 'delete',
390 protected => 1,
391 path => '{storage}', # /storage/config/{storage}
392 method => 'DELETE',
393 description => "Delete storage configuration.",
394 permissions => {
395 check => ['perm', '/storage', ['Datastore.Allocate']],
396 },
397 parameters => {
398 additionalProperties => 0,
399 properties => {
400 storage => get_standard_option('pve-storage-id', {
401 completion => \&PVE::Storage::complete_storage,
402 }),
403 },
404 },
405 returns => { type => 'null' },
406 code => sub {
407 my ($param) = @_;
408
409 my $storeid = extract_param($param, 'storage');
410
411 PVE::Storage::lock_storage_config(sub {
412 my $cfg = PVE::Storage::config();
413
414 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
415
416 die "can't remove storage - storage is used as base of another storage\n"
417 if PVE::Storage::storage_is_used($cfg, $storeid);
418
419 my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
420
421 $plugin->on_delete_hook($storeid, $scfg);
422
423 delete $cfg->{ids}->{$storeid};
424
425 PVE::Storage::write_config($cfg);
426
427 }, "delete storage failed");
428
429 PVE::AccessControl::remove_storage_access($storeid);
430
431 return undef;
432 }});
433
434 1;