]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/API2/Ceph/Pools.pm
api: ceph: pools: get_storages: set pool name if missing
[pve-manager.git] / PVE / API2 / Ceph / Pools.pm
index 002f7893d4ca37f30e2492d47c20e63453e85fa0..6c05250e0926cdcb90a894ff7eef70fdaed3f39c 100644 (file)
@@ -5,7 +5,7 @@ use warnings;
 
 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;
@@ -66,6 +66,11 @@ __PACKAGE__->register_method ({
                    type => 'integer',
                    title => 'Size',
                },
+               type => {
+                   type => 'string',
+                   title => 'Type',
+                   enum => ['replicated', 'erasure', 'unknown'],
+               },
                min_size => {
                    type => 'integer',
                    title => 'Min Size',
@@ -185,6 +190,17 @@ __PACKAGE__->register_method ({
                $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;
        }
 
@@ -280,7 +296,7 @@ my $ceph_pool_common_options = sub {
 
 
 my $add_storage = sub {
-    my ($pool, $storeid) = @_;
+    my ($pool, $storeid, $ec_data_pool) = @_;
 
     my $storage_params = {
        type => 'rbd',
@@ -290,6 +306,8 @@ my $add_storage = sub {
        content => 'rootdir,images',
     };
 
+    $storage_params->{'data-pool'} = $ec_data_pool if $ec_data_pool;
+
     PVE::API2::Storage::Config->create($storage_params);
 };
 
@@ -302,19 +320,74 @@ my $get_storages = sub {
     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 => {
@@ -328,6 +401,15 @@ __PACKAGE__->register_method ({
                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->() },
        },
@@ -339,19 +421,22 @@ __PACKAGE__->register_method ({
        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"
@@ -366,12 +451,47 @@ __PACKAGE__->register_method ({
        $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 $@;
            }
        };
 
@@ -409,6 +529,12 @@ __PACKAGE__->register_method ({
                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' },
@@ -441,8 +567,24 @@ __PACKAGE__->register_method ({
                        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;