From 7ec6cb6aeff1a569361531d57efe9dc63abe4e1e Mon Sep 17 00:00:00 2001 From: Oded Viner Date: Wed, 18 Mar 2026 23:28:27 +0200 Subject: [PATCH] pool: clean up erasure code profile on pool deletion deleting an erasure coded CephBlockPool left the ec profile orphaned. recreating with different settings failed because the old profile could not be overridden. - delete ec profile during CephBlockPool deletion - disable --format json on ec profile set command - fix ec profile name mismatch in CephObjectStore deletion Signed-off-by: Oded Viner (cherry picked from commit b88648d10ff7a383fb9e3d4c05f39be3397033a8) --- .../ceph/client/erasure-code-profile.go | 4 +- pkg/operator/ceph/object/objectstore.go | 4 +- pkg/operator/ceph/object/objectstore_test.go | 6 +-- pkg/operator/ceph/pool/controller.go | 10 +++++ pkg/operator/ceph/pool/controller_test.go | 39 +++++++++++++++++++ 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/pkg/daemon/ceph/client/erasure-code-profile.go b/pkg/daemon/ceph/client/erasure-code-profile.go index 16579af81..115e65c22 100644 --- a/pkg/daemon/ceph/client/erasure-code-profile.go +++ b/pkg/daemon/ceph/client/erasure-code-profile.go @@ -95,7 +95,9 @@ func CreateErasureCodeProfile(context *clusterd.Context, clusterInfo *ClusterInf args := []string{"osd", "erasure-code-profile", "set", profileName, "--force"} args = append(args, profilePairs...) - _, err = NewCephCommand(context, clusterInfo, args).Run() + cmd := NewCephCommand(context, clusterInfo, args) + cmd.JsonOutput = false + _, err = cmd.Run() if err != nil { return errors.Wrap(err, "failed to set ec-profile") } diff --git a/pkg/operator/ceph/object/objectstore.go b/pkg/operator/ceph/object/objectstore.go index 2c12fa48b..f404e3281 100644 --- a/pkg/operator/ceph/object/objectstore.go +++ b/pkg/operator/ceph/object/objectstore.go @@ -725,7 +725,9 @@ func DeletePools(ctx *Context, lastStore bool, poolPrefix string) error { return errors.Wrapf(err, "failed to list erasure code profiles for cluster %s", ctx.clusterInfo.Namespace) } // cleans up the EC profile for the data pool only. Metadata pools don't support EC (only replication is supported). - ecProfileName := cephclient.GetErasureCodeProfileForPool(ctx.Name) + // The profile name must match the full pool name used during creation (e.g., ".rgw.buckets.data_ecprofile"). + dataPool := poolName(poolPrefix, dataPoolName) + ecProfileName := cephclient.GetErasureCodeProfileForPool(dataPool) for i := range erasureCodes { if erasureCodes[i] == ecProfileName { if err := cephclient.DeleteErasureCodeProfile(ctx.Context, ctx.clusterInfo, ecProfileName); err != nil { diff --git a/pkg/operator/ceph/object/objectstore_test.go b/pkg/operator/ceph/object/objectstore_test.go index 3c3b97330..9d86b7f71 100644 --- a/pkg/operator/ceph/object/objectstore_test.go +++ b/pkg/operator/ceph/object/objectstore_test.go @@ -431,13 +431,13 @@ func deleteStore(t *testing.T, name string, existingStores string, expectedDelet } if args[1] == "erasure-code-profile" { if args[2] == "ls" { - return `["default","myobj_ecprofile"]`, nil + return `["default","myobj.rgw.buckets.data_ecprofile"]`, nil } if args[2] == "rm" { - if args[3] == "myobj_ecprofile" { + if args[3] == "myobj.rgw.buckets.data_ecprofile" { deletedErasureCodeProfile = true } else { - assert.Fail(t, fmt.Sprintf("the erasure code profile to be deleted should be myobj_ecprofile. Actual: %s ", args[3])) + assert.Fail(t, fmt.Sprintf("the erasure code profile to be deleted should be myobj.rgw.buckets.data_ecprofile. Actual: %s ", args[3])) } return "", nil } diff --git a/pkg/operator/ceph/pool/controller.go b/pkg/operator/ceph/pool/controller.go index 89320a4e7..31d4a5fbf 100644 --- a/pkg/operator/ceph/pool/controller.go +++ b/pkg/operator/ceph/pool/controller.go @@ -589,6 +589,16 @@ func deletePool(context *clusterd.Context, clusterInfo *cephclient.ClusterInfo, return errors.Wrapf(err, "failed to delete pool %q", p.Name) } } + + // Clean up the erasure code profile for EC pools so that recreating with + // different settings does not fail + if p.IsErasureCoded() { + ecProfileName := cephclient.GetErasureCodeProfileForPool(p.Name) + if err := cephclient.DeleteErasureCodeProfile(context, clusterInfo, ecProfileName); err != nil { + logger.Warningf("failed to delete erasure code profile %q for pool %q: %v", ecProfileName, p.Name, err) + } + } + return nil } diff --git a/pkg/operator/ceph/pool/controller_test.go b/pkg/operator/ceph/pool/controller_test.go index 1cb132945..b7b671327 100644 --- a/pkg/operator/ceph/pool/controller_test.go +++ b/pkg/operator/ceph/pool/controller_test.go @@ -200,6 +200,45 @@ func TestDeletePool(t *testing.T) { p = &cephv1.NamedPoolSpec{Name: "mypool"} err = deletePool(context, clusterInfo, p) assert.Error(t, err) + + // delete an erasure coded pool should also delete the EC profile + ecProfileDeleted := false + failOnDelete = false + ecExecutor := &exectest.MockExecutor{ + MockExecuteCommandWithOutput: func(command string, args ...string) (string, error) { + emptyPool := "{\"images\":{\"count\":0,\"provisioned_bytes\":0,\"snap_count\":0},\"trash\":{\"count\":1,\"provisioned_bytes\":2048,\"snap_count\":0}}" + if command == "ceph" && args[1] == "lspools" { + return `[{"poolnum":1,"poolname":"ecpool"}]`, nil + } else if command == "ceph" && args[1] == "pool" && args[2] == "get" { + return `{"pool": "ecpool","pool_id": 1,"size":1}`, nil + } else if command == "ceph" && args[1] == "pool" && args[2] == "delete" { + return "", nil + } else if command == "ceph" && args[1] == "erasure-code-profile" && args[2] == "rm" { + assert.Equal(t, "ecpool_ecprofile", args[3]) + ecProfileDeleted = true + return "", nil + } else if args[0] == "pool" { + if args[1] == "stats" { + return emptyPool, nil + } + return "", errors.Errorf("rbd: error opening pool %q: (2) No such file or directory", args[3]) + } + return "", errors.Errorf("unexpected rbd command %q", args) + }, + } + ecContext := &clusterd.Context{Executor: ecExecutor} + ecPool := &cephv1.NamedPoolSpec{ + Name: "ecpool", + PoolSpec: cephv1.PoolSpec{ + ErasureCoded: cephv1.ErasureCodedSpec{ + DataChunks: 2, + CodingChunks: 1, + }, + }, + } + err = deletePool(ecContext, clusterInfo, ecPool) + assert.NoError(t, err) + assert.True(t, ecProfileDeleted) } // TestCephBlockPoolController runs ReconcileCephBlockPool.Reconcile() against a