Skip to content

Commit 38d6114

Browse files
author
Saurav Tiwary
committed
feat: Implement support for expanding volume
1 parent b6d793b commit 38d6114

File tree

12 files changed

+296
-13
lines changed

12 files changed

+296
-13
lines changed

charts/latest/blob-csi-driver/templates/csi-blob-controller.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,26 @@ spec:
119119
requests:
120120
cpu: 10m
121121
memory: 20Mi
122+
- name: csi-resizer
123+
image: "{{ .Values.image.csiResizer.repository }}:{{ .Values.image.csiResizer.tag }}"
124+
args:
125+
- "-csi-address=$(ADDRESS)"
126+
- "-v=5"
127+
- "-leader-election"
128+
env:
129+
- name: ADDRESS
130+
value: /csi/csi.sock
131+
imagePullPolicy: {{ .Values.image.csiResizer.pullPolicy }}
132+
volumeMounts:
133+
- name: socket-dir
134+
mountPath: /csi
135+
resources:
136+
limits:
137+
cpu: 100m
138+
memory: 100Mi
139+
requests:
140+
cpu: 10m
141+
memory: 20Mi
122142
volumes:
123143
- name: socket-dir
124144
emptyDir: {}

charts/latest/blob-csi-driver/templates/rbac-csi-blob-controller.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,46 @@ roleRef:
4646

4747
---
4848

49+
kind: ClusterRole
50+
apiVersion: rbac.authorization.k8s.io/v1
51+
metadata:
52+
name: blob-external-resizer-role
53+
namespace: {{ .Release.Namespace }}
54+
{{ include "blob.labels" . | indent 2 }}
55+
rules:
56+
- apiGroups: [""]
57+
resources: ["persistentvolumes"]
58+
verbs: ["get", "list", "watch", "update", "patch"]
59+
- apiGroups: [""]
60+
resources: ["persistentvolumeclaims"]
61+
verbs: ["get", "list", "watch"]
62+
- apiGroups: [""]
63+
resources: ["persistentvolumeclaims/status"]
64+
verbs: ["update", "patch"]
65+
- apiGroups: [""]
66+
resources: ["events"]
67+
verbs: ["list", "watch", "create", "update", "patch"]
68+
- apiGroups: ["coordination.k8s.io"]
69+
resources: ["leases"]
70+
verbs: ["get", "list", "watch", "create", "update", "patch"]
71+
---
72+
kind: ClusterRoleBinding
73+
apiVersion: rbac.authorization.k8s.io/v1
74+
metadata:
75+
name: blob-csi-resizer-role
76+
namespace: {{ .Release.Namespace }}
77+
{{ include "blob.labels" . | indent 2 }}
78+
subjects:
79+
- kind: ServiceAccount
80+
name: csi-blob-controller-sa
81+
namespace: {{ .Release.Namespace }}
82+
roleRef:
83+
kind: ClusterRole
84+
name: blob-external-resizer-role
85+
apiGroup: rbac.authorization.k8s.io
86+
87+
---
88+
4989
kind: ClusterRole
5090
apiVersion: rbac.authorization.k8s.io/v1
5191
metadata:

charts/latest/blob-csi-driver/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ image:
1515
repository: mcr.microsoft.com/oss/kubernetes-csi/csi-node-driver-registrar
1616
tag: v2.0.1
1717
pullPolicy: IfNotPresent
18+
csiResizer:
19+
repository: mcr.microsoft.com/oss/kubernetes-csi/csi-resizer
20+
tag: v0.5.0
21+
pullPolicy: IfNotPresent
1822

1923
serviceAccount:
2024
create: true

