use PVE::Ceph::Tools;
use PVE::Ceph::Services;
-use PVE::JSONSchema qw(get_standard_option);
+use PVE::JSONSchema qw(get_standard_option parse_property_string);
use PVE::RADOS;
use PVE::RESTHandler;
use PVE::RPCEnvironment;
type => 'integer',
title => 'Size',
},
+ type => {
+ type => 'string',
+ title => 'Type',
+ enum => ['replicated', 'erasure', 'unknown'],
+ },
min_size => {
type => 'integer',
title => 'Min Size',
$d->{bytes_used} = $s->{bytes_used};
$d->{percent_used} = $s->{percent_used};
}
+
+ # Cephs numerical pool types are barely documented. Found the following in the Ceph
+ # codebase: https://github.com/ceph/ceph/blob/ff144995a849407c258bcb763daa3e03cfce5059/src/osd/osd_types.h#L1221-L1233
+ if ($e->{type} == 1) {
+ $d->{type} = 'replicated';
+ } elsif ($e->{type} == 3) {
+ $d->{type} = 'erasure';
+ } else {
+ # we should never get here, but better be safe
+ $d->{type} = 'unknown';
+ }
push @$data, $d;
}
my $add_storage = sub {
- my ($pool, $storeid) = @_;
+ my ($pool, $storeid, $ec_data_pool) = @_;
my $storage_params = {
type => 'rbd',
content => 'rootdir,images',
};
+ $storage_params->{'data-pool'} = $ec_data_pool if $ec_data_pool;
+
PVE::API2::Storage::Config->create($storage_params);
};
my $res = {};
foreach my $storeid (keys %$storages) {
my $curr = $storages->{$storeid};
- $res->{$storeid} = $storages->{$storeid}
- if $curr->{type} eq 'rbd' && $pool eq $curr->{pool};
+ next if $curr->{type} ne 'rbd';
+ $curr->{pool} = 'rbd' if !defined $curr->{pool}; # set default
+ if (
+ $pool eq $curr->{pool} ||
+ (defined $curr->{'data-pool'} && $pool eq $curr->{'data-pool'})
+ ) {
+ $res->{$storeid} = $storages->{$storeid};
+ }
}
return $res;
};
+my $ec_format = {
+ k => {
+ type => 'integer',
+ description => "Number of data chunks. Will create an erasure coded pool plus a"
+ ." replicated pool for metadata.",
+ minimum => 2,
+ },
+ m => {
+ type => 'integer',
+ description => "Number of coding chunks. Will create an erasure coded pool plus a"
+ ." replicated pool for metadata.",
+ minimum => 1,
+ },
+ 'failure-domain' => {
+ type => 'string',
+ description => "CRUSH failure domain. Default is 'host'. Will create an erasure"
+ ." coded pool plus a replicated pool for metadata.",
+ format_description => 'domain',
+ optional => 1,
+ default => 'host',
+ },
+ 'device-class' => {
+ type => 'string',
+ description => "CRUSH device class. Will create an erasure coded pool plus a"
+ ." replicated pool for metadata.",
+ format_description => 'class',
+ optional => 1,
+ },
+ profile => {
+ description => "Override the erasure code (EC) profile to use. Will create an"
+ ." erasure coded pool plus a replicated pool for metadata.",
+ type => 'string',
+ format_description => 'profile',
+ optional => 1,
+ },
+};
+
+sub ec_parse_and_check {
+ my ($property, $rados) = @_;
+ return if !$property;
+
+ my $ec = parse_property_string($ec_format, $property);
+
+ die "Erasure code profile '$ec->{profile}' does not exist.\n"
+ if $ec->{profile} && !PVE::Ceph::Tools::ecprofile_exists($ec->{profile}, $rados);
+
+ return $ec;
+}
+
__PACKAGE__->register_method ({
name => 'createpool',
path => '',
method => 'POST',
- description => "Create POOL",
+ description => "Create Ceph pool",
proxyto => 'node',
protected => 1,
permissions => {
description => "Configure VM and CT storage using the new pool.",
type => 'boolean',
optional => 1,
+ default => "0; for erasure coded pools: 1",
+ },
+ 'erasure-coding' => {
+ description => "Create an erasure coded pool for RBD with an accompaning"
+ ." replicated pool for metadata storage. With EC, the common ceph options 'size',"
+ ." 'min_size' and 'crush_rule' parameters will be applied to the metadata pool.",
+ type => 'string',
+ format => $ec_format,
+ optional => 1,
},
%{ $ceph_pool_common_options->() },
},
PVE::Cluster::check_cfs_quorum();
PVE::Ceph::Tools::check_ceph_configured();
- my $pool = extract_param($param, 'name');
+ my $pool = my $name = extract_param($param, 'name');
my $node = extract_param($param, 'node');
my $add_storages = extract_param($param, 'add_storages');
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
-
# Ceph uses target_size_bytes
if (defined($param->{'target_size'})) {
my $target_sizestr = extract_param($param, 'target_size');
$param->{target_size_bytes} = PVE::JSONSchema::parse_size($target_sizestr);
}
+ my $rados = PVE::RADOS->new();
+ my $ec = ec_parse_and_check(extract_param($param, 'erasure-coding'), $rados);
+ $add_storages = 1 if $ec && !defined($add_storages);
+
if ($add_storages) {
$rpcenv->check($user, '/storage', ['Datastore.Allocate']);
die "pool name contains characters which are illegal for storage naming\n"
$param->{pg_autoscale_mode} //= 'warn';
my $worker = sub {
+ # reopen with longer timeout
+ $rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
+
+ if ($ec) {
+ if (!$ec->{profile}) {
+ $ec->{profile} = PVE::Ceph::Tools::get_ecprofile_name($pool, $rados);
+ eval {
+ PVE::Ceph::Tools::create_ecprofile(
+ $ec->@{'profile', 'k', 'm', 'failure-domain', 'device-class'},
+ $rados,
+ );
+ };
+ die "could not create erasure code profile '$ec->{profile}': $@\n" if $@;
+ print "created new erasure code profile '$ec->{profile}'\n";
+ }
- PVE::Ceph::Tools::create_pool($pool, $param);
+ my $ec_data_param = {};
+ # copy all params, should be a flat hash
+ $ec_data_param = { map { $_ => $param->{$_} } keys %$param };
+
+ $ec_data_param->{pool_type} = 'erasure';
+ $ec_data_param->{allow_ec_overwrites} = 'true';
+ $ec_data_param->{erasure_code_profile} = $ec->{profile};
+ delete $ec_data_param->{size};
+ delete $ec_data_param->{min_size};
+ delete $ec_data_param->{crush_rule};
+
+ # metadata pool should be ok with 32 PGs
+ $param->{pg_num} = 32;
+
+ $pool = "${name}-metadata";
+ $ec->{data_pool} = "${name}-data";
+
+ PVE::Ceph::Tools::create_pool($ec->{data_pool}, $ec_data_param, $rados);
+ }
+
+ PVE::Ceph::Tools::create_pool($pool, $param, $rados);
if ($add_storages) {
- eval { $add_storage->($pool, "${pool}") };
- die "adding PVE storage for ceph pool '$pool' failed: $@\n" if $@;
+ eval { $add_storage->($pool, "${name}", $ec->{data_pool}) };
+ die "adding PVE storage for ceph pool '$name' failed: $@\n" if $@;
}
};
optional => 1,
default => 0,
},
+ remove_ecprofile => {
+ description => "Remove the erasure code profile. Defaults to true, if applicable.",
+ type => 'boolean',
+ optional => 1,
+ default => 1,
+ },
},
},
returns => { type => 'string' },
if @{$res->{$storeid}} != 0;
}
}
+ my $rados = PVE::RADOS->new();
+
+ my $pool_properties = PVE::Ceph::Tools::get_pool_properties($pool, $rados);
- PVE::Ceph::Tools::destroy_pool($pool);
+ PVE::Ceph::Tools::destroy_pool($pool, $rados);
+
+ if (my $ecprofile = $pool_properties->{erasure_code_profile}) {
+ print "found erasure coded profile '$ecprofile', destroying its CRUSH rule\n";
+ my $crush_rule = $pool_properties->{crush_rule};
+ eval { PVE::Ceph::Tools::destroy_crush_rule($crush_rule, $rados); };
+ warn "removing crush rule '${crush_rule}' failed: $@\n" if $@;
+
+ if ($param->{remove_ecprofile} // 1) {
+ print "destroying erasure coded profile '$ecprofile'\n";
+ eval { PVE::Ceph::Tools::destroy_ecprofile($ecprofile, $rados) };
+ warn "removing EC profile '${ecprofile}' failed: $@\n" if $@;
+ }
+ }
if ($param->{remove_storages}) {
my $err;