Skip to content

Commit 4334ae7

Browse files
committed
cephfs: apply MutableParameters from CreateVolume and add e2e test
Address review feedback on PR #6390: the mutable VolumeAttributesClass parameters must also be applied during CreateVolume, not only through ControllerModifyVolume. Following the pattern in the NFS driver, the pin logic is moved into an applyMutableParameters helper so that locking and volume resolution happen only once, and both CreateVolume and ControllerModifyVolume reuse it. Also add an e2e test under e2e/cephfs.go that creates a PVC referencing a VolumeAttributesClass with an mds-pin-export rank, verifies the class is applied at creation time, and then updates the PVC to a second class to exercise the ControllerModifyVolume path. Assisted-by: goose <noreply@block.xyz> Signed-off-by: Ismael Puerto Freire <ismaelpf@inditex.com>
1 parent 446e218 commit 4334ae7

3 files changed

Lines changed: 212 additions & 17 deletions

File tree

e2e/cephfs.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2732,6 +2732,99 @@ var _ = Describe(cephfsType, func() {
27322732
validateOmapCount(f, 0, cephfsType, metadataPool, snapsType)
27332733
})
27342734

2735+
It("create a PVC with a VolumeAttributesClass for MDS pinning and modify it", func() {
2736+
// VolumeAttributesClass support requires Kubernetes 1.34+.
2737+
if !k8sVersionGreaterEquals(f.ClientSet, 1, 34) {
2738+
framework.Logf("skipping VolumeAttributesClass test, needs Kubernetes >= 1.34")
2739+
2740+
return
2741+
}
2742+
2743+
// This test validates VolumeAttributesClass (VAC) support for
2744+
// CephFS subvolumes via MDS pinning, exercising both the
2745+
// CreateVolume and ControllerModifyVolume code paths:
2746+
// 1. Create a StorageClass.
2747+
// 2. Create two VACs with different mds-pin-export ranks.
2748+
// 3. Create a PVC referencing the first VAC and verify the PV
2749+
// gets the VAC applied at creation time.
2750+
// 4. Update the PVC to reference the second VAC and verify the
2751+
// change is applied via ControllerModifyVolume.
2752+
// 5. Clean up.
2753+
err := createCephfsStorageClass(f.ClientSet, f, true, nil)
2754+
if err != nil {
2755+
logAndFail("failed to create CephFS storageclass: %v", err)
2756+
}
2757+
defer func() {
2758+
err = deleteResource(cephFSExamplePath + "storageclass.yaml")
2759+
if err != nil {
2760+
logAndFail("failed to delete CephFS storageclass: %v", err)
2761+
}
2762+
}()
2763+
2764+
vacName1 := "e2e-" + f.UniqueName + "-cephfs-vac1"
2765+
vacName2 := "e2e-" + f.UniqueName + "-cephfs-vac2"
2766+
2767+
err = createCephFSVolumeAttributesClass(f.ClientSet, vacName1,
2768+
map[string]string{"mds-pin-export": "0"})
2769+
if err != nil {
2770+
logAndFail("failed to create VolumeAttributesClass: %v", err)
2771+
}
2772+
defer func() {
2773+
if err = deleteCephFSVolumeAttributesClass(f.ClientSet, vacName1); err != nil {
2774+
logAndFail("failed to delete VolumeAttributesClass: %v", err)
2775+
}
2776+
}()
2777+
2778+
err = createCephFSVolumeAttributesClass(f.ClientSet, vacName2,
2779+
map[string]string{"mds-pin-export": "1"})
2780+
if err != nil {
2781+
logAndFail("failed to create VolumeAttributesClass: %v", err)
2782+
}
2783+
defer func() {
2784+
if err = deleteCephFSVolumeAttributesClass(f.ClientSet, vacName2); err != nil {
2785+
logAndFail("failed to delete VolumeAttributesClass: %v", err)
2786+
}
2787+
}()
2788+
2789+
// create a PVC referencing the first VAC; this exercises the
2790+
// CreateVolume path applying the mutable parameters.
2791+
pvc, err := loadPVC(pvcPath)
2792+
if err != nil {
2793+
logAndFail("failed to load PVC: %v", err)
2794+
}
2795+
pvc.Namespace = f.UniqueName
2796+
pvc.Spec.VolumeAttributesClassName = &vacName1
2797+
2798+
err = createPVCAndvalidatePV(f.ClientSet, pvc, deployTimeout)
2799+
if err != nil {
2800+
logAndFail("failed to create PVC: %v", err)
2801+
}
2802+
2803+
pv, err := getBoundPV(f.ClientSet, pvc)
2804+
if err != nil {
2805+
logAndFail("failed to get bound PV: %v", err)
2806+
}
2807+
if pv.Spec.VolumeAttributesClassName == nil ||
2808+
*pv.Spec.VolumeAttributesClassName != vacName1 {
2809+
logAndFail("PV %s does not reference the expected VolumeAttributesClass %q",
2810+
pv.Name, vacName1)
2811+
}
2812+
validateSubvolumeCount(f, 1, fileSystemName, subvolumegroup)
2813+
2814+
// update the PVC to reference the second VAC; this exercises the
2815+
// ControllerModifyVolume path.
2816+
err = modifyPVCVolumeAttributesClass(f.ClientSet, pvc, vacName2)
2817+
if err != nil {
2818+
logAndFail("failed to modify VolumeAttributesClass: %v", err)
2819+
}
2820+
2821+
err = deletePVCAndValidatePV(f.ClientSet, pvc, deployTimeout)
2822+
if err != nil {
2823+
logAndFail("failed to delete PVC: %v", err)
2824+
}
2825+
validateSubvolumeCount(f, 0, fileSystemName, subvolumegroup)
2826+
})
2827+
27352828
// Make sure this should be last testcase in
27362829
// this file, because it deletes pool
27372830
It("Create a PVC and delete PVC when backend pool deleted", func() {

e2e/cephfs_helper.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
snapapi "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
2828
v1 "k8s.io/api/core/v1"
2929
scv1 "k8s.io/api/storage/v1"
30+
apierrs "k8s.io/apimachinery/pkg/api/errors"
3031
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3132
"k8s.io/apimachinery/pkg/util/wait"
3233
"k8s.io/client-go/kubernetes"
@@ -986,3 +987,65 @@ func verifyUserIdMappingMetadataSnapshotBacked(
986987

987988
return nil
988989
}
990+
991+
// createCephFSVolumeAttributesClass creates a VolumeAttributesClass for CephFS
992+
// from the example manifest, optionally overriding its name and parameters.
993+
func createCephFSVolumeAttributesClass(
994+
c kubernetes.Interface,
995+
name string,
996+
params map[string]string,
997+
) error {
998+
vac, err := getVolumeAttributesClass(cephFSExamplePath + "volumeattributesclass.yaml")
999+
if err != nil {
1000+
return fmt.Errorf("failed to get vac: %w", err)
1001+
}
1002+
if name != "" {
1003+
vac.Name = name
1004+
}
1005+
1006+
// override with the parameters passed by the caller, so that a test can
1007+
// exercise a specific MDS pin type.
1008+
if params != nil {
1009+
vac.Parameters = params
1010+
}
1011+
1012+
timeout := time.Duration(deployTimeout) * time.Minute
1013+
1014+
return wait.PollUntilContextTimeout(context.TODO(), poll, timeout, true, func(ctx context.Context) (bool, error) {
1015+
_, err = c.StorageV1().VolumeAttributesClasses().Create(ctx, &vac, metav1.CreateOptions{})
1016+
if err != nil {
1017+
if apierrs.IsAlreadyExists(err) {
1018+
return true, nil
1019+
}
1020+
if isRetryableAPIError(err) {
1021+
return false, nil
1022+
}
1023+
1024+
return false, fmt.Errorf("failed to create VolumeAttributesClass %q: %w", vac.Name, err)
1025+
}
1026+
1027+
return true, nil
1028+
})
1029+
}
1030+
1031+
// deleteCephFSVolumeAttributesClass deletes the named CephFS
1032+
// VolumeAttributesClass.
1033+
func deleteCephFSVolumeAttributesClass(c kubernetes.Interface, name string) error {
1034+
timeout := time.Duration(deployTimeout) * time.Minute
1035+
1036+
return wait.PollUntilContextTimeout(context.TODO(), poll, timeout, true, func(ctx context.Context) (bool, error) {
1037+
err := c.StorageV1().VolumeAttributesClasses().Delete(ctx, name, metav1.DeleteOptions{})
1038+
if err != nil {
1039+
if apierrs.IsNotFound(err) {
1040+
return true, nil
1041+
}
1042+
if isRetryableAPIError(err) {
1043+
return false, nil
1044+
}
1045+
1046+
return false, fmt.Errorf("failed to delete VolumeAttributesClass %q: %w", name, err)
1047+
}
1048+
1049+
return true, nil
1050+
})
1051+
}

internal/cephfs/controllerserver.go

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,15 @@ func (cs *cephfsControllerServer) CreateVolume(
412412
}
413413
}
414414

415+
// apply MutableParameters (e.g. MDS pinning) from a
416+
// VolumeAttributesClass, if any were requested.
417+
if len(req.GetMutableParameters()) != 0 {
418+
if err = applyMutableParameters(ctx, volClient, vID.FsSubvolName,
419+
req.GetMutableParameters()); err != nil {
420+
return nil, err
421+
}
422+
}
423+
415424
return buildCreateVolumeResponse(req, volOptions, vID), nil
416425
}
417426

