]> 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 014e6be7fb394b6dffdbec5f59cd54f94ba21ea6..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;
@@ -26,7 +26,6 @@ my $get_autoscale_status = sub {
 
     my $data;
     foreach my $p (@$autoscale) {
-       $p->{would_adjust} = "$p->{would_adjust}"; # boolean
        $data->{$p->{pool_name}} = $p;
     }
 
@@ -55,21 +54,77 @@ __PACKAGE__->register_method ({
        items => {
            type => "object",
            properties => {
-               pool              => { type => 'integer', title => 'ID' },
-               pool_name         => { type => 'string',  title => 'Name' },
-               size              => { type => 'integer', title => 'Size' },
-               min_size          => { type => 'integer', title => 'Min Size' },
-               pg_num            => { type => 'integer', title => 'PG Num' },
-               pg_num_min        => { type => 'integer', title => 'min. PG Num', optional => 1, },
-               pg_num_final      => { type => 'integer', title => 'Optimal PG Num', optional => 1, },
-               pg_autoscale_mode => { type => 'string',  title => 'PG Autoscale Mode', optional => 1, },
-               crush_rule        => { type => 'integer', title => 'Crush Rule' },
-               crush_rule_name   => { type => 'string',  title => 'Crush Rule Name' },
-               percent_used      => { type => 'number',  title => '%-Used' },
-               bytes_used        => { type => 'integer', title => 'Used' },
-               target_size       => { type => 'integer', title => 'PG Autoscale Target Size', optional => 1 },
-               target_size_ratio => { type => 'number',  title => 'PG Autoscale Target Ratio',optional => 1, },
-               autoscale_status  => { type => 'object',  title => 'Autoscale Status', optional => 1 },
+               pool => {
+                   type => 'integer',
+                   title => 'ID',
+               },
+               pool_name => {
+                   type => 'string',
+                   title => 'Name',
+               },
+               size => {
+                   type => 'integer',
+                   title => 'Size',
+               },
+               type => {
+                   type => 'string',
+                   title => 'Type',
+                   enum => ['replicated', 'erasure', 'unknown'],
+               },
+               min_size => {
+                   type => 'integer',
+                   title => 'Min Size',
+               },
+               pg_num => {
+                   type => 'integer',
+                   title => 'PG Num',
+               },
+               pg_num_min => {
+                   type => 'integer',
+                   title => 'min. PG Num',
+                   optional => 1,
+               },
+               pg_num_final => {
+                   type => 'integer',
+                   title => 'Optimal PG Num',
+                   optional => 1,
+               },
+               pg_autoscale_mode => {
+                   type => 'string',
+                   title => 'PG Autoscale Mode',
+                   optional => 1,
+               },
+               crush_rule => {
+                   type => 'integer',
+                   title => 'Crush Rule',
+               },
+               crush_rule_name => {
+                   type => 'string',
+                   title => 'Crush Rule Name',
+               },
+               percent_used => {
+                   type => 'number',
+                   title => '%-Used',
+               },
+               bytes_used => {
+                   type => 'integer',
+                   title => 'Used',
+               },
+               target_size => {
+                   type => 'integer',
+                   title => 'PG Autoscale Target Size',
+                   optional => 1,
+               },
+               target_size_ratio => {
+                   type => 'number',
+                   title => 'PG Autoscale Target Ratio',
+                   optional => 1,
+               },
+               autoscale_status => {
+                   type => 'object',
+                   title => 'Autoscale Status',
+                   optional => 1,
+               },
            },
        },
        links => [ { rel => 'child', href => "{pool_name}" } ],
@@ -135,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;
        }
 
@@ -175,7 +241,7 @@ my $ceph_pool_common_options = sub {
            type => 'integer',
            default => 128,
            optional => 1,
-           minimum => 8,
+           minimum => 1,
            maximum => 32768,
        },
        pg_num_min => {
@@ -230,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',
@@ -240,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);
 };
 
@@ -252,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 => {
@@ -278,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->() },
        },
@@ -289,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"
@@ -316,18 +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";
+               }
+
+               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};
 
-           PVE::Ceph::Tools::create_pool($pool, $param);
+               # 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) {
-               my $err;
-               eval { $add_storage->($pool, "${pool}"); };
-               if ($@) {
-                   warn "failed to add storage: $@";
-                   $err = 1;
-               }
-               die "adding storage for pool '$pool' failed, check log and add manually!\n"
-                   if $err;
+               eval { $add_storage->($pool, "${name}", $ec->{data_pool}) };
+               die "adding PVE storage for ceph pool '$name' failed: $@\n" if $@;
            }
        };
 
@@ -365,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' },
@@ -397,8 +567,24 @@ __PACKAGE__->register_method ({
                        if @{$res->{$storeid}} != 0;
                }
            }
+           my $rados = PVE::RADOS->new();
 
-           PVE::Ceph::Tools::destroy_pool($pool);
+           my $pool_properties = PVE::Ceph::Tools::get_pool_properties($pool, $rados);
+
+           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;