all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Maximiliano Sandoval <m.sandoval@proxmox.com>
To: Kefu Chai <k.chai@proxmox.com>
Cc: pve-devel@lists.proxmox.com
Subject: Re: [PATCH manager 1/2] api: ceph: pool: destroypool: keep EC profiles pveceph did not create
Date: Wed, 21 Oct 2026 11:31:22 +0200	[thread overview]
Message-ID: <s8o1p9jv1qt.fsf@toolbox> (raw)
In-Reply-To: <20261021054425.3271742-2-k.chai@proxmox.com> (Kefu Chai's message of "Wed, 21 Oct 2026 13:44:24 +0800")

Kefu Chai <k.chai@proxmox.com> writes:

Comments below.

> destroypool removes the erasure code profile of the pool it destroys
> unless remove_ecprofile=0 is passed. When createpool made that profile
> itself, as pve_ec_<name> for the pool <name>-data, this cleans up after
> it. But a pool can also use an existing profile through the 'profile'
> option, such as 'default' or one built with the Ceph tools, and then
> destroying the pool removes that profile too once no other pool uses
> it.
>
> So by default only remove the profile createpool made for the pool
> being destroyed. An explicit remove_ecprofile=1 still removes any
> profile.
>
> While at it, test for a profile with length() rather than truthiness,
> so a profile named '0' still gets its CRUSH rule and profile cleaned
> up.
>
> Signed-off-by: Kefu Chai <k.chai@proxmox.com>
> ---
>  PVE/API2/Ceph/Pool.pm | 16 ++++---
>  PVE/Ceph/Tools.pm     |  6 +++
>  test/CephPool_test.pl | 97 +++++++++++++++++++++++++++++++++++++++++++
>  test/Makefile         |  6 ++-
>  4 files changed, 119 insertions(+), 6 deletions(-)
>  create mode 100755 test/CephPool_test.pl
>
> diff --git a/PVE/API2/Ceph/Pool.pm b/PVE/API2/Ceph/Pool.pm
> index 8fd11b82..1d6474e0 100644
> --- a/PVE/API2/Ceph/Pool.pm
> +++ b/PVE/API2/Ceph/Pool.pm
> @@ -574,11 +574,10 @@ __PACKAGE__->register_method({
>                  default => 0,
>              },
>              remove_ecprofile => {
> -                description =>
> -                    "Remove the erasure code profile. Defaults to true, if applicable.",
> +                description => "Remove the erasure code profile. Defaults to true for a"
> +                    . " profile pveceph created for the pool, and to false for any other.",

Minor nit: Perhaps:

"Remove the erasure code profile. Enabled by default for pools created
 during 'pveceph create', disabled otherwise."

>                  type => 'boolean',
>                  optional => 1,
> -                default => 1,
>              },
>          },
>      },
> @@ -618,16 +617,23 @@ __PACKAGE__->register_method({
>  
>              PVE::Ceph::Tools::destroy_pool($pool, $rados);
>  
> -            if (my $ecprofile = $pool_properties->{erasure_code_profile}) {
> +            my $ecprofile = $pool_properties->{erasure_code_profile};
> +            if (length($ecprofile)) { # a replicated pool has none, or an empty one

nit: Can this be written as follows?

# Replicated pool profiles have zero length
if (my $ecprofile = $pool_properties->{erasure_code_profile} && length($ecprofile)) {

>                  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) {
> +                my $remove = $param->{remove_ecprofile}
> +                    // PVE::Ceph::Tools::is_own_ecprofile($ecprofile, $pool);
> +                if ($remove) {
>                      print "destroying erasure coded profile '$ecprofile'\n";
>                      eval { PVE::Ceph::Tools::destroy_ecprofile($ecprofile, $rados) };
>                      warn "removing EC profile '${ecprofile}' failed: $@\n" if $@;
> +                } elsif (!defined($param->{remove_ecprofile})) {
> +                    print
> +                        "keeping erasure coded profile '$ecprofile', pveceph did not create it"
> +                        . " for this pool. Pass remove_ecprofile=1 to remove it.\n";

nit: Looks good to me, but perhaps this is a bit too late to mention this.

If the first incantation of pveceph pool destroy is called without
remove_ecprofile then the profile is not destroyed and repeating the
call with the remove_ecprofile=1 wont remove the profile as it exits
early, e.g.

root@c3-pve-201:~# ceph osd erasure-code-profile set test k=3 m=2
root@c3-pve-201:~# pveceph pool create test-ec --erasure-coding profile=test
[..]
root@c3-pve-201:~# pveceph pool destroy test-ec-data ; pveceph pool destroy test-ec-metadata
root@c3-pve-201:~# pveceph pool destroy test-ec-data  --remove_ecprofile 1
checking storage 'test-ec' for RBD images..
rbd error: rbd: listing images failed: (2) No such file or directory
root@c3-pve-201:~# ceph osd erasure-code-profile ls
test
default

>                  }
>              }
>  
> diff --git a/PVE/Ceph/Tools.pm b/PVE/Ceph/Tools.pm
> index 1ca0722d..cfd0c296 100644
> --- a/PVE/Ceph/Tools.pm
> +++ b/PVE/Ceph/Tools.pm
> @@ -691,6 +691,12 @@ sub get_ecprofile_name {
>      return "pve_ec_${name}";
>  }
>  
> +# whether createpool made $profile for the data pool $pool, which it names <name>-data
> +sub is_own_ecprofile {
> +    my ($profile, $pool) = @_;
> +    return $pool =~ m/^(.+)-data$/ && $profile eq get_ecprofile_name($1);
> +}
> +
>  sub destroy_crush_rule {
>      my ($rule, $rados) = @_;
>      $rados = PVE::RADOS->new() if !$rados;
> diff --git a/test/CephPool_test.pl b/test/CephPool_test.pl
> new file mode 100755
> index 00000000..730b712f
> --- /dev/null
> +++ b/test/CephPool_test.pl
> @@ -0,0 +1,97 @@
> +#!/usr/bin/perl
> +
> +use strict;
> +use warnings;

nit: New files could preferably use

use v5.36;

> +
> +use lib ('.', '..');
> +
> +use Test::More;
> +use PVE::API2::Ceph::Pool;
> +use PVE::Ceph::Tools;
> +
> +{
> +
> +    package PoolRPCEnvironment;
> +
> +    sub get_user { return 'root@pam'; }
> +
> +    sub check { }
> +
> +    sub fork_worker {
> +        my ($self, $type, $id, $user, $worker) = @_;
> +        $worker->('test-upid');
> +        return 'test-upid';
> +    }
> +}
> +
> +# call an API method, run its worker inline, and return what it printed
> +my $run = sub {
> +    my ($method, $param) = @_;
> +    my $out = '';
> +    open(my $stdout, '>', \$out) or die $!;
> +    local *STDOUT = $stdout;
> +    PVE::API2::Ceph::Pool->map_method_by_name($method)->{code}->($param);
> +    return $out;
> +};
> +
> +# --- the profile a pool owns --------------------------------------------------------------
> +my @own_cases = (
> +    # profile, pool, owned, name
> +    ['pve_ec_foo', 'foo-data', 1, 'the profile made for the pool is its own'],
> +    ['default', 'foo-data', 0, "'default' is not"],
> +    ['pve_ec_bar', 'foo-data', 0, "another pool's profile is not"],
> +    ['pve_ec_foo', 'foo', 0, "a pool without '-data' has no own profile"],
> +    ['pve_ec_x-data', 'x-data-data', 1, "only the last '-data' is stripped"],
> +);
> +for my $case (@own_cases) {
> +    my ($profile, $pool, $owned, $name) = @$case;
> +    is(PVE::Ceph::Tools::is_own_ecprofile($profile, $pool) ? 1 : 0, $owned, $name);
> +}
> +
> +# --- what destroypool removes -------------------------------------------------------------
> +{
> +    no warnings qw(redefine once);
> +    my ($props, @removed);
> +
> +    local *PVE::RPCEnvironment::get = sub { bless {}, 'PoolRPCEnvironment' };
> +    local *PVE::Ceph::Tools::check_ceph_inited = sub { };
> +    local *PVE::Storage::config = sub { { ids => {} } };
> +    local *PVE::RADOS::new = sub { 'rados' };
> +    local *PVE::Ceph::Tools::get_pool_properties = sub { $props };
> +    local *PVE::Ceph::Tools::destroy_pool = sub { };
> +    local *PVE::Ceph::Tools::destroy_crush_rule = sub { push @removed, "rule $_[0]" };
> +    local *PVE::Ceph::Tools::destroy_ecprofile = sub { push @removed, "profile $_[0]" };
> +
> +    my @cases = (
> +        # profile, extra parameters, what gets removed, whether it says it keeps one, name
> +        ['pve_ec_foo', {}, 'rule r, profile pve_ec_foo', 0, 'its own profile is removed'],
> +        ['default', {}, 'rule r', 1, 'a profile the user made is kept, and the log says so'],
> +        [
> +            'default',
> +            { remove_ecprofile => 1 },
> +            'rule r, profile default',
> +            0,
> +            'remove_ecprofile=1 removes any profile',
> +        ],
> +        [
> +            'pve_ec_foo',
> +            { remove_ecprofile => 0 },
> +            'rule r',
> +            0,
> +            'remove_ecprofile=0 keeps even its own profile',
> +        ],
> +        ['0', { remove_ecprofile => 1 }, 'rule r, profile 0', 0, "a profile named '0' counts"],
> +        [undef, {}, '', 0, 'a replicated pool has no profile to remove'],
> +        ['', {}, '', 0, 'nor has one that reports an empty profile'],
> +    );
> +    for my $case (@cases) {
> +        my ($profile, $extra, $expected, $keeps, $name) = @$case;
> +        $props = { crush_rule => 'r', erasure_code_profile => $profile };
> +        @removed = ();
> +        my $out = $run->('destroypool', { name => 'foo-data', %$extra });
> +        is(join(', ', @removed), $expected, $name);
> +        is($out =~ /keeping erasure coded profile/ ? 1 : 0, $keeps, "$name (log)");
> +    }
> +}
> +
> +done_testing();
> diff --git a/test/Makefile b/test/Makefile
> index 2dfd20a4..b8a3edde 100644
> --- a/test/Makefile
> +++ b/test/Makefile
> @@ -5,7 +5,7 @@ all:
>  export PERLLIB=..
>  
>  .PHONY: check
> -check: test-replication test-balloon test-vzdump test-osd test-ceph-health test-ceph-services test-ceph-auth test-ceph-key-migration test-ceph-lockbox-migration test-custom-cpu-models test-pvesh
> +check: test-replication test-balloon test-vzdump test-osd test-ceph-health test-ceph-pool test-ceph-services test-ceph-auth test-ceph-key-migration test-ceph-lockbox-migration test-custom-cpu-models test-pvesh
>  
>  .PHONY: test-balloon
>  test-balloon:
> @@ -36,6 +36,10 @@ test-osd:
>  test-ceph-health:
>  	./CephHealth_test.pl
>  
> +.PHONY: test-ceph-pool
> +test-ceph-pool:
> +	./CephPool_test.pl
> +
>  .PHONY: test-ceph-services
>  test-ceph-services:
>  	./CephServices_test.pl

-- 
Maximiliano




  reply	other threads:[~2026-10-21  9:31 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21  5:44 [PATCH manager 0/2] ceph: pool: create EC pools from an existing profile Kefu Chai
2026-10-21  5:44 ` [PATCH manager 1/2] api: ceph: pool: destroypool: keep EC profiles pveceph did not create Kefu Chai
2026-10-21  9:31   ` Maximiliano Sandoval [this message]
2026-10-21  5:44 ` [PATCH manager 2/2] api: ceph: pool: createpool: make 'k' and 'm' optional with a profile Kefu Chai
2026-10-21  9:52   ` Maximiliano Sandoval
2026-10-21  9:56 ` [PATCH manager 0/2] ceph: pool: create EC pools from an existing profile Maximiliano Sandoval

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=s8o1p9jv1qt.fsf@toolbox \
    --to=m.sandoval@proxmox.com \
    --cc=k.chai@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal