1 package PVE
::API2
::Storage
::Config
;
7 use PVE
::Tools
qw(extract_param extract_sensitive_params);
8 use PVE
::Cluster
qw(cfs_read_file cfs_write_file);
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
;
20 use base
qw(PVE::RESTHandler);
22 my @ctypes = qw(images vztmpl iso backup);
24 my $storage_type_enum = PVE
::Storage
::Plugin-
>lookup_types();
26 my $api_storage_config = sub {
27 my ($cfg, $storeid) = @_;
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
});
35 $scfg->{nodes
} = PVE
::Storage
::Plugin-
>encode_value($scfg->{type
}, 'nodes', $scfg->{nodes
});
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) = @_;
46 my $config = PVE
::Storage
::config
();
47 my $cluster_nodes = PVE
::Cluster
::get_nodelist
();
49 for my $storeid (keys $config->{ids
}->%*) {
50 my $scfg = PVE
::Storage
::storage_config
($config, $storeid);
51 next if !$match->($scfg);
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};
57 if (scalar(keys $nodes->%*) > 0) {
59 nodes
=> join(',', sort keys $nodes->%*),
63 $self->delete({storage
=> $storeid});
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) = @_;
79 my $cfg = PVE
::Storage
::config
();
80 my $scfg = PVE
::Storage
::storage_config
($cfg, $sid, 1);
83 die "storage config for '${sid}' exists but no parameters to verify were provided\n"
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};
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}'";
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";
105 if ($scfg->{nodes
}) {
106 $scfg->{nodes
}->{$node} = 1;
108 nodes
=> join(',', sort keys $scfg->{nodes
}->%*),
111 print "Added '${node}' to nodes for storage '${sid}'\n";
114 $self->create($storage_params);
119 __PACKAGE__-
>register_method ({
123 description
=> "Storage index.",
125 description
=> "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
129 additionalProperties
=> 0,
132 description
=> "Only list storage of specific type",
134 enum
=> $storage_type_enum,
143 properties
=> { storage
=> { type
=> 'string'} },
145 links
=> [ { rel
=> 'child', href
=> "{storage}" } ],
150 my $rpcenv = PVE
::RPCEnvironment
::get
();
151 my $authuser = $rpcenv->get_user();
153 my $cfg = PVE
::Storage
::config
();
155 my @sids = PVE
::Storage
::storage_ids
($cfg);
158 foreach my $storeid (@sids) {
159 my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ];
160 next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
162 my $scfg = &$api_storage_config($cfg, $storeid);
163 next if $param->{type
} && $param->{type
} ne $scfg->{type
};
170 __PACKAGE__-
>register_method ({
174 description
=> "Read storage configuration.",
176 check
=> ['perm', '/storage/{storage}', ['Datastore.Allocate']],
179 additionalProperties
=> 0,
181 storage
=> get_standard_option
('pve-storage-id'),
184 returns
=> { type
=> 'object' },
188 my $cfg = PVE
::Storage
::config
();
190 return &$api_storage_config($cfg, $param->{storage
});
193 my $sensitive_params = [qw(password encryption-key master-pubkey keyring)];
195 __PACKAGE__-
>register_method ({
200 description
=> "Create a new storage.",
202 check
=> ['perm', '/storage', ['Datastore.Allocate']],
204 parameters
=> PVE
::Storage
::Plugin-
>createSchema(),
209 description
=> "The ID of the created storage.",
213 description
=> "The type of the created storage.",
215 enum
=> $storage_type_enum,
218 description
=> "Partial, possible server generated, configuration properties.",
221 additionalProperties
=> 1,
223 'encryption-key' => {
224 description
=> "The, possible auto-generated, encryption-key.",
235 my $type = extract_param
($param, 'type');
236 my $storeid = extract_param
($param, 'storage');
238 # revent an empty nodelist.
239 # fix me in section config create never need an empty entity.
240 delete $param->{nodes
} if !$param->{nodes
};
242 my $sensitive = extract_sensitive_params
($param, $sensitive_params, []);
244 my $plugin = PVE
::Storage
::Plugin-
>lookup($type);
245 my $opts = $plugin->check_config($storeid, $param, 1, 1);
248 PVE
::Storage
::lock_storage_config
(sub {
249 my $cfg = PVE
::Storage
::config
();
251 if (my $scfg = PVE
::Storage
::storage_config
($cfg, $storeid, 1)) {
252 die "storage ID '$storeid' already defined\n";
255 $cfg->{ids
}->{$storeid} = $opts;
257 $returned_config = $plugin->on_add_hook($storeid, $opts, %$sensitive);
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"
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);
272 eval { $plugin->on_delete_hook($storeid, $opts) };
277 PVE
::Storage
::write_config
($cfg);
279 }, "create storage failed");
285 $res->{config
} = $returned_config if $returned_config;
289 __PACKAGE__-
>register_method ({
294 description
=> "Update storage configuration.",
296 check
=> ['perm', '/storage', ['Datastore.Allocate']],
298 parameters
=> PVE
::Storage
::Plugin-
>updateSchema(),
303 description
=> "The ID of the created storage.",
307 description
=> "The type of the created storage.",
309 enum
=> $storage_type_enum,
312 description
=> "Partial, possible server generated, configuration properties.",
315 additionalProperties
=> 1,
317 'encryption-key' => {
318 description
=> "The, possible auto-generated, encryption-key.",
329 my $storeid = extract_param
($param, 'storage');
330 my $digest = extract_param
($param, 'digest');
331 my $delete = extract_param
($param, 'delete');
335 $delete = [ PVE
::Tools
::split_list
($delete) ];
339 PVE
::Storage
::lock_storage_config
(sub {
340 my $cfg = PVE
::Storage
::config
();
342 PVE
::SectionConfig
::assert_if_modified
($cfg, $digest);
344 my $scfg = PVE
::Storage
::storage_config
($cfg, $storeid);
345 $type = $scfg->{type
};
347 my $sensitive = extract_sensitive_params
($param, $sensitive_params, $delete);
349 my $plugin = PVE
::Storage
::Plugin-
>lookup($type);
350 my $opts = $plugin->check_config($storeid, $param, 0, 1);
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});
365 $returned_config = $plugin->on_update_hook($storeid, $opts, %$sensitive);
367 for my $k (keys %$opts) {
368 $scfg->{$k} = $opts->{$k};
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"
376 PVE
::Storage
::write_config
($cfg);
378 }, "update storage failed");
384 $res->{config
} = $returned_config if $returned_config;
388 __PACKAGE__-
>register_method ({
391 path
=> '{storage}', # /storage/config/{storage}
393 description
=> "Delete storage configuration.",
395 check
=> ['perm', '/storage', ['Datastore.Allocate']],
398 additionalProperties
=> 0,
400 storage
=> get_standard_option
('pve-storage-id', {
401 completion
=> \
&PVE
::Storage
::complete_storage
,
405 returns
=> { type
=> 'null' },
409 my $storeid = extract_param
($param, 'storage');
411 PVE
::Storage
::lock_storage_config
(sub {
412 my $cfg = PVE
::Storage
::config
();
414 my $scfg = PVE
::Storage
::storage_config
($cfg, $storeid);
416 die "can't remove storage - storage is used as base of another storage\n"
417 if PVE
::Storage
::storage_is_used
($cfg, $storeid);
419 my $plugin = PVE
::Storage
::Plugin-
>lookup($scfg->{type
});
421 $plugin->on_delete_hook($storeid, $scfg);
423 delete $cfg->{ids
}->{$storeid};
425 PVE
::Storage
::write_config
($cfg);
427 }, "delete storage failed");
429 PVE
::AccessControl
::remove_storage_access
($storeid);