@@ -486,6 +495,15 @@ func (cs *cephfsControllerServer) CreateVolume(
486495
log.DebugLog(ctx, "cephfs: successfully created backing volume named %s for request name %s",
487496
vID.FsSubvolName, requestName)
488497

498+
// apply MutableParameters (e.g. MDS pinning) from a VolumeAttributesClass,
499+
// if any were requested.
500+
if len(req.GetMutableParameters()) != 0 {
501+
if err = applyMutableParameters(ctx, volClient, vID.FsSubvolName,
502+
req.GetMutableParameters()); err != nil {
503+
return nil, err
504+
}
505+
}
506+
489507
return buildCreateVolumeResponse(req, volOptions, vID), nil
490508
}
491509

@@ -1559,8 +1577,12 @@ func validateMDSPinSetting(key, setting string) error {
15591577
return nil
15601578
}
15611579

1562-
// ControllerModifyVolume modifies mutable attributes of a CephFS subvolume
1563-
// based on parameters from a VolumeAttributesClass.
1580+
// applyMutableParameters applies the mutable VolumeAttributesClass parameters
1581+
// to an already resolved subvolume. It performs no locking and does not resolve
1582+
// the volume; the caller is expected to have taken the required locks and to
1583+
// provide a ready-to-use SubVolumeClient. This lets both CreateVolume and
1584+
// ControllerModifyVolume share the same logic without duplicating locking or
1585+
// volume resolution steps.
15641586
//
15651587
// Currently supported parameters (mutually exclusive, at most one may be set):
15661588
// - mds-pin-export: pin to a specific MDS rank (e.g. "2")
@@ -1570,6 +1592,34 @@ func validateMDSPinSetting(key, setting string) error {
15701592
// Similar To:
15711593
//
15721594
// ceph fs subvolume pin <vol_name> <sub_name> <pin_type> <pin_setting>
1595+
func applyMutableParameters(
1596+
ctx context.Context,
1597+
volClient core.SubVolumeClient,
1598+
volID string,
1599+
mutableParams map[string]string,
1600+
) error {
1601+
pinType, pinSetting, err := validateMDSPinParameters(mutableParams)
1602+
if err != nil {
1603+
return status.Error(codes.InvalidArgument, err.Error())
1604+
}
1605+
1606+
if pinType != "" {
1607+
if err = volClient.PinVolume(ctx, pinType, pinSetting); err != nil {
1608+
log.ErrorLog(ctx, "failed to pin subvolume %s: %v", volID, err)
1609+
1610+
return status.Error(codes.Internal, err.Error())
1611+
}
1612+
log.DebugLog(ctx, "cephfs: subvolume %s pinned with type=%s setting=%s",
1613+
volID, pinType, pinSetting)
1614+
}
1615+
1616+
return nil
1617+
}
1618+
1619+
// ControllerModifyVolume modifies mutable attributes of a CephFS subvolume
1620+
// based on parameters from a VolumeAttributesClass. It acquires the required
1621+
// locks, resolves the volume and delegates the actual work to
1622+
// applyMutableParameters.
15731623
func (cs *cephfsControllerServer) ControllerModifyVolume(
15741624
ctx context.Context,
15751625
req *csi.ControllerModifyVolumeRequest,
@@ -1617,21 +1667,10 @@ func (cs *cephfsControllerServer) ControllerModifyVolume(
16171667
}
16181668
defer volOptions.Destroy()
16191669

1620-
pinType, pinSetting, err := validateMDSPinParameters(mutableParams)
1621-
if err != nil {
1622-
return nil, status.Error(codes.InvalidArgument, err.Error())
1623-
}
1624-
1625-
if pinType != "" {
1626-
volClient := core.NewSubVolume(volOptions.GetConnection(),
1627-
&volOptions.SubVolume, volOptions.ClusterID, cs.ClusterName)
1628-
if err = volClient.PinVolume(ctx, pinType, pinSetting); err != nil {
1629-
log.ErrorLog(ctx, "failed to pin subvolume %s: %v", volID, err)
1630-
1631-
return nil, status.Error(codes.Internal, err.Error())
1632-
}
1633-
log.DebugLog(ctx, "cephfs: subvolume %s pinned with type=%s setting=%s",
1634-
volID, pinType, pinSetting)
1670+
volClient := core.NewSubVolume(volOptions.GetConnection(),
1671+
&volOptions.SubVolume, volOptions.ClusterID, cs.ClusterName)
1672+
if err = applyMutableParameters(ctx, volClient, volID, mutableParams); err != nil {
1673+
return nil, err
16351674
}
16361675

16371676
return &csi.ControllerModifyVolumeResponse{}, nil

0 commit comments

Comments
 (0)