]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/Ceph/Tools.pm
ceph tools: set_pools: filter settings for erasure code pools
[pve-manager.git] / PVE / Ceph / Tools.pm
index 12d309be2892389b45f3d839300a9c3af5b00b1b..a1458b40547487f9c37fb65a0c6c81e7cf398eb9 100644 (file)
@@ -8,7 +8,7 @@ use File::Basename;
 use IO::File;
 use JSON;
 
-use PVE::Tools qw(run_command dir_glob_foreach);
+use PVE::Tools qw(run_command dir_glob_foreach extract_param);
 use PVE::Cluster qw(cfs_read_file);
 use PVE::RADOS;
 use PVE::Ceph::Services;
@@ -150,12 +150,16 @@ sub purge_all_ceph_services {
     }
 }
 
+sub ceph_install_flag_file { return '/run/pve-ceph-install-flag' };
+
 sub check_ceph_installed {
     my ($service, $noerr) = @_;
 
     $service = 'ceph_bin' if !defined($service);
 
-    if (! -x $ceph_service->{$service}) {
+    # NOTE: the flag file is checked as on a new installation, the binary gets
+    # extracted by dpkg before the installation is finished
+    if (! -x $ceph_service->{$service} || -f ceph_install_flag_file()) {
        die "binary not installed: $ceph_service->{$service}\n" if !$noerr;
        return undef;
     }
@@ -200,60 +204,105 @@ sub check_ceph_enabled {
     return 1;
 }
 
+my $set_pool_setting = sub {
+    my ($pool, $setting, $value, $rados) = @_;
+
+    my $command;
+    if ($setting eq 'application') {
+       $command = {
+           prefix => "osd pool application enable",
+           pool   => "$pool",
+           app    => "$value",
+       };
+    } else {
+       $command = {
+           prefix => "osd pool set",
+           pool   => "$pool",
+           var    => "$setting",
+           val    => "$value",
+           format => 'plain',
+       };
+    }
+
+    $rados = PVE::RADOS->new() if !$rados;
+    eval { $rados->mon_command($command); };
+    return $@ ? $@ : undef;
+};
+
 sub set_pool {
     my ($pool, $param) = @_;
 
-    foreach my $setting (keys %$param) {
-       my $value = $param->{$setting};
+    my $rados = PVE::RADOS->new();
 
-       my $command;
-       if ($setting eq 'application') {
-           $command = {
-               prefix => "osd pool application enable",
-               pool   => "$pool",
-               app    => "$value",
-           };
-       } else {
-           $command = {
-               prefix => "osd pool set",
-               pool   => "$pool",
-               var    => "$setting",
-               val    => "$value",
-               format => 'plain',
-           };
+    if (get_pool_type($pool, $rados) eq 'erasure') {
+       #remove parameters that cannot be changed for erasure coded pools
+       my $ignore_params = ['size', 'crush_rule'];
+       for my $setting (@$ignore_params) {
+           if ($param->{$setting}) {
+               print "cannot set '${setting}' for erasure coded pool\n";
+               delete $param->{$setting};
+           }
        }
+    }
+    # by default, pool size always resets min_size, so set it as first item
+    # https://tracker.ceph.com/issues/44862
+    my $keys = [ grep { $_ ne 'size' } sort keys %$param ];
+    unshift @$keys, 'size' if exists $param->{size};
+
+    for my $setting (@$keys) {
+       my $value = $param->{$setting};
 
-       my $rados = PVE::RADOS->new();
-       eval { $rados->mon_command($command); };
-       if ($@) {
-           print "$@";
+       print "pool $pool: applying $setting = $value\n";
+       if (my $err = $set_pool_setting->($pool, $setting, $value, $rados)) {
+           print "$err";
        } else {
            delete $param->{$setting};
        }
     }
 
-    if ((keys %$param) > 0) {
-       my @missing = join(', ', keys %$param );
-       die "Could not set: @missing\n";
+    if (scalar(keys %$param) > 0) {
+       my $missing = join(', ', sort keys %$param );
+       die "Could not set: $missing\n";
     }
 
 }
 
+sub get_pool_properties {
+    my ($pool, $rados) = @_;
+    $rados = PVE::RADOS->new() if !defined($rados);
+    my $command = {
+       prefix => "osd pool get",
+       pool   => "$pool",
+       var    => "all",
+       format => 'json',
+    };
+    return $rados->mon_command($command);
+}
+
+sub get_pool_type {
+    my ($pool, $rados) = @_;
+    $rados = PVE::RADOS->new() if !defined($rados);
+    return 'erasure' if get_pool_properties($pool, $rados)->{erasure_code_profile};
+    return 'replicated';
+}
+
 sub create_pool {
     my ($pool, $param, $rados) = @_;
-
-    if (!defined($rados)) {
-       $rados = PVE::RADOS->new();
-    }
+    $rados = PVE::RADOS->new() if !defined($rados);
 
     my $pg_num = $param->{pg_num} || 128;
 
-    $rados->mon_command({
+    my $mon_params = {
        prefix => "osd pool create",
        pool => $pool,
        pg_num => int($pg_num),
        format => 'plain',
-    });
+    };
+    $mon_params->{pool_type} = extract_param($param, 'pool_type') if $param->{pool_type};
+    $mon_params->{erasure_code_profile} = extract_param($param, 'erasure_code_profile')
+       if $param->{erasure_code_profile};
+
+    $rados->mon_command($mon_params);
 
     set_pool($pool, $param);
 
@@ -261,10 +310,7 @@ sub create_pool {
 
 sub ls_pools {
     my ($pool, $rados) = @_;
-
-    if (!defined($rados)) {
-       $rados = PVE::RADOS->new();
-    }
+    $rados = PVE::RADOS->new() if !defined($rados);
 
     my $res = $rados->mon_command({ prefix => "osd lspools" });
 
@@ -273,10 +319,7 @@ sub ls_pools {
 
 sub destroy_pool {
     my ($pool, $rados) = @_;
-
-    if (!defined($rados)) {
-       $rados = PVE::RADOS->new();
-    }
+    $rados = PVE::RADOS->new() if !defined($rados);
 
     # fixme: '--yes-i-really-really-mean-it'
     $rados->mon_command({
@@ -288,11 +331,56 @@ sub destroy_pool {
     });
 }
 
+# we get something like:
+#[{
+#   'metadata_pool_id' => 2,
+#   'data_pool_ids' => [ 1 ],
+#   'metadata_pool' => 'cephfs_metadata',
+#   'data_pools' => [ 'cephfs_data' ],
+#   'name' => 'cephfs',
+#}]
+sub ls_fs {
+    my ($rados) = @_;
+    $rados = PVE::RADOS->new() if !defined($rados);
+
+    my $res = $rados->mon_command({ prefix => "fs ls" });
+
+    return $res;
+}
+
+sub create_fs {
+    my ($fs, $param, $rados) = @_;
+
+    if (!defined($rados)) {
+       $rados = PVE::RADOS->new();
+    }
+
+    $rados->mon_command({
+       prefix => "fs new",
+       fs_name => $fs,
+       metadata => $param->{pool_metadata},
+       data => $param->{pool_data},
+       format => 'plain',
+    });
+}
+
+sub destroy_fs {
+    my ($fs, $rados) = @_;
+    $rados = PVE::RADOS->new() if !defined($rados);
+
+    $rados->mon_command({
+       prefix => "fs rm",
+       fs_name => $fs,
+       'yes_i_really_mean_it' => JSON::true,
+       format => 'plain',
+    });
+}
+
 sub setup_pve_symlinks {
     # fail if we find a real file instead of a link
     if (-f $ceph_cfgpath) {
        my $lnk = readlink($ceph_cfgpath);
-       die "file '$ceph_cfgpath' already exists\n"
+       die "file '$ceph_cfgpath' already exists and is not a symlink to $pve_ceph_cfgpath\n"
            if !$lnk || $lnk ne $pve_ceph_cfgpath;
     } else {
        mkdir $ceph_cfgdir;
@@ -321,29 +409,6 @@ sub get_or_create_admin_keyring {
     return $pve_ckeyring_path;
 }
 
-# wipe the first 200 MB to clear off leftovers from previous use, otherwise a
-# create OSD fails.
-sub wipe_disks {
-    my (@devs) = @_;
-
-    my @wipe_cmd = qw(/bin/dd if=/dev/zero bs=1M conv=fdatasync);
-
-    foreach my $devpath (@devs) {
-       my $devname = basename($devpath);
-       my $dev_size = PVE::Tools::file_get_contents("/sys/class/block/$devname/size");
-
-       ($dev_size) = $dev_size =~ m|(\d+)|; # untaint $dev_size
-       die "Coulnd't get the size of the device $devname\n" if (!defined($dev_size));
-
-       my $size = ($dev_size * 512 / 1024 / 1024);
-       my $count = ($size < 200) ? $size : 200;
-
-       print "wipe disk/partition: $devpath\n";
-       eval { run_command([@wipe_cmd, "count=$count", "of=${devpath}"]) };
-       warn $@ if $@;
-    }
-};
-
 # get ceph-volume managed osds
 sub ceph_volume_list {
     my $result = {};
@@ -487,4 +552,64 @@ sub ceph_cluster_status {
     return $status;
 }
 
+sub ecprofile_exists {
+    my ($name, $rados) = @_;
+    $rados = PVE::RADOS->new() if !$rados;
+
+    my $res = $rados->mon_command({ prefix => 'osd erasure-code-profile ls' });
+
+    my $profiles = { map { $_ => 1 } @$res };
+    return $profiles->{$name};
+}
+
+sub create_ecprofile {
+    my ($name, $k, $m, $failure_domain, $device_class, $rados) = @_;
+    $rados = PVE::RADOS->new() if !$rados;
+
+    $failure_domain = 'host' if !$failure_domain;
+
+    my $profile = [
+       "crush-failure-domain=${failure_domain}",
+       "k=${k}",
+       "m=${m}",
+    ];
+
+    push(@$profile, "crush-device-class=${device_class}") if $device_class;
+
+    $rados->mon_command({
+       prefix => 'osd erasure-code-profile set',
+       name => $name,
+       profile => $profile,
+    });
+}
+
+sub destroy_ecprofile {
+    my ($profile, $rados) = @_;
+    $rados = PVE::RADOS->new() if !$rados;
+
+    my $command = {
+       prefix => 'osd erasure-code-profile rm',
+       name => $profile,
+       format => 'plain',
+    };
+    return $rados->mon_command($command);
+}
+
+sub get_ecprofile_name {
+    my ($name) = @_;
+    return "pve_ec_${name}";
+}
+
+sub destroy_crush_rule {
+    my ($rule, $rados) = @_;
+    $rados = PVE::RADOS->new() if !$rados;
+
+    my $command = {
+       prefix => 'osd crush rule rm',
+       name => $rule,
+       format => 'plain',
+    };
+    return $rados->mon_command($command);
+}
+
 1;