]>
git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Ceph/Pools.pm
9c3b88844d6caa1e8d3c9c1a8f325ba671f1231b
1 package PVE
::API2
::Ceph
::Pools
;
7 use PVE
::Ceph
::Services
;
8 use PVE
::JSONSchema
qw(get_standard_option parse_property_string);
11 use PVE
::RPCEnvironment
;
13 use PVE
::Tools
qw(extract_param);
15 use PVE
::API2
::Storage
::Config
;
17 use base
qw(PVE::RESTHandler);
19 my $get_autoscale_status = sub {
22 $rados = PVE
::RADOS-
>new() if !defined($rados);
24 my $autoscale = $rados->mon_command({
25 prefix
=> 'osd pool autoscale-status'});
28 foreach my $p (@$autoscale) {
29 $data->{$p->{pool_name
}} = $p;
36 __PACKAGE__-
>register_method ({
40 description
=> "List all pools.",
44 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
47 additionalProperties
=> 0,
49 node
=> get_standard_option
('pve-node'),
72 enum
=> ['replicated', 'erasure', 'unknown'],
84 title
=> 'min. PG Num',
89 title
=> 'Optimal PG Num',
92 pg_autoscale_mode
=> {
94 title
=> 'PG Autoscale Mode',
99 title
=> 'Crush Rule',
103 title
=> 'Crush Rule Name',
115 title
=> 'PG Autoscale Target Size',
118 target_size_ratio
=> {
120 title
=> 'PG Autoscale Target Ratio',
123 autoscale_status
=> {
125 title
=> 'Autoscale Status',
130 links
=> [ { rel
=> 'child', href
=> "{pool_name}" } ],
135 PVE
::Ceph
::Tools
::check_ceph_inited
();
137 my $rados = PVE
::RADOS-
>new();
140 my $res = $rados->mon_command({ prefix
=> 'df' });
142 foreach my $d (@{$res->{pools
}}) {
143 next if !$d->{stats
};
144 next if !defined($d->{id
});
145 $stats->{$d->{id
}} = $d->{stats
};
148 $res = $rados->mon_command({ prefix
=> 'osd dump' });
149 my $rulestmp = $rados->mon_command({ prefix
=> 'osd crush rule dump'});
152 for my $rule (@$rulestmp) {
153 $rules->{$rule->{rule_id
}} = $rule->{rule_name
};
167 # pg_autoscaler module is not enabled in Nautilus
168 my $autoscale = eval { $get_autoscale_status->($rados) };
170 foreach my $e (@{$res->{pools
}}) {
172 foreach my $attr (@$attr_list) {
173 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
177 $d->{autoscale_status
} = $autoscale->{$d->{pool_name
}};
178 $d->{pg_num_final
} = $d->{autoscale_status
}->{pg_num_final
};
179 # some info is nested under options instead
180 $d->{pg_num_min
} = $e->{options
}->{pg_num_min
};
181 $d->{target_size
} = $e->{options
}->{target_size_bytes
};
182 $d->{target_size_ratio
} = $e->{options
}->{target_size_ratio
};
185 if (defined($d->{crush_rule
}) && defined($rules->{$d->{crush_rule
}})) {
186 $d->{crush_rule_name
} = $rules->{$d->{crush_rule
}};
189 if (my $s = $stats->{$d->{pool
}}) {
190 $d->{bytes_used
} = $s->{bytes_used
};
191 $d->{percent_used
} = $s->{percent_used
};
194 # Cephs numerical pool types are barely documented. Found the following in the Ceph
195 # codebase: https://github.com/ceph/ceph/blob/ff144995a849407c258bcb763daa3e03cfce5059/src/osd/osd_types.h#L1221-L1233
196 if ($e->{type
} == 1) {
197 $d->{type
} = 'replicated';
198 } elsif ($e->{type
} == 3) {
199 $d->{type
} = 'erasure';
201 # we should never get here, but better be safe
202 $d->{type
} = 'unknown';
212 my $ceph_pool_common_options = sub {
213 my ($nodefault) = shift;
217 description
=> "The name of the pool. It must be unique.",
222 description
=> 'Number of replicas per object',
231 description
=> 'Minimum number of replicas per object',
240 description
=> "Number of placement groups.",
248 title
=> 'min. PG Num',
249 description
=> "Minimal number of placement groups.",
255 title
=> 'Crush Rule Name',
256 description
=> "The rule to use for mapping object placement in the cluster.",
261 title
=> 'Application',
262 description
=> "The application of the pool.",
265 enum
=> ['rbd', 'cephfs', 'rgw'],
268 pg_autoscale_mode
=> {
269 title
=> 'PG Autoscale Mode',
270 description
=> "The automatic PG scaling mode of the pool.",
272 enum
=> ['on', 'off', 'warn'],
277 description
=> "The estimated target size of the pool for the PG autoscaler.",
278 title
=> 'PG Autoscale Target Size',
280 pattern
=> '^(\d+(\.\d+)?)([KMGT])?$',
283 target_size_ratio
=> {
284 description
=> "The estimated target ratio of the pool for the PG autoscaler.",
285 title
=> 'PG Autoscale Target Ratio',
292 delete $options->{$_}->{default} for keys %$options;
298 my $add_storage = sub {
299 my ($pool, $storeid, $ec_data_pool) = @_;
301 my $storage_params = {
306 content
=> 'rootdir,images',
309 $storage_params->{'data-pool'} = $ec_data_pool if $ec_data_pool;
311 PVE
::API2
::Storage
::Config-
>create($storage_params);
314 my $get_storages = sub {
317 my $cfg = PVE
::Storage
::config
();
319 my $storages = $cfg->{ids
};
321 foreach my $storeid (keys %$storages) {
322 my $curr = $storages->{$storeid};
323 next if $curr->{type
} ne 'rbd';
325 $pool eq $curr->{pool
} ||
326 (defined $curr->{'data-pool'} && $pool eq $curr->{'data-pool'})
328 $res->{$storeid} = $storages->{$storeid};
338 description
=> "Number of data chunks. Will create an erasure coded pool plus a"
339 ." replicated pool for metadata.",
344 description
=> "Number of coding chunks. Will create an erasure coded pool plus a"
345 ." replicated pool for metadata.",
348 'failure-domain' => {
350 description
=> "CRUSH failure domain. Default is 'host'. Will create an erasure"
351 ." coded pool plus a replicated pool for metadata.",
352 format_description
=> 'domain',
358 description
=> "CRUSH device class. Will create an erasure coded pool plus a"
359 ." replicated pool for metadata.",
360 format_description
=> 'class',
364 description
=> "Override the erasure code (EC) profile to use. Will create an"
365 ." erasure coded pool plus a replicated pool for metadata.",
367 format_description
=> 'profile',
372 sub ec_parse_and_check
{
373 my ($property, $rados) = @_;
374 return if !$property;
376 my $ec = parse_property_string
($ec_format, $property);
378 die "Erasure code profile '$ec->{profile}' does not exist.\n"
379 if $ec->{profile
} && !PVE
::Ceph
::Tools
::ecprofile_exists
($ec->{profile
}, $rados);
385 __PACKAGE__-
>register_method ({
386 name
=> 'createpool',
389 description
=> "Create Ceph pool",
393 check
=> ['perm', '/', [ 'Sys.Modify' ]],
396 additionalProperties
=> 0,
398 node
=> get_standard_option
('pve-node'),
400 description
=> "Configure VM and CT storage using the new pool.",
403 default => "0; for erasure coded pools: 1",
405 'erasure-coding' => {
406 description
=> "Create an erasure coded pool for RBD with an"
407 ." accompaning replicated pool for metadata storage.",
409 format
=> $ec_format,
412 %{ $ceph_pool_common_options->() },
415 returns
=> { type
=> 'string' },
419 PVE
::Cluster
::check_cfs_quorum
();
420 PVE
::Ceph
::Tools
::check_ceph_configured
();
422 my $pool = my $name = extract_param
($param, 'name');
423 my $node = extract_param
($param, 'node');
424 my $add_storages = extract_param
($param, 'add_storages');
426 my $rpcenv = PVE
::RPCEnvironment
::get
();
427 my $user = $rpcenv->get_user();
428 # Ceph uses target_size_bytes
429 if (defined($param->{'target_size'})) {
430 my $target_sizestr = extract_param
($param, 'target_size');
431 $param->{target_size_bytes
} = PVE
::JSONSchema
::parse_size
($target_sizestr);
434 my $rados = PVE
::RADOS-
>new();
435 my $ec = ec_parse_and_check
(extract_param
($param, 'erasure-coding'), $rados);
436 $add_storages = 1 if $ec && !defined($add_storages);
439 $rpcenv->check($user, '/storage', ['Datastore.Allocate']);
440 die "pool name contains characters which are illegal for storage naming\n"
441 if !PVE
::JSONSchema
::parse_storage_id
($pool);
445 $param->{pg_num
} //= 128;
446 $param->{size
} //= 3;
447 $param->{min_size
} //= 2;
448 $param->{application
} //= 'rbd';
449 $param->{pg_autoscale_mode
} //= 'warn';
452 # reopen with longer timeout
453 $rados = PVE
::RADOS-
>new(timeout
=> PVE
::Ceph
::Tools
::get_config
('long_rados_timeout'));
456 if (!$ec->{profile
}) {
457 $ec->{profile
} = PVE
::Ceph
::Tools
::get_ecprofile_name
($pool, $rados);
459 PVE
::Ceph
::Tools
::create_ecprofile
(
460 $ec->@{'profile', 'k', 'm', 'failure-domain', 'device-class'},
464 die "could not create erasure code profile '$ec->{profile}': $@\n" if $@;
465 print "created new erasure code profile '$ec->{profile}'\n";
468 my $ec_data_param = {};
469 # copy all params, should be a flat hash
470 $ec_data_param = { map { $_ => $param->{$_} } keys %$param };
472 $ec_data_param->{pool_type
} = 'erasure';
473 $ec_data_param->{allow_ec_overwrites
} = 'true';
474 $ec_data_param->{erasure_code_profile
} = $ec->{profile
};
475 delete $ec_data_param->{size
};
476 delete $ec_data_param->{min_size
};
477 delete $ec_data_param->{crush_rule
};
479 # metadata pool should be ok with 32 PGs
480 $param->{pg_num
} = 32;
482 $pool = "${name}-metadata";
483 $ec->{data_pool
} = "${name}-data";
485 PVE
::Ceph
::Tools
::create_pool
($ec->{data_pool
}, $ec_data_param, $rados);
488 PVE
::Ceph
::Tools
::create_pool
($pool, $param, $rados);
491 eval { $add_storage->($pool, "${name}", $ec->{data_pool
}) };
492 die "adding PVE storage for ceph pool '$name' failed: $@\n" if $@;
496 return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker);
500 __PACKAGE__-
>register_method ({
501 name
=> 'destroypool',
504 description
=> "Destroy pool",
508 check
=> ['perm', '/', [ 'Sys.Modify' ]],
511 additionalProperties
=> 0,
513 node
=> get_standard_option
('pve-node'),
515 description
=> "The name of the pool. It must be unique.",
519 description
=> "If true, destroys pool even if in use",
525 description
=> "Remove all pveceph-managed storages configured for this pool",
530 remove_ecprofile
=> {
531 description
=> "Remove the erasure code profile. Defaults to true, if applicable.",
538 returns
=> { type
=> 'string' },
542 PVE
::Ceph
::Tools
::check_ceph_inited
();
544 my $rpcenv = PVE
::RPCEnvironment
::get
();
545 my $user = $rpcenv->get_user();
546 $rpcenv->check($user, '/storage', ['Datastore.Allocate'])
547 if $param->{remove_storages
};
549 my $pool = $param->{name
};
552 my $storages = $get_storages->($pool);
554 # if not forced, destroy ceph pool only when no
555 # vm disks are on it anymore
556 if (!$param->{force
}) {
557 my $storagecfg = PVE
::Storage
::config
();
558 foreach my $storeid (keys %$storages) {
559 my $storage = $storages->{$storeid};
561 # check if any vm disks are on the pool
562 print "checking storage '$storeid' for RBD images..\n";
563 my $res = PVE
::Storage
::vdisk_list
($storagecfg, $storeid);
564 die "ceph pool '$pool' still in use by storage '$storeid'\n"
565 if @{$res->{$storeid}} != 0;
568 my $rados = PVE
::RADOS-
>new();
570 my $pool_properties = PVE
::Ceph
::Tools
::get_pool_properties
($pool, $rados);
572 PVE
::Ceph
::Tools
::destroy_pool
($pool, $rados);
574 if (my $ecprofile = $pool_properties->{erasure_code_profile
}) {
575 print "found erasure coded profile '$ecprofile', destroying its CRUSH rule\n";
576 my $crush_rule = $pool_properties->{crush_rule
};
577 eval { PVE
::Ceph
::Tools
::destroy_crush_rule
($crush_rule, $rados); };
578 warn "removing crush rule '${crush_rule}' failed: $@\n" if $@;
580 if ($param->{remove_ecprofile
} // 1) {
581 print "destroying erasure coded profile '$ecprofile'\n";
582 eval { PVE
::Ceph
::Tools
::destroy_ecprofile
($ecprofile, $rados) };
583 warn "removing EC profile '${ecprofile}' failed: $@\n" if $@;
587 if ($param->{remove_storages
}) {
589 foreach my $storeid (keys %$storages) {
590 # skip external clusters, not managed by pveceph
591 next if $storages->{$storeid}->{monhost
};
592 eval { PVE
::API2
::Storage
::Config-
>delete({storage
=> $storeid}) };
594 warn "failed to remove storage '$storeid': $@\n";
598 die "failed to remove (some) storages - check log and remove manually!\n"
602 return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker);
606 __PACKAGE__-
>register_method ({
610 description
=> "Change POOL settings",
614 check
=> ['perm', '/', [ 'Sys.Modify' ]],
617 additionalProperties
=> 0,
619 node
=> get_standard_option
('pve-node'),
620 %{ $ceph_pool_common_options->('nodefault') },
623 returns
=> { type
=> 'string' },
627 PVE
::Ceph
::Tools
::check_ceph_configured
();
629 my $rpcenv = PVE
::RPCEnvironment
::get
();
630 my $authuser = $rpcenv->get_user();
632 my $pool = extract_param
($param, 'name');
633 my $node = extract_param
($param, 'node');
635 # Ceph uses target_size_bytes
636 if (defined($param->{'target_size'})) {
637 my $target_sizestr = extract_param
($param, 'target_size');
638 $param->{target_size_bytes
} = PVE
::JSONSchema
::parse_size
($target_sizestr);
642 PVE
::Ceph
::Tools
::set_pool
($pool, $param);
645 return $rpcenv->fork_worker('cephsetpool', $pool, $authuser, $worker);
649 __PACKAGE__-
>register_method ({
653 description
=> "List pool settings.",
657 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
660 additionalProperties
=> 0,
662 node
=> get_standard_option
('pve-node'),
664 description
=> "The name of the pool. It must be unique.",
671 description
=> "If enabled, will display additional data".
679 id
=> { type
=> 'integer', title
=> 'ID' },
680 pgp_num
=> { type
=> 'integer', title
=> 'PGP num' },
681 noscrub
=> { type
=> 'boolean', title
=> 'noscrub' },
682 'nodeep-scrub' => { type
=> 'boolean', title
=> 'nodeep-scrub' },
683 nodelete
=> { type
=> 'boolean', title
=> 'nodelete' },
684 nopgchange
=> { type
=> 'boolean', title
=> 'nopgchange' },
685 nosizechange
=> { type
=> 'boolean', title
=> 'nosizechange' },
686 write_fadvise_dontneed
=> { type
=> 'boolean', title
=> 'write_fadvise_dontneed' },
687 hashpspool
=> { type
=> 'boolean', title
=> 'hashpspool' },
688 use_gmt_hitset
=> { type
=> 'boolean', title
=> 'use_gmt_hitset' },
689 fast_read
=> { type
=> 'boolean', title
=> 'Fast Read' },
690 application_list
=> { type
=> 'array', title
=> 'Application', optional
=> 1 },
691 statistics
=> { type
=> 'object', title
=> 'Statistics', optional
=> 1 },
692 autoscale_status
=> { type
=> 'object', title
=> 'Autoscale Status', optional
=> 1 },
693 %{ $ceph_pool_common_options->() },
699 PVE
::Ceph
::Tools
::check_ceph_inited
();
701 my $verbose = $param->{verbose
};
702 my $pool = $param->{name
};
704 my $rados = PVE
::RADOS-
>new();
705 my $res = $rados->mon_command({
706 prefix
=> 'osd pool get',
712 id
=> $res->{pool_id
},
714 size
=> $res->{size
},
715 min_size
=> $res->{min_size
},
716 pg_num
=> $res->{pg_num
},
717 pg_num_min
=> $res->{pg_num_min
},
718 pgp_num
=> $res->{pgp_num
},
719 crush_rule
=> $res->{crush_rule
},
720 pg_autoscale_mode
=> $res->{pg_autoscale_mode
},
721 noscrub
=> "$res->{noscrub}",
722 'nodeep-scrub' => "$res->{'nodeep-scrub'}",
723 nodelete
=> "$res->{nodelete}",
724 nopgchange
=> "$res->{nopgchange}",
725 nosizechange
=> "$res->{nosizechange}",
726 write_fadvise_dontneed
=> "$res->{write_fadvise_dontneed}",
727 hashpspool
=> "$res->{hashpspool}",
728 use_gmt_hitset
=> "$res->{use_gmt_hitset}",
729 fast_read
=> "$res->{fast_read}",
730 target_size
=> $res->{target_size_bytes
},
731 target_size_ratio
=> $res->{target_size_ratio
},
736 my $res = $rados->mon_command({ prefix
=> 'df' });
738 # pg_autoscaler module is not enabled in Nautilus
739 # avoid partial read further down, use new rados instance
740 my $autoscale_status = eval { $get_autoscale_status->() };
741 $data->{autoscale_status
} = $autoscale_status->{$pool};
743 foreach my $d (@{$res->{pools
}}) {
744 next if !$d->{stats
};
745 next if !defined($d->{name
}) && !$d->{name
} ne "$pool";
746 $data->{statistics
} = $d->{stats
};
749 my $apps = $rados->mon_command({ prefix
=> "osd pool application get", pool
=> "$pool", });
750 $data->{application_list
} = [ keys %$apps ];