public inbox for pve-devel@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal