]>
Commit | Line | Data |
---|---|---|
56d02a86 AA |
1 | package PVE::API2::Ceph::Pools; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use PVE::Ceph::Tools; | |
7 | use PVE::Ceph::Services; | |
8 | use PVE::JSONSchema qw(get_standard_option); | |
9 | use PVE::RADOS; | |
10 | use PVE::RESTHandler; | |
11 | use PVE::RPCEnvironment; | |
12 | use PVE::Storage; | |
13 | use PVE::Tools qw(extract_param); | |
14 | ||
15 | use PVE::API2::Storage::Config; | |
16 | ||
17 | use base qw(PVE::RESTHandler); | |
18 | ||
19 | __PACKAGE__->register_method ({ | |
20 | name => 'lspools', | |
21 | path => '', | |
22 | method => 'GET', | |
23 | description => "List all pools.", | |
24 | proxyto => 'node', | |
25 | protected => 1, | |
26 | permissions => { | |
27 | check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], | |
28 | }, | |
29 | parameters => { | |
30 | additionalProperties => 0, | |
31 | properties => { | |
32 | node => get_standard_option('pve-node'), | |
33 | }, | |
34 | }, | |
35 | returns => { | |
36 | type => 'array', | |
37 | items => { | |
38 | type => "object", | |
39 | properties => { | |
40 | pool => { type => 'integer', title => 'ID' }, | |
41 | pool_name => { type => 'string', title => 'Name' }, | |
42 | size => { type => 'integer', title => 'Size' }, | |
43 | min_size => { type => 'integer', title => 'Min Size' }, | |
44 | pg_num => { type => 'integer', title => 'PG Num' }, | |
45 | pg_autoscale_mode => { type => 'string', optional => 1, title => 'PG Autoscale Mode' }, | |
46 | crush_rule => { type => 'integer', title => 'Crush Rule' }, | |
47 | crush_rule_name => { type => 'string', title => 'Crush Rule Name' }, | |
48 | percent_used => { type => 'number', title => '%-Used' }, | |
49 | bytes_used => { type => 'integer', title => 'Used' }, | |
50 | }, | |
51 | }, | |
52 | links => [ { rel => 'child', href => "{pool_name}" } ], | |
53 | }, | |
54 | code => sub { | |
55 | my ($param) = @_; | |
56 | ||
57 | PVE::Ceph::Tools::check_ceph_inited(); | |
58 | ||
59 | my $rados = PVE::RADOS->new(); | |
60 | ||
61 | my $stats = {}; | |
62 | my $res = $rados->mon_command({ prefix => 'df' }); | |
63 | ||
64 | foreach my $d (@{$res->{pools}}) { | |
65 | next if !$d->{stats}; | |
66 | next if !defined($d->{id}); | |
67 | $stats->{$d->{id}} = $d->{stats}; | |
68 | } | |
69 | ||
70 | $res = $rados->mon_command({ prefix => 'osd dump' }); | |
71 | my $rulestmp = $rados->mon_command({ prefix => 'osd crush rule dump'}); | |
72 | ||
73 | my $rules = {}; | |
74 | for my $rule (@$rulestmp) { | |
75 | $rules->{$rule->{rule_id}} = $rule->{rule_name}; | |
76 | } | |
77 | ||
78 | my $data = []; | |
79 | my $attr_list = [ | |
80 | 'pool', | |
81 | 'pool_name', | |
82 | 'size', | |
83 | 'min_size', | |
84 | 'pg_num', | |
85 | 'crush_rule', | |
86 | 'pg_autoscale_mode', | |
87 | ]; | |
88 | ||
89 | foreach my $e (@{$res->{pools}}) { | |
90 | my $d = {}; | |
91 | foreach my $attr (@$attr_list) { | |
92 | $d->{$attr} = $e->{$attr} if defined($e->{$attr}); | |
93 | } | |
94 | ||
95 | if (defined($d->{crush_rule}) && defined($rules->{$d->{crush_rule}})) { | |
96 | $d->{crush_rule_name} = $rules->{$d->{crush_rule}}; | |
97 | } | |
98 | ||
99 | if (my $s = $stats->{$d->{pool}}) { | |
100 | $d->{bytes_used} = $s->{bytes_used}; | |
101 | $d->{percent_used} = $s->{percent_used}; | |
102 | } | |
103 | push @$data, $d; | |
104 | } | |
105 | ||
106 | ||
107 | return $data; | |
108 | }}); | |
109 | ||
110 | ||
111 | my $ceph_pool_common_options = sub { | |
112 | my ($nodefault) = shift; | |
113 | my $options = { | |
114 | name => { | |
461e2141 | 115 | title => 'Name', |
56d02a86 AA |
116 | description => "The name of the pool. It must be unique.", |
117 | type => 'string', | |
118 | }, | |
119 | size => { | |
461e2141 | 120 | title => 'Size', |
56d02a86 AA |
121 | description => 'Number of replicas per object', |
122 | type => 'integer', | |
123 | default => 3, | |
124 | optional => 1, | |
125 | minimum => 1, | |
126 | maximum => 7, | |
127 | }, | |
128 | min_size => { | |
461e2141 | 129 | title => 'Min Size', |
56d02a86 AA |
130 | description => 'Minimum number of replicas per object', |
131 | type => 'integer', | |
132 | default => 2, | |
133 | optional => 1, | |
134 | minimum => 1, | |
135 | maximum => 7, | |
136 | }, | |
137 | pg_num => { | |
461e2141 | 138 | title => 'PG Num', |
56d02a86 AA |
139 | description => "Number of placement groups.", |
140 | type => 'integer', | |
141 | default => 128, | |
142 | optional => 1, | |
143 | minimum => 8, | |
144 | maximum => 32768, | |
145 | }, | |
146 | crush_rule => { | |
461e2141 | 147 | title => 'Crush Rule Name', |
56d02a86 AA |
148 | description => "The rule to use for mapping object placement in the cluster.", |
149 | type => 'string', | |
150 | optional => 1, | |
151 | }, | |
152 | application => { | |
461e2141 | 153 | title => 'Application', |
56d02a86 AA |
154 | description => "The application of the pool.", |
155 | default => 'rbd', | |
156 | type => 'string', | |
157 | enum => ['rbd', 'cephfs', 'rgw'], | |
158 | optional => 1, | |
159 | }, | |
160 | pg_autoscale_mode => { | |
461e2141 | 161 | title => 'PG Autoscale Mode', |
56d02a86 AA |
162 | description => "The automatic PG scaling mode of the pool.", |
163 | type => 'string', | |
164 | enum => ['on', 'off', 'warn'], | |
165 | default => 'warn', | |
166 | optional => 1, | |
167 | }, | |
168 | }; | |
169 | ||
170 | if ($nodefault) { | |
171 | delete $options->{$_}->{default} for keys %$options; | |
172 | } | |
173 | return $options; | |
174 | }; | |
175 | ||
176 | ||
177 | my $add_storage = sub { | |
178 | my ($pool, $storeid) = @_; | |
179 | ||
180 | my $storage_params = { | |
181 | type => 'rbd', | |
182 | pool => $pool, | |
183 | storage => $storeid, | |
184 | krbd => 0, | |
185 | content => 'rootdir,images', | |
186 | }; | |
187 | ||
188 | PVE::API2::Storage::Config->create($storage_params); | |
189 | }; | |
190 | ||
191 | my $get_storages = sub { | |
192 | my ($pool) = @_; | |
193 | ||
194 | my $cfg = PVE::Storage::config(); | |
195 | ||
196 | my $storages = $cfg->{ids}; | |
197 | my $res = {}; | |
198 | foreach my $storeid (keys %$storages) { | |
199 | my $curr = $storages->{$storeid}; | |
200 | $res->{$storeid} = $storages->{$storeid} | |
201 | if $curr->{type} eq 'rbd' && $pool eq $curr->{pool}; | |
202 | } | |
203 | ||
204 | return $res; | |
205 | }; | |
206 | ||
207 | ||
208 | __PACKAGE__->register_method ({ | |
209 | name => 'createpool', | |
210 | path => '', | |
211 | method => 'POST', | |
212 | description => "Create POOL", | |
213 | proxyto => 'node', | |
214 | protected => 1, | |
215 | permissions => { | |
216 | check => ['perm', '/', [ 'Sys.Modify' ]], | |
217 | }, | |
218 | parameters => { | |
219 | additionalProperties => 0, | |
220 | properties => { | |
221 | node => get_standard_option('pve-node'), | |
222 | add_storages => { | |
223 | description => "Configure VM and CT storage using the new pool.", | |
224 | type => 'boolean', | |
225 | optional => 1, | |
226 | }, | |
227 | %{ $ceph_pool_common_options->() }, | |
228 | }, | |
229 | }, | |
230 | returns => { type => 'string' }, | |
231 | code => sub { | |
232 | my ($param) = @_; | |
233 | ||
234 | PVE::Cluster::check_cfs_quorum(); | |
235 | PVE::Ceph::Tools::check_ceph_configured(); | |
236 | ||
237 | my $pool = extract_param($param, 'name'); | |
238 | my $node = extract_param($param, 'node'); | |
239 | my $add_storages = extract_param($param, 'add_storages'); | |
240 | ||
241 | my $rpcenv = PVE::RPCEnvironment::get(); | |
242 | my $user = $rpcenv->get_user(); | |
243 | ||
244 | if ($add_storages) { | |
245 | $rpcenv->check($user, '/storage', ['Datastore.Allocate']); | |
246 | die "pool name contains characters which are illegal for storage naming\n" | |
247 | if !PVE::JSONSchema::parse_storage_id($pool); | |
248 | } | |
249 | ||
250 | # pool defaults | |
251 | $param->{pg_num} //= 128; | |
252 | $param->{size} //= 3; | |
253 | $param->{min_size} //= 2; | |
254 | $param->{application} //= 'rbd'; | |
255 | $param->{pg_autoscale_mode} //= 'warn'; | |
256 | ||
257 | my $worker = sub { | |
258 | ||
259 | PVE::Ceph::Tools::create_pool($pool, $param); | |
260 | ||
261 | if ($add_storages) { | |
262 | my $err; | |
263 | eval { $add_storage->($pool, "${pool}"); }; | |
264 | if ($@) { | |
265 | warn "failed to add storage: $@"; | |
266 | $err = 1; | |
267 | } | |
268 | die "adding storage for pool '$pool' failed, check log and add manually!\n" | |
269 | if $err; | |
270 | } | |
271 | }; | |
272 | ||
273 | return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker); | |
274 | }}); | |
275 | ||
276 | ||
277 | __PACKAGE__->register_method ({ | |
278 | name => 'destroypool', | |
279 | path => '{name}', | |
280 | method => 'DELETE', | |
281 | description => "Destroy pool", | |
282 | proxyto => 'node', | |
283 | protected => 1, | |
284 | permissions => { | |
285 | check => ['perm', '/', [ 'Sys.Modify' ]], | |
286 | }, | |
287 | parameters => { | |
288 | additionalProperties => 0, | |
289 | properties => { | |
290 | node => get_standard_option('pve-node'), | |
291 | name => { | |
292 | description => "The name of the pool. It must be unique.", | |
293 | type => 'string', | |
294 | }, | |
295 | force => { | |
296 | description => "If true, destroys pool even if in use", | |
297 | type => 'boolean', | |
298 | optional => 1, | |
299 | default => 0, | |
300 | }, | |
301 | remove_storages => { | |
302 | description => "Remove all pveceph-managed storages configured for this pool", | |
303 | type => 'boolean', | |
304 | optional => 1, | |
305 | default => 0, | |
306 | }, | |
307 | }, | |
308 | }, | |
309 | returns => { type => 'string' }, | |
310 | code => sub { | |
311 | my ($param) = @_; | |
312 | ||
313 | PVE::Ceph::Tools::check_ceph_inited(); | |
314 | ||
315 | my $rpcenv = PVE::RPCEnvironment::get(); | |
316 | my $user = $rpcenv->get_user(); | |
317 | $rpcenv->check($user, '/storage', ['Datastore.Allocate']) | |
318 | if $param->{remove_storages}; | |
319 | ||
320 | my $pool = $param->{name}; | |
321 | ||
322 | my $worker = sub { | |
323 | my $storages = $get_storages->($pool); | |
324 | ||
325 | # if not forced, destroy ceph pool only when no | |
326 | # vm disks are on it anymore | |
327 | if (!$param->{force}) { | |
328 | my $storagecfg = PVE::Storage::config(); | |
329 | foreach my $storeid (keys %$storages) { | |
330 | my $storage = $storages->{$storeid}; | |
331 | ||
332 | # check if any vm disks are on the pool | |
333 | print "checking storage '$storeid' for RBD images..\n"; | |
334 | my $res = PVE::Storage::vdisk_list($storagecfg, $storeid); | |
335 | die "ceph pool '$pool' still in use by storage '$storeid'\n" | |
336 | if @{$res->{$storeid}} != 0; | |
337 | } | |
338 | } | |
339 | ||
340 | PVE::Ceph::Tools::destroy_pool($pool); | |
341 | ||
342 | if ($param->{remove_storages}) { | |
343 | my $err; | |
344 | foreach my $storeid (keys %$storages) { | |
345 | # skip external clusters, not managed by pveceph | |
346 | next if $storages->{$storeid}->{monhost}; | |
347 | eval { PVE::API2::Storage::Config->delete({storage => $storeid}) }; | |
348 | if ($@) { | |
349 | warn "failed to remove storage '$storeid': $@\n"; | |
350 | $err = 1; | |
351 | } | |
352 | } | |
353 | die "failed to remove (some) storages - check log and remove manually!\n" | |
354 | if $err; | |
355 | } | |
356 | }; | |
357 | return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker); | |
358 | }}); | |
359 | ||
360 | ||
361 | __PACKAGE__->register_method ({ | |
362 | name => 'setpool', | |
363 | path => '{name}', | |
364 | method => 'PUT', | |
365 | description => "Change POOL settings", | |
366 | proxyto => 'node', | |
367 | protected => 1, | |
368 | permissions => { | |
369 | check => ['perm', '/', [ 'Sys.Modify' ]], | |
370 | }, | |
371 | parameters => { | |
372 | additionalProperties => 0, | |
373 | properties => { | |
374 | node => get_standard_option('pve-node'), | |
375 | %{ $ceph_pool_common_options->('nodefault') }, | |
376 | }, | |
377 | }, | |
378 | returns => { type => 'string' }, | |
379 | code => sub { | |
380 | my ($param) = @_; | |
381 | ||
382 | PVE::Ceph::Tools::check_ceph_configured(); | |
383 | ||
384 | my $rpcenv = PVE::RPCEnvironment::get(); | |
385 | my $authuser = $rpcenv->get_user(); | |
386 | ||
51d6db58 AA |
387 | my $pool = extract_param($param, 'name'); |
388 | my $node = extract_param($param, 'node'); | |
56d02a86 AA |
389 | |
390 | my $worker = sub { | |
51d6db58 | 391 | PVE::Ceph::Tools::set_pool($pool, $param); |
56d02a86 AA |
392 | }; |
393 | ||
394 | return $rpcenv->fork_worker('cephsetpool', $pool, $authuser, $worker); | |
395 | }}); | |
396 | ||
397 | ||
398 | 1; |