Skip to content

Commit 46ffeeb

Browse files
authored
Add Quota limit support on COS CSI (#291)
1 parent 2aa060b commit 46ffeeb

9 files changed

Lines changed: 456 additions & 25 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.24.5
44

55
require (
66
github.com/IBM/go-sdk-core/v5 v5.21.0
7-
github.com/IBM/ibm-cos-sdk-go v1.12.2
7+
github.com/IBM/ibm-cos-sdk-go v1.12.3
88
github.com/IBM/ibm-cos-sdk-go-config/v2 v2.3.0
99
github.com/IBM/ibm-csi-common v1.1.21
1010
github.com/IBM/ibmcloud-volume-interface v1.2.20

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU
44
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
55
github.com/IBM/go-sdk-core/v5 v5.21.0 h1:DUnYhvC4SoC8T84rx5omnhY3+xcQg/Whyoa3mDPIMkk=
66
github.com/IBM/go-sdk-core/v5 v5.21.0/go.mod h1:Q3BYO6iDA2zweQPDGbNTtqft5tDcEpm6RTuqMlPcvbw=
7-
github.com/IBM/ibm-cos-sdk-go v1.12.2 h1:71A4tDl8u6BZ548h71ecEe7fw5bBA7ECTVqYmeSQWQA=
8-
github.com/IBM/ibm-cos-sdk-go v1.12.2/go.mod h1:ODYcmrmdpjo5hVguq9RbD6xmC8xb1XZMG7NefUbJNcc=
7+
github.com/IBM/ibm-cos-sdk-go v1.12.3 h1:kMIs1nfPY0UXAMcW6bq8O9WOd6KgqiDBnIMd0e/fMqA=
8+
github.com/IBM/ibm-cos-sdk-go v1.12.3/go.mod h1:dt13UIqJRgfGIlSNlnf17JmAXlBXhfTgXLKV3as8ABk=
99
github.com/IBM/ibm-cos-sdk-go-config/v2 v2.3.0 h1:956Nqk0eKI3lq+AkzWXZDid4UZHRz0wWh1LwkleBsWk=
1010
github.com/IBM/ibm-cos-sdk-go-config/v2 v2.3.0/go.mod h1:chnQxV+i38wD0aIi4KNU5bP2uzPtc7EHqB3/8Rhyjlg=
1111
github.com/IBM/ibm-csi-common v1.1.21 h1:/OmjpF+YgmkLmtehFmmfWp9kHGP9iMNkTU/KQDYXzrY=

pkg/constants/constants.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ const (
4343
SecretNameKey = "cos.csi.driver/secret" // #nosec G101 -- false positive, this is not a credential
4444
SecretNamespaceKey = "cos.csi.driver/secret-namespace" // #nosec G101 -- false positive, this is not a credential
4545

46-
BucketVersioning = "bucketVersioning"
46+
BucketVersioning = "bucketVersioning"
47+
QuotaLimitKey = "quotaLimit"
48+
ResourceConfigApiKey = "resourceConfigApiKey" // #nosec G101 -- this is just a map key name, not a real credential
4749

4850
IsNodeServer = "IS_NODE_SERVER"
4951
KubeNodeName = "KUBE_NODE_NAME"

pkg/driver/controllerserver.go

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"errors"
1717
"fmt"
1818
"io"
19+
"strconv"
1920
"strings"
2021
"time"
2122

@@ -50,6 +51,7 @@ func (cs *controllerServer) CreateVolume(_ context.Context, req *csi.CreateVolum
5051
pvcName string
5152
pvcNamespace string
5253
bucketVersioning string
54+
quotaLimitEnabled bool
5355
)
5456

5557
modifiedRequest, err := utils.ReplaceAndReturnCopy(req)
@@ -60,7 +62,7 @@ func (cs *controllerServer) CreateVolume(_ context.Context, req *csi.CreateVolum
6062

6163
volumeName, err := sanitizeVolumeID(req.GetName())
6264
if err != nil {
63-
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Error in sanitizeVolumeID %v", err))
65+
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Error in sanitizeVolumeID %v", err))
6466
}
6567
volumeID := volumeName
6668
if len(volumeID) == 0 {
@@ -141,6 +143,28 @@ func (cs *controllerServer) CreateVolume(_ context.Context, req *csi.CreateVolum
141143

142144
secretMap = secretMapCustom
143145
}
146+
if quotaLimitStr, ok := secretMap[constants.QuotaLimitKey]; ok && quotaLimitStr != "" {
147+
klog.Infof("quotaLimit from secretMap: %q", quotaLimitStr)
148+
quotaLimitEnabled, err = strconv.ParseBool(quotaLimitStr)
149+
if err != nil {
150+
return nil, status.Error(codes.InvalidArgument,
151+
fmt.Sprintf("invalid quotaLimit value %q: must be 'true' or 'false'", quotaLimitStr))
152+
}
153+
154+
if quotaLimitEnabled {
155+
if secretMap[constants.ResourceConfigApiKey] == "" {
156+
return nil, status.Error(codes.InvalidArgument,
157+
"resourceConfigApiKey missing in secret, cannot set quota limit for bucket")
158+
}
159+
160+
quotaBytes := req.GetCapacityRange().GetRequiredBytes()
161+
if quotaBytes <= 0 {
162+
return nil, status.Error(codes.InvalidArgument,
163+
"enable quotaLimit requested but no positive storage size requested in PVC")
164+
}
165+
klog.Infof("enable quota limit requested with %d bytes", quotaBytes)
166+
}
167+
}
144168

145169
endPoint = secretMap["cosEndpoint"]
146170
if endPoint == "" {
@@ -216,6 +240,22 @@ func (cs *controllerServer) CreateVolume(_ context.Context, req *csi.CreateVolum
216240
klog.Infof("Created bucket: %s", bucketName)
217241
}
218242

243+
if quotaLimitEnabled {
244+
quotaBytes := req.GetCapacityRange().GetRequiredBytes()
245+
resConfApikey := secretMap[constants.ResourceConfigApiKey]
246+
247+
klog.Infof("Applying hard quota of %d bytes to bucket %s", quotaBytes, bucketName)
248+
err = sess.UpdateQuotaLimit(quotaBytes, resConfApikey, bucketName, endPoint, creds.IAMEndpoint)
249+
if err != nil {
250+
klog.Errorf("Failed to set quota limit on bucket %s: %v", bucketName, err)
251+
if params["userProvidedBucket"] == "false" {
252+
_ = sess.DeleteBucket(bucketName)
253+
}
254+
return nil, status.Error(codes.Internal, fmt.Sprintf("failed to set bucket quota limit: %v", err))
255+
}
256+
klog.Infof("Successfully applied hard quota %d bytes to bucket %s", quotaBytes, bucketName)
257+
}
258+
219259
if bucketVersioning != "" {
220260
enable := strings.ToLower(strings.TrimSpace(bucketVersioning)) == "true"
221261
klog.Infof("Bucket versioning value evaluated to: %t", enable)
@@ -247,6 +287,20 @@ func (cs *controllerServer) CreateVolume(_ context.Context, req *csi.CreateVolum
247287
return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("%v: %v", err, tempBucketName))
248288
}
249289

290+
if quotaLimitEnabled {
291+
quotaBytes := req.GetCapacityRange().GetRequiredBytes()
292+
resConfApikey := secretMap[constants.ResourceConfigApiKey]
293+
294+
klog.Infof("Applying hard quota of %d bytes to temp bucket %s", quotaBytes, tempBucketName)
295+
err = sess.UpdateQuotaLimit(quotaBytes, resConfApikey, tempBucketName, endPoint, creds.IAMEndpoint)
296+
if err != nil {
297+
klog.Errorf("Failed to set quota limit on temp bucket %s: %v", tempBucketName, err)
298+
_ = sess.DeleteBucket(tempBucketName)
299+
return nil, status.Error(codes.Internal, fmt.Sprintf("failed to set bucket quota limit: %v", err))
300+
}
301+
klog.Infof("Successfully applied hard quota %d bytes to temp bucket %s", quotaBytes, tempBucketName)
302+
}
303+
250304
if bucketVersioning != "" {
251305
enable := strings.ToLower(strings.TrimSpace(bucketVersioning)) == "true"
252306
klog.Infof("Temp bucket versioning value evaluated to: %t", enable)
@@ -505,6 +559,8 @@ func parseCustomSecret(secret *v1.Secret) map[string]string {
505559
locationConstraint string
506560
bucketVersioning string
507561
objectPath string
562+
resConfApiKey string
563+
quotaLimit string
508564
)
509565

510566
if bytesVal, ok := secret.Data["accessKey"]; ok {
@@ -551,6 +607,13 @@ func parseCustomSecret(secret *v1.Secret) map[string]string {
551607
objectPath = string(bytesVal)
552608
}
553609

610+
if bytesVal, ok := secret.Data[constants.ResourceConfigApiKey]; ok {
611+
resConfApiKey = string(bytesVal)
612+
}
613+
if bytesVal, ok := secret.Data[constants.QuotaLimitKey]; ok {
614+
quotaLimit = string(bytesVal)
615+
}
616+
554617
secretMapCustom["accessKey"] = accessKey
555618
secretMapCustom["secretKey"] = secretKey
556619
secretMapCustom["apiKey"] = apiKey
@@ -562,6 +625,8 @@ func parseCustomSecret(secret *v1.Secret) map[string]string {
562625
secretMapCustom["locationConstraint"] = locationConstraint
563626
secretMapCustom[constants.BucketVersioning] = bucketVersioning
564627
secretMapCustom["objectPath"] = objectPath
628+
secretMapCustom[constants.ResourceConfigApiKey] = resConfApiKey
629+
secretMapCustom[constants.QuotaLimitKey] = quotaLimit
565630

566631
return secretMapCustom
567632
}

0 commit comments

Comments
 (0)