deploy/csi-blob-controller.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,25 @@ spec:
113113
requests:
114114
cpu: 10m
115115
memory: 20Mi
116+
- name: csi-resizer
117+
image: mcr.microsoft.com/oss/kubernetes-csi/csi-resizer:v0.5.0
118+
args:
119+
- "-csi-address=$(ADDRESS)"
120+
- "-v=5"
121+
- "-leader-election"
122+
env:
123+
- name: ADDRESS
124+
value: /csi/csi.sock
125+
volumeMounts:
126+
- name: socket-dir
127+
mountPath: /csi
128+
resources:
129+
limits:
130+
cpu: 100m
131+
memory: 100Mi
132+
requests:
133+
cpu: 10m
134+
memory: 20Mi
116135
volumes:
117136
- name: socket-dir
118137
emptyDir: {}

pkg/blob/blob.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ const (
8383
shareNotFound = "The specified share does not exist"
8484
shareBeingDeleted = "The specified share is being deleted"
8585
clientThrottled = "client throttled"
86+
87+
// containerMaxSize is the max size of the blob container. See https://docs.microsoft.com/en-us/azure/storage/blobs/scalability-targets#scale-targets-for-blob-storage
88+
containerMaxSize = 100 * util.TiB
8689
)
8790

8891
var (
@@ -141,6 +144,7 @@ func (d *Driver) Run(endpoint, kubeconfig string, testBool bool) {
141144
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
142145
//csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
143146
//csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
147+
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
144148
})
145149
d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
146150
csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,

pkg/blob/controllerserver.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,5 +360,26 @@ func (d *Driver) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsReques
360360

361361
// ControllerExpandVolume controller expand volume
362362
func (d *Driver) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
363-
return nil, status.Error(codes.Unimplemented, "ControllerExpandVolume is not yet implemented")
363+
if len(req.GetVolumeId()) == 0 {
364+
return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
365+
}
366+
367+
if req.GetCapacityRange() == nil {
368+
return nil, status.Error(codes.InvalidArgument, "Capacity Range missing in request")
369+
}
370+
371+
if err := d.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_EXPAND_VOLUME); err != nil {
372+
return nil, fmt.Errorf("invalid expand volume req: %v", req)
373+
}
374+
375+
volSizeBytes := int64(req.GetCapacityRange().GetRequiredBytes())
376+
requestGiB := int64(util.RoundUpGiB(volSizeBytes))
377+
378+
if volSizeBytes > containerMaxSize {
379+
return nil, status.Errorf(codes.OutOfRange, "required bytes (%d) exceeds the maximum supported bytes (%d)", volSizeBytes, containerMaxSize)
380+
}
381+
382+
klog.V(2).Infof("ControllerExpandVolume(%s) successfully, currentQuota: %d Gi", req.VolumeId, requestGiB)
383+
384+
return &csi.ControllerExpandVolumeResponse{CapacityBytes: req.GetCapacityRange().GetRequiredBytes()}, nil
364385
}

pkg/blob/controllerserver_test.go

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -551,11 +551,54 @@ func TestListSnapshots(t *testing.T) {
551551
}
552552

