Repository: ceph/ceph-csi
Pull request #5206: rbd: use ListChildrenAttributes() instead of ListChildren()
Author: login: Rakshith-R
Labels: name: component/rbd
description: Issues related to RBD
Created: 2025-03-10T10:24:10Z | Merged: 2025-03-12T08:51:02Z

# Describe what this PR does #

This commit modifies listSnapAndChildren() to make use of ListChildrenAttributes() instead of ListChildren() which allows us to filter out images in trash.
This commit also order the alive images so that temp clone images are followed by images backing volume snapshots so that temp clone images are flattened first.

Is the change backward compatible?
Yes

Are there concerns around backward compatibility?
No

## Related issues ##

Fixes: #5173 

**Checklist:**

* [ ] **Commit Message Formatting**: Commit titles and messages follow
  guidelines in the [developer
  guide](https://github.com/ceph/ceph-csi/blob/devel/docs/development-guide.md#commit-messages).
* [ ] Reviewed the developer guide on [Submitting a Pull
  Request](https://github.com/ceph/ceph-csi/blob/devel/docs/development-guide.md#development-workflow)
* [ ] [Pending release
  notes](https://github.com/ceph/ceph-csi/blob/devel/PendingReleaseNotes.md)
  updated with breaking and/or notable changes for the next major release.
* [ ] Documentation has been updated, if necessary.
* [ ] Unit tests have been added, if necessary.
* [ ] Integration tests have been added, if necessary.

---

<details>
<summary>Show available bot commands</summary>

These commands are normally not required, but in case of issues, leave any of
the following bot commands in an otherwise empty comment in this PR:

* `/retest ci/centos/<job-name>`: retest the `<job-name>` after unrelated
  failure (please report the failure too!)

</details>



Diff:
diff --git a/go.mod b/go.mod
index 95f35b8182f..0a18db35be6 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
 	github.com/aws/aws-sdk-go v1.55.6
 	github.com/aws/aws-sdk-go-v2/service/sts v1.33.17
 	github.com/ceph/ceph-csi/api v0.0.0-00010101000000-000000000000
-	github.com/ceph/go-ceph v0.32.0
+	github.com/ceph/go-ceph v0.32.1-0.20250307053135-38b9676b1d4e
 	github.com/container-storage-interface/spec v1.11.0
 	github.com/csi-addons/kubernetes-csi-addons v0.12.0
 	github.com/csi-addons/spec v0.2.1-0.20241104111131-27825f744db5
@@ -78,7 +78,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
-	github.com/aws/smithy-go v1.22.2 // indirect
+	github.com/aws/smithy-go v1.22.3 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/blang/semver/v4 v4.0.0 // indirect
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
diff --git a/go.sum b/go.sum
index ff9ea9ebb04..9d91eb5b169 100644
--- a/go.sum
+++ b/go.sum
@@ -122,6 +122,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5
 github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
 github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
 github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
+github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
+github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -136,6 +138,10 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/ceph/go-ceph v0.32.0 h1:iXRUGdPmH7h9Vf/WA1Dg3Wo1tgL7gcUbylfpbxrlGLs=
 github.com/ceph/go-ceph v0.32.0/go.mod h1:42eoJzyLS3VREzqrg2ot44NtuluQZi55hFRSoLF36GQ=
+github.com/ceph/go-ceph v0.32.1-0.20250303071035-7740b94e7f49 h1:0q5Ye65vYS5szvVsH5VILBGTv80p6v+znzPHsjiheDg=
+github.com/ceph/go-ceph v0.32.1-0.20250303071035-7740b94e7f49/go.mod h1:FtN9DMlp/aS7VuO0VXdpdFfaetHNwtvn5q8gIhW/hQ4=
+github.com/ceph/go-ceph v0.32.1-0.20250307053135-38b9676b1d4e h1:Ykum5wAFYzyUi+yW0qQFDsbP9pAuRKTOf+ttMoo1jZ4=
+github.com/ceph/go-ceph v0.32.1-0.20250307053135-38b9676b1d4e/go.mod h1:bIS41nqhGmD7gQVoSBu3IYCwCiVEMeIxFU5qaqmC4a8=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
diff --git a/internal/rbd/clone.go b/internal/rbd/clone.go
index bcf801c4520..648d85621d5 100644
--- a/internal/rbd/clone.go
+++ b/internal/rbd/clone.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"strings"
 
 	"github.com/ceph/ceph-csi/internal/util"
 	"github.com/ceph/ceph-csi/internal/util/k8s"
@@ -122,7 +123,7 @@ func (rv *rbdVolume) generateTempClone() *rbdVolume {
 	// The temp cloned image name will be always (rbd image name + "-temp")
 	// this name will be always unique, as cephcsi never creates an image with
 	// this format for new rbd images
-	tempClone.RbdImageName = rv.RbdImageName + "-temp"
+	tempClone.RbdImageName = rv.RbdImageName + tempImageSuffix
 
 	return &tempClone
 }
@@ -250,3 +251,9 @@ func (rv *rbdVolume) doSnapClone(ctx context.Context, parentVol *rbdVolume) erro
 
 	return nil
 }
+
+// isTempClonedImage checks whether the image is a temporary cloned image
+// by checking the suffix of the image name.
+func isTempClonedImage(imageName string) bool {
+	return strings.HasSuffix(imageName, tempImageSuffix)
+}
diff --git a/internal/rbd/controllerserver.go b/internal/rbd/controllerserver.go
index 846bd7af956..15f49a14555 100644
--- a/internal/rbd/controllerserver.go
+++ b/internal/rbd/controllerserver.go
@@ -590,7 +590,7 @@ func (cs *ControllerServer) repairExistingVolume(ctx context.Context, req *csi.C
 // are more than the `minSnapshotOnImage` Add a task to flatten all the
 // temporary cloned images.
 func flattenTemporaryClonedImages(ctx context.Context, rbdVol *rbdVolume, cr *util.Credentials) error {
-	snaps, children, err := rbdVol.listSnapAndChildren()
+	snapAndChildrenInfo, err := rbdVol.listSnapAndChildren()
 	if err != nil {
 		if errors.Is(err, util.ErrImageNotFound) {
 			return status.Error(codes.InvalidArgument, err.Error())
@@ -598,6 +598,17 @@ func flattenTemporaryClonedImages(ctx context.Context, rbdVol *rbdVolume, cr *ut
 
 		return status.Error(codes.Internal, err.Error())
 	}
+	children := make([]string, 0)
+	// order the temp clone images first followed by the volume snapshot
+	// images so that the temp clone images are flattened first.
+	// This is done in order to:
+	// - increase number of snapshots which can be supported for
+	// changed block tracking(rbd snap diff).
+	// - favor scalability since multiple PVCs can be restored from same
+	// volumesnapshot versus just one PVC-PVC clone.
+	children = append(children, snapAndChildrenInfo.TempCloneChildren...)
+	children = append(children, snapAndChildrenInfo.VolumeSnapshotChildren...)
+	snaps := snapAndChildrenInfo.SnapInfoList
 
 	if len(snaps) > int(maxSnapshotsOnImage) {
 		log.DebugLog(
diff --git a/internal/rbd/rbd_util.go b/internal/rbd/rbd_util.go
index 7e36b6b1b9c..8c49ce12a07 100644
--- a/internal/rbd/rbd_util.go
+++ b/internal/rbd/rbd_util.go
@@ -84,6 +84,10 @@ const (
 
 	// clusterNameKey cluster Key, set on RBD image.
 	clusterNameKey = "csi.ceph.com/cluster/name"
+
+	// Suffix added to the temp cloned image name.
+	// This will always be (rbd image name + "-temp").
+	tempImageSuffix = "-temp"
 )
 
 // rbdImage contains common attributes and methods for the rbdVolume and
@@ -2025,26 +2029,49 @@ func (ri *rbdImage) DisableDeepFlatten() error {
 	return image.UpdateFeatures(librbd.FeatureDeepFlatten, false)
 }
 
-// listSnapAndChildren returns list of names of snapshots and child images.
-func (ri *rbdImage) listSnapAndChildren() ([]librbd.SnapInfo, []string, error) {
+type snapAndChildrenInfo struct {
+	SnapInfoList           []librbd.SnapInfo
+	TempCloneChildren      []string
+	VolumeSnapshotChildren []string
+}
+
+// listSnapAndChildren returns list of snapshot names, volume snapshot images and
+// child temp clone images. Only child images which are not in trash are returned.
+func (ri *rbdImage) listSnapAndChildren() (*snapAndChildrenInfo, error) {
 	image, err := ri.open()
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 	defer image.Close()
 
 	snaps, err := image.GetSnapshotNames()
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 
-	// ListChildren() returns pools, images, err.
-	_, children, err := image.ListChildren()
+	childImageSpecList, err := image.ListChildrenAttributes()
 	if err != nil {
-		return nil, nil, err
+		return nil, err
+	}
+
+	tempCloneChildren := make([]string, 0)
+	volSnapChildren := make([]string, 0)
+	for _, child := range childImageSpecList {
+		if child.Trash {
+			continue
+		}
+		if isTempClonedImage(child.ImageName) {
+			tempCloneChildren = append(tempCloneChildren, child.ImageName)
+		} else {
+			volSnapChildren = append(volSnapChildren, child.ImageName)
+		}
 	}
 
-	return snaps, children, nil
+	return &snapAndChildrenInfo{
+		SnapInfoList:           snaps,
+		TempCloneChildren:      tempCloneChildren,
+		VolumeSnapshotChildren: volSnapChildren,
+	}, nil
 }
 
 func (ri *rbdImage) isCompatibleEncryption(dst *rbdImage) error {
diff --git a/vendor/github.com/aws/smithy-go/CHANGELOG.md b/vendor/github.com/aws/smithy-go/CHANGELOG.md
index de39171cf09..4df632dce80 100644
--- a/vendor/github.com/aws/smithy-go/CHANGELOG.md
+++ b/vendor/github.com/aws/smithy-go/CHANGELOG.md
@@ -1,3 +1,13 @@
+# Release (2025-02-17)
+
+## General Highlights
+* **Dependency Update**: Updated to the latest SDK module versions
+
+## Module Highlights
+* `github.com/aws/smithy-go`: v1.22.3
+  * **Bug Fix**: Fix HTTP metrics data race.
+  * **Bug Fix**: Replace usages of deprecated ioutil package.
+
 # Release (2025-01-21)
 
 ## General Highlights
diff --git a/vendor/github.com/aws/smithy-go/go_module_metadata.go b/vendor/github.com/aws/smithy-go/go_module_metadata.go
index a51ceca4ce0..d12d95891d2 100644
--- a/vendor/github.com/aws/smithy-go/go_module_metadata.go
+++ b/vendor/github.com/aws/smithy-go/go_module_metadata.go
@@ -3,4 +3,4 @@
 package smithy
 
 // goModuleVersion is the tagged release for this module
-const goModuleVersion = "1.22.2"
+const goModuleVersion = "1.22.3"
diff --git a/vendor/github.com/ceph/go-ceph/rbd/encryption.go b/vendor/github.com/ceph/go-ceph/rbd/encryption.go
index 1fede749d01..3ad0c60619f 100644
--- a/vendor/github.com/ceph/go-ceph/rbd/encryption.go
+++ b/vendor/github.com/ceph/go-ceph/rbd/encryption.go
@@ -61,30 +61,44 @@ type EncryptionOptions interface {
 }
 
 func (opts EncryptionOptionsLUKS1) allocateEncryptionOptions() cEncryptionData {
-	var cOpts C.rbd_encryption_luks1_format_options_t
 	var retData cEncryptionData
-	cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
+
 	// CBytes allocates memory. it will be freed when cEncryptionData.free is called
-	cOpts.passphrase = (*C.char)(C.CBytes(opts.Passphrase))
+	cPassphrase := (*C.char)(C.CBytes(opts.Passphrase))
+	cOptsSize := C.size_t(C.sizeof_rbd_encryption_luks1_format_options_t)
+	cOpts := (*C.rbd_encryption_luks1_format_options_t)(C.malloc(cOptsSize))
+	cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
+	cOpts.passphrase = cPassphrase
 	cOpts.passphrase_size = C.size_t(len(opts.Passphrase))
-	retData.opts = C.rbd_encryption_options_t(&cOpts)
-	retData.optsSize = C.size_t(C.sizeof_rbd_encryption_luks1_format_options_t)
-	retData.free = func() { C.free(unsafe.Pointer(cOpts.passphrase)) }
+
 	retData.format = C.RBD_ENCRYPTION_FORMAT_LUKS1
+	retData.opts = C.rbd_encryption_options_t(cOpts)
+	retData.optsSize = cOptsSize
+	retData.free = func() {
+		C.free(unsafe.Pointer(cOpts.passphrase))
+		C.free(unsafe.Pointer(cOpts))
+	}
 	return retData
 }
 
 func (opts EncryptionOptionsLUKS2) allocateEncryptionOptions() cEncryptionData {
-	var cOpts C.rbd_encryption_luks2_format_options_t
 	var retData cEncryptionData
-	cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
+
 	// CBytes allocates memory. it will be freed when cEncryptionData.free is called
-	cOpts.passphrase = (*C.char)(C.CBytes(opts.Passphrase))
+	cPassphrase := (*C.char)(C.CBytes(opts.Passphrase))
+	cOptsSize := C.size_t(C.sizeof_rbd_encryption_luks2_format_options_t)
+	cOpts := (*C.rbd_encryption_luks2_format_options_t)(C.malloc(cOptsSize))
+	cOpts.alg = C.rbd_encryption_algorithm_t(opts.Alg)
+	cOpts.passphrase = cPassphrase
 	cOpts.passphrase_size = C.size_t(len(opts.Passphrase))
-	retData.opts = C.rbd_encryption_options_t(&cOpts)
-	retData.optsSize = C.size_t(C.sizeof_rbd_encryption_luks2_format_options_t)
-	retData.free = func() { C.free(unsafe.Pointer(cOpts.passphrase)) }
+
 	retData.format = C.RBD_ENCRYPTION_FORMAT_LUKS2
+	retData.opts = C.rbd_encryption_options_t(cOpts)
+	retData.optsSize = cOptsSize
+	retData.free = func() {
+		C.free(unsafe.Pointer(cOpts.passphrase))
+		C.free(unsafe.Pointer(cOpts))
+	}
 	return retData
 }
 
diff --git a/vendor/github.com/ceph/go-ceph/rbd/encryption_load2.go b/vendor/github.com/ceph/go-ceph/rbd/encryption_load2.go
index 81f6f3e4f4f..5d12f97c6f0 100644
--- a/vendor/github.com/ceph/go-ceph/rbd/encryption_load2.go
+++ b/vendor/github.com/ceph/go-ceph/rbd/encryption_load2.go
@@ -81,25 +81,22 @@ func (image *Image) EncryptionLoad2(opts []EncryptionOptions) error {
 	}
 
 	length := len(opts)
-	cspecs := (*C.rbd_encryption_spec_t)(C.malloc(
-		C.size_t(C.sizeof_rbd_encryption_spec_t * length)))
-	specs := unsafe.Slice(cspecs, length)
+	cspecs := make([]C.rbd_encryption_spec_t, length)
 	freeFuncs := make([]func(), length)
 
 	for idx, option := range opts {
-		f := option.(encryptionOptions2).writeEncryptionSpec(&specs[idx])
+		f := option.(encryptionOptions2).writeEncryptionSpec(&cspecs[idx])
 		freeFuncs[idx] = f
 	}
 	defer func() {
 		for _, f := range freeFuncs {
 			f()
 		}
-		C.free(unsafe.Pointer(cspecs))
 	}()
 
 	ret := C.rbd_encryption_load2(
 		image.image,
-		cspecs,
+		(*C.rbd_encryption_spec_t)(unsafe.Pointer(&cspecs[0])),
 		C.size_t(length))
 	return getError(ret)
 }
diff --git a/vendor/github.com/ceph/go-ceph/rbd/encryption_opt_luks.go b/vendor/github.com/ceph/go-ceph/rbd/encryption_opt_luks.go
index bfacdde54f7..363b780cbc0 100644
--- a/vendor/github.com/ceph/go-ceph/rbd/encryption_opt_luks.go
+++ b/vendor/github.com/ceph/go-ceph/rbd/encryption_opt_luks.go
@@ -21,15 +21,22 @@ type EncryptionOptionsLUKS struct {
 }
 
 func (opts EncryptionOptionsLUKS) allocateEncryptionOptions() cEncryptionData {
-	var cOpts C.rbd_encryption_luks_format_options_t
 	var retData cEncryptionData
+
 	// CBytes allocates memory. it will be freed when cEncryptionData.free is called
-	cOpts.passphrase = (*C.char)(C.CBytes(opts.Passphrase))
+	cPassphrase := (*C.char)(C.CBytes(opts.Passphrase))
+	cOptsSize := C.size_t(C.sizeof_rbd_encryption_luks_format_options_t)
+	cOpts := (*C.rbd_encryption_luks_format_options_t)(C.malloc(cOptsSize))
+	cOpts.passphrase = cPassphrase
 	cOpts.passphrase_size = C.size_t(len(opts.Passphrase))
-	retData.opts = C.rbd_encryption_options_t(&cOpts)
-	retData.optsSize = C.size_t(C.sizeof_rbd_encryption_luks_format_options_t)
-	retData.free = func() { C.free(unsafe.Pointer(cOpts.passphrase)) }
+
 	retData.format = C.RBD_ENCRYPTION_FORMAT_LUKS
+	retData.opts = C.rbd_encryption_options_t(cOpts)
+	retData.optsSize = cOptsSize
+	retData.free = func() {
+		C.free(unsafe.Pointer(cOpts.passphrase))
+		C.free(unsafe.Pointer(cOpts))
+	}
 	return retData
 }
 
diff --git a/vendor/github.com/ceph/go-ceph/rbd/snapshot_nautilus.go b/vendor/github.com/ceph/go-ceph/rbd/snapshot_nautilus.go
index ce3e11a16f8..aeaa06fafd3 100644
--- a/vendor/github.com/ceph/go-ceph/rbd/snapshot_nautilus.go
+++ b/vendor/github.com/ceph/go-ceph/rbd/snapshot_nautilus.go
@@ -168,6 +168,50 @@ func (image *Image) ListChildren() (pools []string, images []string, err error)
 	return pools, images, nil
 }
 
+// ListChildrenAttributes returns an array of struct with the names and ids of the
+// images and pools and the trash of the images that are children of the given image.
+//
+// Implements:
+//
+//	int rbd_list_children3(rbd_image_t image, rbd_linked_image_spec_t *images,
+//	                       size_t *max_images);
+func (image *Image) ListChildrenAttributes() (imgSpec []ImageSpec, err error) {
+	if err := image.validate(imageIsOpen); err != nil {
+		return nil, err
+	}
+	var (
+		csize    C.size_t
+		children []C.rbd_linked_image_spec_t
+	)
+	retry.WithSizes(16, 4096, func(size int) retry.Hint {
+		csize = C.size_t(size)
+		children = make([]C.rbd_linked_image_spec_t, csize)
+		ret := C.rbd_list_children3(
+			image.image,
+			(*C.rbd_linked_image_spec_t)(unsafe.Pointer(&children[0])),
+			&csize)
+		err = getErrorIfNegative(ret)
+		return retry.Size(int(csize)).If(err == errRange)
+	})
+	if err != nil {
+		return nil, err
+	}
+	defer C.rbd_linked_image_spec_list_cleanup((*C.rbd_linked_image_spec_t)(unsafe.Pointer(&children[0])), csize)
+
+	imgSpec = make([]ImageSpec, csize)
+	for i, child := range children[:csize] {
+		imgSpec[i] = ImageSpec{
+			ImageName:     C.GoString(child.image_name),
+			ImageID:       C.GoString(child.image_id),
+			PoolName:      C.GoString(child.pool_name),
+			PoolNamespace: C.GoString(child.pool_namespace),
+			PoolID:        uint64(child.pool_id),
+			Trash:         bool(child.trash),
+		}
+	}
+	return imgSpec, nil
+}
+
 // SetSnapByID updates the rbd image (not the Snapshot) such that the snapshot
 // is the source of readable data.
 //
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 7810c3c9015..b63461aa25d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -157,8 +157,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url
 github.com/aws/aws-sdk-go-v2/service/sts
 github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints
 github.com/aws/aws-sdk-go-v2/service/sts/types
-# github.com/aws/smithy-go v1.22.2
-## explicit; go 1.21
+# github.com/aws/smithy-go v1.22.3
+## explicit; go 1.22
 github.com/aws/smithy-go
 github.com/aws/smithy-go/auth
 github.com/aws/smithy-go/auth/bearer
@@ -195,8 +195,8 @@ github.com/ceph/ceph-csi/api/deploy/kubernetes/cephfs
 github.com/ceph/ceph-csi/api/deploy/kubernetes/nfs
 github.com/ceph/ceph-csi/api/deploy/kubernetes/rbd
 github.com/ceph/ceph-csi/api/deploy/ocp
-# github.com/ceph/go-ceph v0.32.0
-## explicit; go 1.21
+# github.com/ceph/go-ceph v0.32.1-0.20250307053135-38b9676b1d4e
+## explicit; go 1.22
 github.com/ceph/go-ceph/cephfs
 github.com/ceph/go-ceph/cephfs/admin
 github.com/ceph/go-ceph/common/admin/manager


Reviews:
user: login: nixpanic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T08:56:04Z
user: login: Rakshith-R
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T09:19:56Z
user: login: nixpanic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T09:43:29Z
user: login: Rakshith-R
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T10:19:04Z
user: login: nixpanic
body: Thanks, makes it much cleaner. Just a nit that needs addressing.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T12:10:37Z
user: login: nixpanic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T12:35:59Z
user: login: nixpanic
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-11T12:43:27Z
user: login: nixpanic
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-11T15:01:59Z
user: login: yati1998
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T05:19:39Z

Review comments:
diff_hunk: @@ -2038,12 +2041,28 @@ func (ri *rbdImage) listSnapAndChildren() ([]librbd.SnapInfo, []string, error) {
 		return nil, nil, err
 	}
 
-	// ListChildren() returns pools, images, err.
-	_, children, err := image.ListChildren()
+	// ListChildrenAttributes() returns child ImageSpec list, err.
path: internal/rbd/rbd_util.go
user: login: nixpanic
body: you can drop this comment, I think the function is rather clear
created_at: 2025-03-11T08:38:19Z
author_association: MEMBER
diff_hunk: @@ -2038,12 +2041,28 @@ func (ri *rbdImage) listSnapAndChildren() ([]librbd.SnapInfo, []string, error) {
 		return nil, nil, err
 	}
 
-	// ListChildren() returns pools, images, err.
-	_, children, err := image.ListChildren()
+	// ListChildrenAttributes() returns child ImageSpec list, err.
+	childImageSpecList, err := image.ListChildrenAttributes()
 	if err != nil {
 		return nil, nil, err
 	}
 
+	children := make([]string, 0)
+	volSnapChildren := make([]string, 0)
+	for _, child := range childImageSpecList {
+		if child.Trash {
+			continue
+		}
+		if strings.HasSuffix(child.ImageName, "-temp") {
path: internal/rbd/rbd_util.go
user: login: nixpanic
body: Maybe consider adding a `isTempImage(child)` function?
created_at: 2025-03-11T08:53:31Z
author_association: MEMBER
diff_hunk: @@ -2038,12 +2041,28 @@ func (ri *rbdImage) listSnapAndChildren() ([]librbd.SnapInfo, []string, error) {
 		return nil, nil, err
 	}
 
-	// ListChildren() returns pools, images, err.
-	_, children, err := image.ListChildren()
+	// ListChildrenAttributes() returns child ImageSpec list, err.
+	childImageSpecList, err := image.ListChildrenAttributes()
 	if err != nil {
 		return nil, nil, err
 	}
 
+	children := make([]string, 0)
+	volSnapChildren := make([]string, 0)
+	for _, child := range childImageSpecList {
+		if child.Trash {
+			continue
+		}
+		if strings.HasSuffix(child.ImageName, "-temp") {
+			children = append(children, child.ImageName)
+		} else {
+			volSnapChildren = append(volSnapChildren, child.ImageName)
+		}
+	}
+	// order the temp clone images first followed by the volume snapshots
+	// so that the temp clones are flattened first.
path: internal/rbd/rbd_util.go
user: login: nixpanic
body: This is the 2nd comment about `temp clones are flattened first`. I don't get why that is the case. If that is a requirement of the returned list, mention something about the order. Or wherever the list is consumed, use some other way of inspecting the list. Might be cleaner to return two lists, so that the distinction is very clear.
created_at: 2025-03-11T08:55:28Z
author_association: MEMBER
diff_hunk: @@ -2038,12 +2041,28 @@ func (ri *rbdImage) listSnapAndChildren() ([]librbd.SnapInfo, []string, error) {
 		return nil, nil, err
 	}
 
-	// ListChildren() returns pools, images, err.
-	_, children, err := image.ListChildren()
+	// ListChildrenAttributes() returns child ImageSpec list, err.
+	childImageSpecList, err := image.ListChildrenAttributes()
 	if err != nil {
 		return nil, nil, err
 	}
 
+	children := make([]string, 0)
+	volSnapChildren := make([]string, 0)
+	for _, child := range childImageSpecList {
+		if child.Trash {
+			continue
+		}
+		if strings.HasSuffix(child.ImageName, "-temp") {
+			children = append(children, child.ImageName)
+		} else {
+			volSnapChildren = append(volSnapChildren, child.ImageName)
+		}
+	}
+	// order the temp clone images first followed by the volume snapshots
+	// so that the temp clones are flattened first.
path: internal/rbd/rbd_util.go
user: login: Rakshith-R
body: > This is the 2nd comment about `temp clones are flattened first`. I don't get why that is the case. If that is a requirement of the returned list, mention something about the order. Or wherever the list is consumed, use some other way of inspecting the list. Might be cleaner to return two lists, so that the distinction is very clear.

I've removed it from the function definition, since we still are returning full list of child images which are not in trash.

I've expanded the comment in this section with reasons why such ordering is done so it can be understood in the future.
created_at: 2025-03-11T09:19:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2038,12 +2041,28 @@ func (ri *rbdImage) listSnapAndChildren() ([]librbd.SnapInfo, []string, error) {
 		return nil, nil, err
 	}
 
-	// ListChildren() returns pools, images, err.
-	_, children, err := image.ListChildren()
+	// ListChildrenAttributes() returns child ImageSpec list, err.
+	childImageSpecList, err := image.ListChildrenAttributes()
 	if err != nil {
 		return nil, nil, err
 	}
 
+	children := make([]string, 0)
+	volSnapChildren := make([]string, 0)
+	for _, child := range childImageSpecList {
+		if child.Trash {
+			continue
+		}
+		if strings.HasSuffix(child.ImageName, "-temp") {
+			children = append(children, child.ImageName)
+		} else {
+			volSnapChildren = append(volSnapChildren, child.ImageName)
+		}
+	}
+	// order the temp clone images first followed by the volume snapshots
+	// so that the temp clones are flattened first.
path: internal/rbd/rbd_util.go
user: login: nixpanic
body: Thanks, that makes things a little bit clearer. I don't understand (yet) if, and why callers need to care about the order in the list that is returned. If callers need to care, or benefit from it, why not return separate lists? Or, a struct like `{ snaps, tempChildren, volSnapChildren }` so that callers of the function are very aware of the differences, and can decide how to handle them?
created_at: 2025-03-11T09:43:29Z
author_association: MEMBER
diff_hunk: @@ -2038,12 +2041,28 @@ func (ri *rbdImage) listSnapAndChildren() ([]librbd.SnapInfo, []string, error) {
 		return nil, nil, err
 	}
 
-	// ListChildren() returns pools, images, err.
-	_, children, err := image.ListChildren()
+	// ListChildrenAttributes() returns child ImageSpec list, err.
+	childImageSpecList, err := image.ListChildrenAttributes()
 	if err != nil {
 		return nil, nil, err
 	}
 
+	children := make([]string, 0)
+	volSnapChildren := make([]string, 0)
+	for _, child := range childImageSpecList {
+		if child.Trash {
+			continue
+		}
+		if strings.HasSuffix(child.ImageName, "-temp") {
+			children = append(children, child.ImageName)
+		} else {
+			volSnapChildren = append(volSnapChildren, child.ImageName)
+		}
+	}
+	// order the temp clone images first followed by the volume snapshots
+	// so that the temp clones are flattened first.
path: internal/rbd/rbd_util.go
user: login: Rakshith-R
body: > Thanks, that makes things a little bit clearer. I don't understand (yet) if, and why callers need to care about the order in the list that is returned. If callers need to care, or benefit from it, why not return separate lists? Or, a struct like `{ snaps, tempChildren, volSnapChildren }` so that callers of the function are very aware of the differences, and can decide how to handle them?

I've moved the logic to order images into the caller and added a struct for returned parameters.
PTAL
created_at: 2025-03-11T10:19:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2025,26 +2029,49 @@ func (ri *rbdImage) DisableDeepFlatten() error {
 	return image.UpdateFeatures(librbd.FeatureDeepFlatten, false)
 }
 
-// listSnapAndChildren returns list of names of snapshots and child images.
-func (ri *rbdImage) listSnapAndChildren() ([]librbd.SnapInfo, []string, error) {
+type SnapAndChildrenInfo struct {
path: internal/rbd/rbd_util.go
user: login: nixpanic
body: No need to export this, use `snapAndChildrenInfo` instead.
created_at: 2025-03-11T12:10:20Z
author_association: MEMBER
diff_hunk: @@ -590,14 +590,23 @@ func (cs *ControllerServer) repairExistingVolume(ctx context.Context, req *csi.C
 // are more than the `minSnapshotOnImage` Add a task to flatten all the
 // temporary cloned images.
 func flattenTemporaryClonedImages(ctx context.Context, rbdVol *rbdVolume, cr *util.Credentials) error {
-	snaps, children, err := rbdVol.listSnapAndChildren()
+	snapAndChildrenInfo, err := rbdVol.listSnapAndChildren()
 	if err != nil {
 		if errors.Is(err, util.ErrImageNotFound) {
 			return status.Error(codes.InvalidArgument, err.Error())
 		}
 
 		return status.Error(codes.Internal, err.Error())
 	}
+	// order the temp clone images first followed by the volume snapshot
+	// images so that the temp clone images are flattened first.
+	// This is done in order to:
+	// - increase number of snapshots which can be supported for
+	// changed block tracking(rbd snap diff).
+	// - favor scalability since multiple PVCs can be restored from same
+	// volumesnapshot versus just one PVC-PVC clone.
+	children := append(snapAndChildrenInfo.TempCloneChildren, snapAndChildrenInfo.VolumeSnapshotChildren...)
path: internal/rbd/controllerserver.go
user: login: nixpanic
body: **golangci-lint** complains about this. `append()` may modify the 1st parameter (`snapAndChildrenInfo`), which could cause confusion later on in case it is used again ([docs](https://pkg.go.dev/builtin#append)).

Instead, you could do it like this:
```go
children := make([]string, len(snapAndChildrenInfo.TempCloneChildren) + len(snapAndChildrenInfo.VolumeSnapshotChildren))
children = append(children, snapAndChildrenInfo.TempCloneChildren...)
children = append(children, snapAndChildrenInfo.VolumeSnapshotChildren...)
```
created_at: 2025-03-11T12:35:59Z
author_association: MEMBER

PR comments:
user: login: Rakshith-R
created_at: 2025-03-10T10:24:35Z
body: /test ci/centos/mini-e2e/k8s-1.31
author_association: CONTRIBUTOR
user: login: Rakshith-R
created_at: 2025-03-11T06:48:41Z
body: /test ci/centos/mini-e2e-helm/k8s-1.31
author_association: CONTRIBUTOR
user: login: Rakshith-R
created_at: 2025-03-11T12:55:26Z
body: > **golangci-lint** complains about this. `append()` may modify the 1st parameter (`snapAndChildrenInfo`), which could cause confusion later on in case it is used again ([docs](https://pkg.go.dev/builtin#append)).
> 
> Instead, you could do it like this:
> 
> ```go
> children := make([]string, len(snapAndChildrenInfo.TempCloneChildren) + len(snapAndChildrenInfo.VolumeSnapshotChildren))
> children = append(children, snapAndChildrenInfo.TempCloneChildren...)
> children = append(children, snapAndChildrenInfo.VolumeSnapshotChildren...)
> ```

It needs to be initialized with zero length :(
ci passes now
ptal
author_association: CONTRIBUTOR
user: login: Rakshith-R
created_at: 2025-03-12T05:47:20Z
body: @mergifyio queue
author_association: CONTRIBUTOR
user: login: mergify[bot]
created_at: 2025-03-12T05:47:27Z
body: > queue

#### 🛑 The pull request has been removed from the queue `default`

<details>

The merge conditions cannot be satisfied due to failing checks.

You can take a look at `Queue: Embarked in merge queue` check runs for more details.

In case of a failure due to a flaky test, you should first retrigger the CI.
Then, re-embark the pull request into the merge queue by posting the comment
`@mergifyio refresh` on the pull request.


</details>


<!---
DO NOT EDIT
-*- Mergify Payload -*-
{"command": "queue", "conclusion": "cancelled", "action_is_running": true}
-*- Mergify Payload End -*-
-->
author_association: CONTRIBUTOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:39Z
body: /test ci/centos/k8s-e2e-external-storage/1.31
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:40Z
body: /test ci/centos/mini-e2e-helm/k8s-1.31
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:40Z
body: /test ci/centos/mini-e2e/k8s-1.31
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:41Z
body: /test ci/centos/upgrade-tests-cephfs
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:41Z
body: /test ci/centos/k8s-e2e-external-storage/1.30
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:41Z
body: /test ci/centos/upgrade-tests-rbd
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:41Z
body: /test ci/centos/mini-e2e-helm/k8s-1.30
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:42Z
body: /test ci/centos/mini-e2e/k8s-1.30
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:43Z
body: /test ci/centos/k8s-e2e-external-storage/1.32
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:43Z
body: /test ci/centos/mini-e2e-helm/k8s-1.32
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T05:47:44Z
body: /test ci/centos/mini-e2e/k8s-1.32
author_association: COLLABORATOR
user: login: mergify[bot]
created_at: 2025-03-12T05:49:05Z
body: This pull request has been removed from the queue for the following reason: <code>checks failed</code>.

The merge conditions cannot be satisfied due to failing checks:
- ❌ [ci/centos/upgrade-tests-cephfs](https://jenkins-ceph-csi.apps.ocp.cloud.ci.centos.org/job/upgrade-tests-cephfs/8520/display/redirect)
- ❌ [ci/centos/upgrade-tests-rbd](https://jenkins-ceph-csi.apps.ocp.cloud.ci.centos.org/job/upgrade-tests-rbd/8500/display/redirect)


You may have to fix your CI before adding the pull request to the queue again.

If you want to requeue this pull request, you can post a `@mergifyio requeue` comment.

author_association: CONTRIBUTOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:00:10Z
body: /retest ci/centos/upgrade-tests-rbd
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:00:11Z
body: @Rakshith-R "ci/centos/upgrade-tests-rbd" test failed. Logs are available at [location](https://jenkins-ceph-csi.apps.ocp.cloud.ci.centos.org/job/upgrade-tests-rbd/8500/display/redirect) for debugging
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:00:11Z
body: /retest ci/centos/upgrade-tests-cephfs
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:00:12Z
body: @Rakshith-R "ci/centos/upgrade-tests-cephfs" test failed. Logs are available at [location](https://jenkins-ceph-csi.apps.ocp.cloud.ci.centos.org/job/upgrade-tests-cephfs/8520/display/redirect) for debugging
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:00:12Z
body: @Mergifyio requeue
author_association: COLLABORATOR
user: login: mergify[bot]
created_at: 2025-03-12T06:00:18Z
body: > requeue

#### ✅ The queue state of this pull request has been cleaned. It can be re-embarked automatically



<!---
DO NOT EDIT
-*- Mergify Payload -*-
{"command": "requeue", "conclusion": "success", "action_is_running": true}
-*- Mergify Payload End -*-
-->
author_association: CONTRIBUTOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:41:12Z
body: /retest ci/centos/upgrade-tests-cephfs
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:41:12Z
body: @Rakshith-R "ci/centos/upgrade-tests-cephfs" test failed. Logs are available at [location](https://jenkins-ceph-csi.apps.ocp.cloud.ci.centos.org/job/upgrade-tests-cephfs/8521/display/redirect) for debugging
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:41:13Z
body: /retest ci/centos/upgrade-tests-rbd
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:41:14Z
body: @Rakshith-R "ci/centos/upgrade-tests-rbd" test failed. Logs are available at [location](https://jenkins-ceph-csi.apps.ocp.cloud.ci.centos.org/job/upgrade-tests-rbd/8501/display/redirect) for debugging
author_association: COLLABORATOR
user: login: ceph-csi-bot
created_at: 2025-03-12T06:41:14Z
body: @Mergifyio requeue
author_association: COLLABORATOR
user: login: mergify[bot]
created_at: 2025-03-12T06:41:20Z
body: > requeue

#### ☑️ This pull request is already queued



<!---
DO NOT EDIT
-*- Mergify Payload -*-
{"command": "requeue", "conclusion": "neutral", "action_is_running": true}
-*- Mergify Payload End -*-
-->
author_association: CONTRIBUTOR
user: login: Rakshith-R
created_at: 2025-03-12T07:21:20Z
body: Upgrade tests are failing due to 
https://github.com/ceph/ceph-csi/pull/5211
author_association: CONTRIBUTOR
user: login: Rakshith-R
created_at: 2025-03-12T08:32:48Z
body: /retest ci/centos/upgrade-tests-rbd
author_association: CONTRIBUTOR
user: login: Rakshith-R
created_at: 2025-03-12T08:33:04Z
body: /retest ci/centos/upgrade-tests-cephfs
author_association: CONTRIBUTOR
user: login: Rakshith-R
created_at: 2025-03-12T08:33:21Z
body: @mergifyio requeue
author_association: CONTRIBUTOR
user: login: mergify[bot]
created_at: 2025-03-12T08:33:30Z
body: > requeue

#### ☑️ This pull request is already queued



<!---
DO NOT EDIT
-*- Mergify Payload -*-
{"command": "requeue", "conclusion": "neutral", "action_is_running": true}
-*- Mergify Payload End -*-
-->
author_association: CONTRIBUTOR