Skip to content

Commit ca56e4b

Browse files
committed
feat: add dataReplicas and metadataTarget StorageClass parameters
All four protocol controllers (NFS, iSCSI, NVMe-oF, SMB) now support dataReplicas and metadataTarget in StorageClass parameters, flowing through to the engine's subvolume.create RPC for per-subvolume bcachefs tiering and replication control.
1 parent 63fc79d commit ca56e4b

5 files changed

Lines changed: 112 additions & 6 deletions

File tree

pkg/driver/controller.go

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,19 @@ func (s *ControllerService) logCreateVolumeDebugInfo(req *csi.CreateVolumeReques
435435
klog.V(4).Infof("===============================")
436436
}
437437

438+
// parseDataReplicas parses the dataReplicas StorageClass parameter.
439+
// Returns (0, nil) if empty (inherit filesystem default), or the parsed value.
440+
func parseDataReplicas(val string) (uint32, error) {
441+
if val == "" {
442+
return 0, nil
443+
}
444+
n, err := strconv.ParseUint(val, 10, 32)
445+
if err != nil || n < 1 {
446+
return 0, status.Errorf(codes.InvalidArgument, "dataReplicas must be a positive integer, got %q", val)
447+
}
448+
return uint32(n), nil
449+
}
450+
438451
// validateCreateVolumeRequest validates the CreateVolume request parameters.
439452
func validateCreateVolumeRequest(req *csi.CreateVolumeRequest) error {
440453
if req.GetName() == "" {
@@ -1046,14 +1059,22 @@ func (s *ControllerService) checkAndAdoptVolume(ctx context.Context, req *csi.Cr
10461059
subvol, err := s.apiClient.FindSubvolumeByCSIVolumeName(ctx, filesystem, volumeName)
10471060
if err != nil {
10481061
klog.V(4).Infof("Error searching for orphaned volume %s: %v", volumeName, err)
1049-
return nil, false, nil // Not found or error - continue with normal creation
1062+
subvol = nil // Continue to fallback search
1063+
}
1064+
1065+
// Fallback: search by PVC name + namespace stored in xattr properties.
1066+
// This handles the case where a PVC is recreated with a new UID — the CSI volume name
1067+
// changes (pvc-<new-uid>) but the stored pvc_name/pvc_namespace still match.
1068+
if subvol == nil {
1069+
subvol = s.findSubvolumeByPVCIdentity(ctx, params, filesystem)
10501070
}
1071+
10511072
if subvol == nil {
10521073
klog.V(4).Infof("No orphaned volume found for %s", volumeName)
10531074
return nil, false, nil // Not found - continue with normal creation
10541075
}
10551076

1056-
// Found a subvolume with matching CSI volume name - check if adoption is allowed
1077+
// Found a subvolume - check if adoption is allowed
10571078
props := subvol.Properties
10581079
if props == nil {
10591080
klog.V(4).Infof("Subvolume %s/%s has no properties, cannot adopt", subvol.Filesystem, subvol.Name)
@@ -1123,6 +1144,43 @@ func (s *ControllerService) checkAndAdoptVolume(ctx context.Context, req *csi.Cr
11231144
}
11241145
}
11251146

1147+
// findSubvolumeByPVCIdentity searches for an orphaned subvolume by its stored
1148+
// pvc_name and pvc_namespace xattr properties. This is used as a fallback when
1149+
// the CSI volume name search fails, which happens when a PVC is recreated with
1150+
// a new UID (e.g., cluster rebuild) but the underlying subvolume still has the
1151+
// original PVC identity in its properties.
1152+
func (s *ControllerService) findSubvolumeByPVCIdentity(ctx context.Context, params map[string]string, filesystem string) *nastyapi.Subvolume {
1153+
pvcName := params[CSIPVCName]
1154+
pvcNamespace := params[CSIPVCNamespace]
1155+
if pvcName == "" || pvcNamespace == "" {
1156+
return nil
1157+
}
1158+
1159+
klog.V(4).Infof("Fallback adoption search: looking for subvolume with pvc_name=%s, pvc_namespace=%s", pvcName, pvcNamespace)
1160+
1161+
// Search by pvc_name first, then filter by pvc_namespace
1162+
candidates, err := s.apiClient.FindSubvolumesByProperty(ctx, nastyapi.PropertyPVCName, pvcName, filesystem)
1163+
if err != nil {
1164+
klog.V(4).Infof("Error searching by PVC identity (%s/%s): %v", pvcNamespace, pvcName, err)
1165+
return nil
1166+
}
1167+
1168+
for i := range candidates {
1169+
props := candidates[i].Properties
1170+
if props == nil {
1171+
continue
1172+
}
1173+
if props[nastyapi.PropertyPVCNamespace] == pvcNamespace {
1174+
klog.V(4).Infof("Found subvolume by PVC identity: %s/%s (pvc=%s/%s)",
1175+
candidates[i].Filesystem, candidates[i].Name, pvcNamespace, pvcName)
1176+
return &candidates[i]
1177+
}
1178+
}
1179+
1180+
klog.V(4).Infof("No subvolume found with pvc_name=%s, pvc_namespace=%s", pvcName, pvcNamespace)
1181+
return nil
1182+
}
1183+
11261184
// ControllerGetCapabilities returns controller capabilities.
11271185
func (s *ControllerService) ControllerGetCapabilities(_ context.Context, _ *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
11281186
klog.V(4).Info("ControllerGetCapabilities called")

pkg/driver/controller_iscsi.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ type iscsiVolumeParams struct {
3131
foregroundTarget string
3232
backgroundTarget string
3333
promoteTarget string
34+
metadataTarget string
3435
server string
3536
filesystem string
3637
requestedCapacity int64
38+
dataReplicas uint32
3739
markAdoptable bool
3840
encrypted bool
3941
}
@@ -100,6 +102,11 @@ func validateISCSIParams(req *csi.CreateVolumeRequest) (*iscsiVolumeParams, erro
100102
foregroundTarget := params["foregroundTarget"]
101103
backgroundTarget := params["backgroundTarget"]
102104
promoteTarget := params["promoteTarget"]
105+
metadataTarget := params["metadataTarget"]
106+
dataReplicas, err := parseDataReplicas(params["dataReplicas"])
107+
if err != nil {
108+
return nil, err
109+
}
103110

104111
return &iscsiVolumeParams{
105112
requestedCapacity: requestedCapacity,
@@ -115,6 +122,8 @@ func validateISCSIParams(req *csi.CreateVolumeRequest) (*iscsiVolumeParams, erro
115122
foregroundTarget: foregroundTarget,
116123
backgroundTarget: backgroundTarget,
117124
promoteTarget: promoteTarget,
125+
metadataTarget: metadataTarget,
126+
dataReplicas: dataReplicas,
118127
pvcName: pvcName,
119128
pvcNamespace: pvcNamespace,
120129
storageClass: storageClass,
@@ -192,7 +201,7 @@ func (s *ControllerService) createISCSIVolume(ctx context.Context, req *csi.Crea
192201

193202
// Step 1: Create or reuse block subvolume
194203
subvol, subvolIsNew, err := s.getOrCreateSubvolume(ctx, params.filesystem, params.subvolumeName,
195-
"block", params.comment, params.compression, params.foregroundTarget, params.backgroundTarget, params.promoteTarget, params.requestedCapacity, timer)
204+
"block", params.comment, params.compression, params.foregroundTarget, params.backgroundTarget, params.promoteTarget, params.metadataTarget, params.dataReplicas, params.requestedCapacity, timer)
196205
if err != nil {
197206
return nil, err
198207
}

pkg/driver/controller_nfs.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ type nfsVolumeParams struct {
3030
foregroundTarget string
3131
backgroundTarget string
3232
promoteTarget string
33+
metadataTarget string
3334
pvcName string
3435
pvcNamespace string
3536
storageClass string
3637
nfsClients []nastyapi.NFSClient
3738
requestedCapacity int64
39+
dataReplicas uint32
3840
markAdoptable bool
3941
encrypted bool
4042
}
@@ -118,6 +120,11 @@ func validateNFSParams(req *csi.CreateVolumeRequest) (*nfsVolumeParams, error) {
118120
foregroundTarget := params["foregroundTarget"]
119121
backgroundTarget := params["backgroundTarget"]
120122
promoteTarget := params["promoteTarget"]
123+
metadataTarget := params["metadataTarget"]
124+
dataReplicas, err := parseDataReplicas(params["dataReplicas"])
125+
if err != nil {
126+
return nil, err
127+
}
121128

122129
return &nfsVolumeParams{
123130
filesystem: filesystem,
@@ -134,6 +141,8 @@ func validateNFSParams(req *csi.CreateVolumeRequest) (*nfsVolumeParams, error) {
134141
foregroundTarget: foregroundTarget,
135142
backgroundTarget: backgroundTarget,
136143
promoteTarget: promoteTarget,
144+
metadataTarget: metadataTarget,
145+
dataReplicas: dataReplicas,
137146
pvcName: pvcName,
138147
pvcNamespace: pvcNamespace,
139148
storageClass: storageClass,
@@ -374,6 +383,12 @@ func (s *ControllerService) createNFSVolume(ctx context.Context, req *csi.Create
374383
if params.promoteTarget != "" {
375384
createParams.PromoteTarget = params.promoteTarget
376385
}
386+
if params.metadataTarget != "" {
387+
createParams.MetadataTarget = params.metadataTarget
388+
}
389+
if params.dataReplicas > 0 {
390+
createParams.DataReplicas = &params.dataReplicas
391+
}
377392
if params.requestedCapacity > 0 {
378393
volsize := uint64(params.requestedCapacity)
379394
createParams.VolsizeBytes = &volsize
@@ -695,7 +710,7 @@ func (s *ControllerService) expandNFSVolume(ctx context.Context, meta *VolumeMet
695710

696711
// getOrCreateSubvolume gets an existing subvolume or creates a new one.
697712
// Returns (subvolume, isNewlyCreated, error).
698-
func (s *ControllerService) getOrCreateSubvolume(ctx context.Context, filesystem, name, subvolumeType, comment, compression, foregroundTarget, backgroundTarget, promoteTarget string, requestedCapacity int64, timer *metrics.OperationTimer) (*nastyapi.Subvolume, bool, error) {
713+
func (s *ControllerService) getOrCreateSubvolume(ctx context.Context, filesystem, name, subvolumeType, comment, compression, foregroundTarget, backgroundTarget, promoteTarget, metadataTarget string, dataReplicas uint32, requestedCapacity int64, timer *metrics.OperationTimer) (*nastyapi.Subvolume, bool, error) {
699714
// Try to get existing subvolume
700715
existing, err := s.apiClient.GetSubvolume(ctx, filesystem, name)
701716
if err == nil && existing != nil {
@@ -726,6 +741,12 @@ func (s *ControllerService) getOrCreateSubvolume(ctx context.Context, filesystem
726741
if promoteTarget != "" {
727742
createParams.PromoteTarget = promoteTarget
728743
}
744+
if metadataTarget != "" {
745+
createParams.MetadataTarget = metadataTarget
746+
}
747+
if dataReplicas > 0 {
748+
createParams.DataReplicas = &dataReplicas
749+
}
729750
if requestedCapacity > 0 {
730751
volsize := uint64(requestedCapacity)
731752
createParams.VolsizeBytes = &volsize

pkg/driver/controller_nvmeof.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ type nvmeofVolumeParams struct {
4040
foregroundTarget string
4141
backgroundTarget string
4242
promoteTarget string
43+
metadataTarget string
4344
requestedCapacity int64
45+
dataReplicas uint32
4446
markAdoptable bool
4547
encrypted bool
4648
}
@@ -121,6 +123,11 @@ func validateNVMeOFParams(req *csi.CreateVolumeRequest) (*nvmeofVolumeParams, er
121123
foregroundTarget := params["foregroundTarget"]
122124
backgroundTarget := params["backgroundTarget"]
123125
promoteTarget := params["promoteTarget"]
126+
metadataTarget := params["metadataTarget"]
127+
dataReplicas, err := parseDataReplicas(params["dataReplicas"])
128+
if err != nil {
129+
return nil, err
130+
}
124131

125132
return &nvmeofVolumeParams{
126133
filesystem: filesystem,
@@ -136,6 +143,8 @@ func validateNVMeOFParams(req *csi.CreateVolumeRequest) (*nvmeofVolumeParams, er
136143
foregroundTarget: foregroundTarget,
137144
backgroundTarget: backgroundTarget,
138145
promoteTarget: promoteTarget,
146+
metadataTarget: metadataTarget,
147+
dataReplicas: dataReplicas,
139148
pvcName: pvcName,
140149
pvcNamespace: pvcNamespace,
141150
storageClass: storageClass,
@@ -218,7 +227,7 @@ func (s *ControllerService) createNVMeOFVolume(ctx context.Context, req *csi.Cre
218227

219228
// Step 1: Create or reuse block subvolume
220229
subvol, subvolIsNew, err := s.getOrCreateSubvolume(ctx, params.filesystem, params.subvolumeName,
221-
"block", params.comment, params.compression, params.foregroundTarget, params.backgroundTarget, params.promoteTarget, params.requestedCapacity, timer)
230+
"block", params.comment, params.compression, params.foregroundTarget, params.backgroundTarget, params.promoteTarget, params.metadataTarget, params.dataReplicas, params.requestedCapacity, timer)
222231
if err != nil {
223232
return nil, err
224233
}

pkg/driver/controller_smb.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ type smbVolumeParams struct {
2828
foregroundTarget string
2929
backgroundTarget string
3030
promoteTarget string
31+
metadataTarget string
3132
smbUsername string
3233
pvcName string
3334
pvcNamespace string
3435
storageClass string
3536
requestedCapacity int64
37+
dataReplicas uint32
3638
markAdoptable bool
3739
encrypted bool
3840
}
@@ -77,6 +79,11 @@ func validateSMBParams(req *csi.CreateVolumeRequest) (*smbVolumeParams, error) {
7779
foregroundTarget := params["foregroundTarget"]
7880
backgroundTarget := params["backgroundTarget"]
7981
promoteTarget := params["promoteTarget"]
82+
metadataTarget := params["metadataTarget"]
83+
dataReplicas, err := parseDataReplicas(params["dataReplicas"])
84+
if err != nil {
85+
return nil, err
86+
}
8087
encrypted := strings.EqualFold(params["encryption"], "true")
8188

8289
return &smbVolumeParams{
@@ -92,6 +99,8 @@ func validateSMBParams(req *csi.CreateVolumeRequest) (*smbVolumeParams, error) {
9299
foregroundTarget: foregroundTarget,
93100
backgroundTarget: backgroundTarget,
94101
promoteTarget: promoteTarget,
102+
metadataTarget: metadataTarget,
103+
dataReplicas: dataReplicas,
95104
smbUsername: params["smbUsername"],
96105
pvcName: params["csi.storage.k8s.io/pvc/name"],
97106
pvcNamespace: params["csi.storage.k8s.io/pvc/namespace"],
@@ -239,7 +248,7 @@ func (s *ControllerService) createSMBVolume(ctx context.Context, req *csi.Create
239248
// Subvolume exists but no SMB share - continue with share creation
240249
} else {
241250
// Create new subvolume
242-
newSubvol, _, createErr := s.getOrCreateSubvolume(ctx, params.filesystem, params.subvolumeName, "filesystem", params.comment, params.compression, params.foregroundTarget, params.backgroundTarget, params.promoteTarget, params.requestedCapacity, timer)
251+
newSubvol, _, createErr := s.getOrCreateSubvolume(ctx, params.filesystem, params.subvolumeName, "filesystem", params.comment, params.compression, params.foregroundTarget, params.backgroundTarget, params.promoteTarget, params.metadataTarget, params.dataReplicas, params.requestedCapacity, timer)
243252
if createErr != nil {
244253
return nil, createErr
245254
}

0 commit comments

Comments
 (0)