]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Ceph/Pools.pm
ceph: add autoscale_status to api calls
[pve-manager.git] / PVE / API2 / Ceph / Pools.pm
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 my $get_autoscale_status = sub {
20 my ($rados) = shift;
21
22 $rados = PVE::RADOS->new() if !defined($rados);
23
24 my $autoscale = $rados->mon_command({
25 prefix => 'osd pool autoscale-status'});
26
27 my $data;
28 foreach my $p (@$autoscale) {
29 $p->{would_adjust} = "$p->{would_adjust}"; # boolean
30 $data->{$p->{pool_name}} = $p;
31 }
32
33 return $data;
34 };
35
36
37 __PACKAGE__->register_method ({
38 name => 'lspools',
39 path => '',
40 method => 'GET',
41 description => "List all pools.",
42 proxyto => 'node',
43 protected => 1,
44 permissions => {
45 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
46 },
47 parameters => {
48 additionalProperties => 0,
49 properties => {
50 node => get_standard_option('pve-node'),
51 },
52 },
53 returns => {
54 type => 'array',
55 items => {
56 type => "object",
57 properties => {
58 pool => { type => 'integer', title => 'ID' },
59 pool_name => { type => 'string', title => 'Name' },
60 size => { type => 'integer', title => 'Size' },
61 min_size => { type => 'integer', title => 'Min Size' },
62 pg_num => { type => 'integer', title => 'PG Num' },
63 pg_num_min => { type => 'integer', title => 'min. PG Num', optional => 1, },
64 pg_num_final => { type => 'integer', title => 'Optimal PG Num', optional => 1, },
65 pg_autoscale_mode => { type => 'string', title => 'PG Autoscale Mode', optional => 1, },
66 crush_rule => { type => 'integer', title => 'Crush Rule' },
67 crush_rule_name => { type => 'string', title => 'Crush Rule Name' },
68 percent_used => { type => 'number', title => '%-Used' },
69 bytes_used => { type => 'integer', title => 'Used' },
70 target_size => { type => 'integer', title => 'PG Autoscale Target Size', optional => 1 },
71 target_size_ratio => { type => 'number', title => 'PG Autoscale Target Ratio',optional => 1, },
72 autoscale_status => { type => 'object', title => 'Autoscale Status', optional => 1 },
73 },
74 },
75 links => [ { rel => 'child', href => "{pool_name}" } ],
76 },
77 code => sub {
78 my ($param) = @_;
79
80 PVE::Ceph::Tools::check_ceph_inited();
81
82 my $rados = PVE::RADOS->new();
83
84 my $stats = {};
85 my $res = $rados->mon_command({ prefix => 'df' });
86
87 foreach my $d (@{$res->{pools}}) {
88 next if !$d->{stats};
89 next if !defined($d->{id});
90 $stats->{$d->{id}} = $d->{stats};
91 }
92
93 $res = $rados->mon_command({ prefix => 'osd dump' });
94 my $rulestmp = $rados->mon_command({ prefix => 'osd crush rule dump'});
95
96 my $rules = {};
97 for my $rule (@$rulestmp) {
98 $rules->{$rule->{rule_id}} = $rule->{rule_name};
99 }
100
101 my $data = [];
102 my $attr_list = [
103 'pool',
104 'pool_name',
105 'size',
106 'min_size',
107 'pg_num',
108 'crush_rule',
109 'pg_autoscale_mode',
110 ];
111
112 # pg_autoscaler module is not enabled in Nautilus
113 my $autoscale = eval { $get_autoscale_status->($rados) };
114
115 foreach my $e (@{$res->{pools}}) {
116 my $d = {};
117 foreach my $attr (@$attr_list) {
118 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
119 }
120
121 if ($autoscale) {
122 $d->{autoscale_status} = $autoscale->{$d->{pool_name}};
123 $d->{pg_num_final} = $d->{autoscale_status}->{pg_num_final};
124 # some info is nested under options instead
125 $d->{pg_num_min} = $e->{options}->{pg_num_min};
126 $d->{target_size} = $e->{options}->{target_size_bytes};
127 $d->{target_size_ratio} = $e->{options}->{target_size_ratio};
128 }
129
130 if (defined($d->{crush_rule}) && defined($rules->{$d->{crush_rule}})) {
131 $d->{crush_rule_name} = $rules->{$d->{crush_rule}};
132 }
133
134 if (my $s = $stats->{$d->{pool}}) {
135 $d->{bytes_used} = $s->{bytes_used};
136 $d->{percent_used} = $s->{percent_used};
137 }
138 push @$data, $d;
139 }
140
141
142 return $data;
143 }});
144
145
146 my $ceph_pool_common_options = sub {
147 my ($nodefault) = shift;
148 my $options = {
149 name => {
150 title => 'Name',
151 description => "The name of the pool. It must be unique.",
152 type => 'string',
153 },
154 size => {
155 title => 'Size',
156 description => 'Number of replicas per object',
157 type => 'integer',
158 default => 3,
159 optional => 1,
160 minimum => 1,
161 maximum => 7,
162 },
163 min_size => {
164 title => 'Min Size',
165 description => 'Minimum number of replicas per object',
166 type => 'integer',
167 default => 2,
168 optional => 1,
169 minimum => 1,
170 maximum => 7,
171 },
172 pg_num => {
173 title => 'PG Num',
174 description => "Number of placement groups.",
175 type => 'integer',
176 default => 128,
177 optional => 1,
178 minimum => 8,
179 maximum => 32768,
180 },
181 pg_num_min => {
182 title => 'min. PG Num',
183 description => "Minimal number of placement groups.",
184 type => 'integer',
185 optional => 1,
186 maximum => 32768,
187 },
188 crush_rule => {
189 title => 'Crush Rule Name',
190 description => "The rule to use for mapping object placement in the cluster.",
191 type => 'string',
192 optional => 1,
193 },
194 application => {
195 title => 'Application',
196 description => "The application of the pool.",
197 default => 'rbd',
198 type => 'string',
199 enum => ['rbd', 'cephfs', 'rgw'],
200 optional => 1,
201 },
202 pg_autoscale_mode => {
203 title => 'PG Autoscale Mode',
204 description => "The automatic PG scaling mode of the pool.",
205 type => 'string',
206 enum => ['on', 'off', 'warn'],
207 default => 'warn',
208 optional => 1,
209 },
210 target_size => {
211 description => "The estimated target size of the pool for the PG autoscaler.",
212 title => 'PG Autoscale Target Size',
213 type => 'string',
214 pattern => '^(\d+(\.\d+)?)([KMGT])?$',
215 optional => 1,
216 },
217 target_size_ratio => {
218 description => "The estimated target ratio of the pool for the PG autoscaler.",
219 title => 'PG Autoscale Target Ratio',
220 type => 'number',
221 optional => 1,
222 },
223 };
224
225 if ($nodefault) {
226 delete $options->{$_}->{default} for keys %$options;
227 }
228 return $options;
229 };
230
231
232 my $add_storage = sub {
233 my ($pool, $storeid) = @_;
234
235 my $storage_params = {
236 type => 'rbd',
237 pool => $pool,
238 storage => $storeid,
239 krbd => 0,
240 content => 'rootdir,images',
241 };
242
243 PVE::API2::Storage::Config->create($storage_params);
244 };
245
246 my $get_storages = sub {
247 my ($pool) = @_;
248
249 my $cfg = PVE::Storage::config();
250
251 my $storages = $cfg->{ids};
252 my $res = {};
253 foreach my $storeid (keys %$storages) {
254 my $curr = $storages->{$storeid};
255 $res->{$storeid} = $storages->{$storeid}
256 if $curr->{type} eq 'rbd' && $pool eq $curr->{pool};
257 }
258
259 return $res;
260 };
261
262
263 __PACKAGE__->register_method ({
264 name => 'createpool',
265 path => '',
266 method => 'POST',
267 description => "Create POOL",
268 proxyto => 'node',
269 protected => 1,
270 permissions => {
271 check => ['perm', '/', [ 'Sys.Modify' ]],
272 },
273 parameters => {
274 additionalProperties => 0,
275 properties => {
276 node => get_standard_option('pve-node'),
277 add_storages => {
278 description => "Configure VM and CT storage using the new pool.",
279 type => 'boolean',
280 optional => 1,
281 },
282 %{ $ceph_pool_common_options->() },
283 },
284 },
285 returns => { type => 'string' },
286 code => sub {
287 my ($param) = @_;
288
289 PVE::Cluster::check_cfs_quorum();
290 PVE::Ceph::Tools::check_ceph_configured();
291
292 my $pool = extract_param($param, 'name');
293 my $node = extract_param($param, 'node');
294 my $add_storages = extract_param($param, 'add_storages');
295
296 my $rpcenv = PVE::RPCEnvironment::get();
297 my $user = $rpcenv->get_user();
298
299 # Ceph uses target_size_bytes
300 if (defined($param->{'target_size'})) {
301 my $target_sizestr = extract_param($param, 'target_size');
302 $param->{target_size_bytes} = PVE::JSONSchema::parse_size($target_sizestr);
303 }
304
305 if ($add_storages) {
306 $rpcenv->check($user, '/storage', ['Datastore.Allocate']);
307 die "pool name contains characters which are illegal for storage naming\n"
308 if !PVE::JSONSchema::parse_storage_id($pool);
309 }
310
311 # pool defaults
312 $param->{pg_num} //= 128;
313 $param->{size} //= 3;
314 $param->{min_size} //= 2;
315 $param->{application} //= 'rbd';
316 $param->{pg_autoscale_mode} //= 'warn';
317
318 my $worker = sub {
319
320 PVE::Ceph::Tools::create_pool($pool, $param);
321
322 if ($add_storages) {
323 my $err;
324 eval { $add_storage->($pool, "${pool}"); };
325 if ($@) {
326 warn "failed to add storage: $@";
327 $err = 1;
328 }
329 die "adding storage for pool '$pool' failed, check log and add manually!\n"
330 if $err;
331 }
332 };
333
334 return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker);
335 }});
336
337
338 __PACKAGE__->register_method ({
339 name => 'destroypool',
340 path => '{name}',
341 method => 'DELETE',
342 description => "Destroy pool",
343 proxyto => 'node',
344 protected => 1,
345 permissions => {
346 check => ['perm', '/', [ 'Sys.Modify' ]],
347 },
348 parameters => {
349 additionalProperties => 0,
350 properties => {
351 node => get_standard_option('pve-node'),
352 name => {
353 description => "The name of the pool. It must be unique.",
354 type => 'string',
355 },
356 force => {
357 description => "If true, destroys pool even if in use",
358 type => 'boolean',
359 optional => 1,
360 default => 0,
361 },
362 remove_storages => {
363 description => "Remove all pveceph-managed storages configured for this pool",
364 type => 'boolean',
365 optional => 1,
366 default => 0,
367 },
368 },
369 },
370 returns => { type => 'string' },
371 code => sub {
372 my ($param) = @_;
373
374 PVE::Ceph::Tools::check_ceph_inited();
375
376 my $rpcenv = PVE::RPCEnvironment::get();
377 my $user = $rpcenv->get_user();
378 $rpcenv->check($user, '/storage', ['Datastore.Allocate'])
379 if $param->{remove_storages};
380
381 my $pool = $param->{name};
382
383 my $worker = sub {
384 my $storages = $get_storages->($pool);
385
386 # if not forced, destroy ceph pool only when no
387 # vm disks are on it anymore
388 if (!$param->{force}) {
389 my $storagecfg = PVE::Storage::config();
390 foreach my $storeid (keys %$storages) {
391 my $storage = $storages->{$storeid};
392
393 # check if any vm disks are on the pool
394 print "checking storage '$storeid' for RBD images..\n";
395 my $res = PVE::Storage::vdisk_list($storagecfg, $storeid);
396 die "ceph pool '$pool' still in use by storage '$storeid'\n"
397 if @{$res->{$storeid}} != 0;
398 }
399 }
400
401 PVE::Ceph::Tools::destroy_pool($pool);
402
403 if ($param->{remove_storages}) {
404 my $err;
405 foreach my $storeid (keys %$storages) {
406 # skip external clusters, not managed by pveceph
407 next if $storages->{$storeid}->{monhost};
408 eval { PVE::API2::Storage::Config->delete({storage => $storeid}) };
409 if ($@) {
410 warn "failed to remove storage '$storeid': $@\n";
411 $err = 1;
412 }
413 }
414 die "failed to remove (some) storages - check log and remove manually!\n"
415 if $err;
416 }
417 };
418 return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker);
419 }});
420
421
422 __PACKAGE__->register_method ({
423 name => 'setpool',
424 path => '{name}',
425 method => 'PUT',
426 description => "Change POOL settings",
427 proxyto => 'node',
428 protected => 1,
429 permissions => {
430 check => ['perm', '/', [ 'Sys.Modify' ]],
431 },
432 parameters => {
433 additionalProperties => 0,
434 properties => {
435 node => get_standard_option('pve-node'),
436 %{ $ceph_pool_common_options->('nodefault') },
437 },
438 },
439 returns => { type => 'string' },
440 code => sub {
441 my ($param) = @_;
442
443 PVE::Ceph::Tools::check_ceph_configured();
444
445 my $rpcenv = PVE::RPCEnvironment::get();
446 my $authuser = $rpcenv->get_user();
447
448 my $pool = extract_param($param, 'name');
449 my $node = extract_param($param, 'node');
450
451 # Ceph uses target_size_bytes
452 if (defined($param->{'target_size'})) {
453 my $target_sizestr = extract_param($param, 'target_size');
454 $param->{target_size_bytes} = PVE::JSONSchema::parse_size($target_sizestr);
455 }
456
457 my $worker = sub {
458 PVE::Ceph::Tools::set_pool($pool, $param);
459 };
460
461 return $rpcenv->fork_worker('cephsetpool', $pool, $authuser, $worker);
462 }});
463
464
465 __PACKAGE__->register_method ({
466 name => 'getpool',
467 path => '{name}',
468 method => 'GET',
469 description => "List pool settings.",
470 proxyto => 'node',
471 protected => 1,
472 permissions => {
473 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
474 },
475 parameters => {
476 additionalProperties => 0,
477 properties => {
478 node => get_standard_option('pve-node'),
479 name => {
480 description => "The name of the pool. It must be unique.",
481 type => 'string',
482 },
483 verbose => {
484 type => 'boolean',
485 default => 0,
486 optional => 1,
487 description => "If enabled, will display additional data".
488 "(eg. statistics).",
489 },
490 },
491 },
492 returns => {
493 type => "object",
494 properties => {
495 id => { type => 'integer', title => 'ID' },
496 pgp_num => { type => 'integer', title => 'PGP num' },
497 noscrub => { type => 'boolean', title => 'noscrub' },
498 'nodeep-scrub' => { type => 'boolean', title => 'nodeep-scrub' },
499 nodelete => { type => 'boolean', title => 'nodelete' },
500 nopgchange => { type => 'boolean', title => 'nopgchange' },
501 nosizechange => { type => 'boolean', title => 'nosizechange' },
502 write_fadvise_dontneed => { type => 'boolean', title => 'write_fadvise_dontneed' },
503 hashpspool => { type => 'boolean', title => 'hashpspool' },
504 use_gmt_hitset => { type => 'boolean', title => 'use_gmt_hitset' },
505 fast_read => { type => 'boolean', title => 'Fast Read' },
506 application_list => { type => 'array', title => 'Application', optional => 1 },
507 statistics => { type => 'object', title => 'Statistics', optional => 1 },
508 autoscale_status => { type => 'object', title => 'Autoscale Status', optional => 1 },
509 %{ $ceph_pool_common_options->() },
510 },
511 },
512 code => sub {
513 my ($param) = @_;
514
515 PVE::Ceph::Tools::check_ceph_inited();
516
517 my $verbose = $param->{verbose};
518 my $pool = $param->{name};
519
520 my $rados = PVE::RADOS->new();
521 my $res = $rados->mon_command({
522 prefix => 'osd pool get',
523 pool => "$pool",
524 var => 'all',
525 });
526
527 my $data = {
528 id => $res->{pool_id},
529 name => $pool,
530 size => $res->{size},
531 min_size => $res->{min_size},
532 pg_num => $res->{pg_num},
533 pg_num_min => $res->{pg_num_min},
534 pgp_num => $res->{pgp_num},
535 crush_rule => $res->{crush_rule},
536 pg_autoscale_mode => $res->{pg_autoscale_mode},
537 noscrub => "$res->{noscrub}",
538 'nodeep-scrub' => "$res->{'nodeep-scrub'}",
539 nodelete => "$res->{nodelete}",
540 nopgchange => "$res->{nopgchange}",
541 nosizechange => "$res->{nosizechange}",
542 write_fadvise_dontneed => "$res->{write_fadvise_dontneed}",
543 hashpspool => "$res->{hashpspool}",
544 use_gmt_hitset => "$res->{use_gmt_hitset}",
545 fast_read => "$res->{fast_read}",
546 target_size => $res->{target_size_bytes},
547 target_size_ratio => $res->{target_size_ratio},
548 };
549
550 if ($verbose) {
551 my $stats;
552 my $res = $rados->mon_command({ prefix => 'df' });
553
554 # pg_autoscaler module is not enabled in Nautilus
555 # avoid partial read further down, use new rados instance
556 my $autoscale_status = eval { $get_autoscale_status->() };
557 $data->{autoscale_status} = $autoscale_status->{$pool};
558
559 foreach my $d (@{$res->{pools}}) {
560 next if !$d->{stats};
561 next if !defined($d->{name}) && !$d->{name} ne "$pool";
562 $data->{statistics} = $d->{stats};
563 }
564
565 my $apps = $rados->mon_command({ prefix => "osd pool application get", pool => "$pool", });
566 $data->{application_list} = [ keys %$apps ];
567 }
568
569 return $data;
570 }});
571
572
573 1;