553553
func TestControllerExpandVolume(t *testing.T) {
554-
d := NewFakeDriver()
555-
req := csi.ControllerExpandVolumeRequest{}
556-
resp, err := d.ControllerExpandVolume(context.Background(), &req)
557-
assert.Nil(t, resp)
558-
if !reflect.DeepEqual(err, status.Error(codes.Unimplemented, "ControllerExpandVolume is not yet implemented")) {
559-
t.Errorf("Unexpected error: %v", err)
554+
testCases := []struct {
555+
name string
556+
testFunc func(t *testing.T)
557+
}{
558+
{
559+
name: "volume ID missing",
560+
testFunc: func(t *testing.T) {
561+
d := NewFakeDriver()
562+
req := &csi.ControllerExpandVolumeRequest{}
563+
_, err := d.ControllerExpandVolume(context.Background(), req)
564+
expectedErr := status.Error(codes.InvalidArgument, "Volume ID missing in request")
565+
if !reflect.DeepEqual(err, expectedErr) {
566+
t.Errorf("actualErr: (%v), expectedErr: (%v)", err, expectedErr)
567+
}
568+
},
569+
},
570+
{
571+
name: "Capacity Range missing",
572+
testFunc: func(t *testing.T) {
573+
d := NewFakeDriver()
574+
req := &csi.ControllerExpandVolumeRequest{
575+
VolumeId: "unit-test",
576+
}
577+
_, err := d.ControllerExpandVolume(context.Background(), req)
578+
expectedErr := status.Error(codes.InvalidArgument, "Capacity Range missing in request")
579+
if !reflect.DeepEqual(err, expectedErr) {
580+
t.Errorf("actualErr: (%v), expectedErr: (%v)", err, expectedErr)
581+
}
582+
},
583+
},
584+
{
585+
name: "invalid expand volume req",
586+
testFunc: func(t *testing.T) {
587+
d := NewFakeDriver()
588+
req := &csi.ControllerExpandVolumeRequest{
589+
VolumeId: "unit-test",
590+
CapacityRange: &csi.CapacityRange{},
591+
}
592+
_, err := d.ControllerExpandVolume(context.Background(), req)
593+
expectedErr := fmt.Errorf("invalid expand volume req: volume_id:\"unit-test\" capacity_range:<> ")
594+
if !reflect.DeepEqual(err, expectedErr) {
595+
t.Errorf("actualErr: (%v), expectedErr: (%v)", err, expectedErr)
596+
}
597+
},
598+
},
599+
}
600+
601+
for _, tc := range testCases {
602+
t.Run(tc.name, tc.testFunc)
560603
}
561604
}

pkg/util/util.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
const (
2525
GiB = 1024 * 1024 * 1024
26+
TiB = 1024 * GiB
2627
)
2728

2829
// RoundUpBytes rounds up the volume size in bytes upto multiplications of GiB

test/e2e/driver/driver.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,17 @@ func getStorageClass(
5858
defaultBindingMode := storagev1.VolumeBindingImmediate
5959
bindingMode = &defaultBindingMode
6060
}
61+
allowVolumeExpansion := true
6162
return &storagev1.StorageClass{
6263
ObjectMeta: metav1.ObjectMeta{
6364
GenerateName: generateName,
6465
},
65-
Provisioner: provisioner,
66-
Parameters: parameters,
67-
MountOptions: mountOptions,
68-
ReclaimPolicy: reclaimPolicy,
69-
VolumeBindingMode: bindingMode,
70-
AllowedTopologies: allowedTopologies,
66+
Provisioner: provisioner,
67+
Parameters: parameters,
68+
MountOptions: mountOptions,
69+
ReclaimPolicy: reclaimPolicy,
70+
VolumeBindingMode: bindingMode,
71+
AllowedTopologies: allowedTopologies,
72+
AllowVolumeExpansion: &allowVolumeExpansion,
7173
}
7274
}

test/e2e/dynamic_provisioning_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,4 +313,27 @@ var _ = ginkgo.Describe("[blob-csi-e2e] Dynamic Provisioning", func() {
313313
}
314314
test.Run(cs, ns)
315315
})
316+
317+
ginkgo.It("should create a volume on demand and resize it [kubernetes.io/blob-csi] [blob.csi.azure.com]", func() {
318+
pods := []testsuites.PodDetails{
319+
{
320+
Cmd: "echo 'hello world' > /mnt/test-1/data && grep 'hello world' /mnt/test-1/data",
321+
Volumes: []testsuites.VolumeDetails{
322+
{
323+
ClaimSize: "10Gi",
324+
VolumeMount: testsuites.VolumeMountDetails{
325+
NameGenerate: "test-volume-",
326+
MountPathGenerate: "/mnt/test-",
327+
},
328+
},
329+
},
330+
},
331+
}
332+
test := testsuites.DynamicallyProvisionedResizeVolumeTest{
333+
CSIDriver: testDriver,
334+
Pods: pods,
335+
StorageClassParameters: map[string]string{"skuName": "Standard_LRS"},
336+
}
337+
test.Run(cs, ns)
338+
})
316339
})

0 commit comments

Comments
 (0)