Skip to content

Commit f4b54f2

Browse files
authored
Merge pull request #259 from boddumanohar/createvol-delete-vol-full-support
create subDir in CreateVolume and delete subDir in DeleteVolume
2 parents 7104ad2 + d020b78 commit f4b54f2

File tree

15 files changed

+444
-75
lines changed

15 files changed

+444
-75
lines changed
14 Bytes
Binary file not shown.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ spec:
1515
{{ include "smb.labels" . | indent 6 }}
1616
app: csi-smb-controller
1717
spec:
18+
dnsPolicy: ClusterFirstWithHostNet
1819
serviceAccountName: csi-smb-controller-sa
1920
nodeSelector:
2021
kubernetes.io/os: linux
@@ -96,6 +97,8 @@ spec:
9697
env:
9798
- name: CSI_ENDPOINT
9899
value: unix:///csi/csi.sock
100+
securityContext:
101+
privileged: true
99102
volumeMounts:
100103
- mountPath: /csi
101104
name: socket-dir

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ rules:
3737
- apiGroups: ["coordination.k8s.io"]
3838
resources: ["leases"]
3939
verbs: ["get", "list", "watch", "create", "update", "patch"]
40+
- apiGroups: [""]
41+
resources: ["secrets"]
42+
verbs: ["get"]
4043
---
4144

4245
kind: ClusterRoleBinding

deploy/csi-smb-controller.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ spec:
1414
labels:
1515
app: csi-smb-controller
1616
spec:
17+
dnsPolicy: ClusterFirstWithHostNet
1718
serviceAccountName: csi-smb-controller-sa
1819
nodeSelector:
1920
kubernetes.io/os: linux
@@ -89,6 +90,8 @@ spec:
8990
env:
9091
- name: CSI_ENDPOINT
9192
value: unix:///csi/csi.sock
93+
securityContext:
94+
privileged: true
9295
volumeMounts:
9396
- mountPath: /csi
9497
name: socket-dir

deploy/example/storageclass-smb.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ parameters:
1212
source: "//smb-server.default.svc.cluster.local/share"
1313
csi.storage.k8s.io/node-stage-secret-name: "smbcreds"
1414
csi.storage.k8s.io/node-stage-secret-namespace: "default"
15-
createSubDir: "false" # optional: create a sub dir for new volume
15+
csi.storage.k8s.io/provisioner-secret-name: "smbcreds"
16+
csi.storage.k8s.io/provisioner-secret-namespace: "default"
17+
createSubDir: "true" # optional: create a sub dir for new volume
1618
reclaimPolicy: Retain # only retain is supported
1719
volumeBindingMode: Immediate
1820
mountOptions:

deploy/rbac-csi-smb-controller.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ rules:
3333
- apiGroups: ["coordination.k8s.io"]
3434
resources: ["leases"]
3535
verbs: ["get", "list", "watch", "create", "update", "patch"]
36+
- apiGroups: [""]
37+
resources: ["secrets"]
38+
verbs: ["get"]
3639
---
3740

3841
kind: ClusterRoleBinding

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ require (
2525
k8s.io/client-go v0.21.0
2626
k8s.io/klog/v2 v2.8.0
2727
k8s.io/kubernetes v1.21.0
28-
k8s.io/mount-utils v0.0.0
28+
k8s.io/mount-utils v0.21.1
2929
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
3030
sigs.k8s.io/yaml v1.2.0
3131
)

pkg/smb/controllerserver.go

Lines changed: 225 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,132 @@ package smb
1818

1919
import (
2020
"context"
21+
"fmt"
22+
"os"
23+
"path/filepath"
24+
"regexp"
25+
"strings"
2126

2227
"github.com/container-storage-interface/spec/lib/go/csi"
2328
"google.golang.org/grpc/codes"
2429
"google.golang.org/grpc/status"
30+
"k8s.io/klog/v2"
31+
)
32+
33+
// smbVolume is an internal representation of a volume
34+
// created by the provisioner.
35+
type smbVolume struct {
36+
// Volume id
37+
id string
38+
// Address of the SMB server.
39+
sourceField string
40+
// Subdirectory of the SMB server to create volumes under
41+
subDir string
42+
// size of volume
43+
size int64
44+
}
45+
46+
// Ordering of elements in the CSI volume id.
47+
// ID is of the form {server}/{subDir}.
48+
const (
49+
idsourceField = iota
50+
idSubDir
51+
totalIDElements // Always last
2552
)
2653

2754
// CreateVolume only supports static provisioning, no create volume action
2855
func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
56+
name := req.GetName()
57+
if len(name) == 0 {
58+
return nil, status.Error(codes.InvalidArgument, "CreateVolume name must be provided")
59+
}
60+
61+
var volCap *csi.VolumeCapability
2962
volumeCapabilities := req.GetVolumeCapabilities()
3063
if len(volumeCapabilities) == 0 {
3164
return nil, status.Error(codes.InvalidArgument, "CreateVolume Volume capabilities must be provided")
3265
}
33-
return &csi.CreateVolumeResponse{
34-
Volume: &csi.Volume{
35-
VolumeId: req.GetName(),
36-
CapacityBytes: req.GetCapacityRange().GetRequiredBytes(),
37-
VolumeContext: req.GetParameters(),
38-
},
39-
}, nil
66+
if len(volumeCapabilities) > 0 {
67+
volCap = req.GetVolumeCapabilities()[0]
68+
}
69+
70+
reqCapacity := req.GetCapacityRange().GetRequiredBytes()
71+
smbVol, err := d.newSMBVolume(name, reqCapacity, req.GetParameters())
72+
if err != nil {
73+
return nil, status.Error(codes.InvalidArgument, err.Error())
74+
}
75+
76+
// check if create SubDir is enable in storage class parameters
77+
parameters := req.GetParameters()
78+
var createSubDir string
79+
for k, v := range parameters {
80+
switch strings.ToLower(k) {
81+
case createSubDirField:
82+
createSubDir = v
83+
}
84+
}
85+
86+
secrets := req.GetSecrets()
87+
if strings.EqualFold(createSubDir, "true") {
88+
if len(secrets) > 0 {
89+
// Mount smb base share so we can create a subdirectory
90+
if err := d.internalMount(ctx, smbVol, volCap, secrets); err != nil {
91+
return nil, status.Errorf(codes.Internal, "failed to mount smb server: %v", err.Error())
92+
}
93+
defer func() {
94+
if err = d.internalUnmount(ctx, smbVol); err != nil {
95+
klog.Warningf("failed to unmount smb server: %v", err.Error())
96+
}
97+
}()
98+
// Create subdirectory under base-dir
99+
// TODO: revisit permissions
100+
internalVolumePath := d.getInternalVolumePath(smbVol)
101+
if err = os.Mkdir(internalVolumePath, 0777); err != nil && !os.IsExist(err) {
102+
return nil, status.Errorf(codes.Internal, "failed to make subdirectory: %v", err.Error())
103+
}
104+
parameters[sourceField] = parameters[sourceField] + "/" + smbVol.subDir
105+
} else {
106+
klog.Warningf("CreateVolume: Volume secrets should be provided when createSubDir is true")
107+
}
108+
}
109+
return &csi.CreateVolumeResponse{Volume: d.smbVolToCSI(smbVol, parameters)}, nil
40110
}
41111

42112
// DeleteVolume only supports static provisioning, no delete volume action
43113
func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
44-
if len(req.GetVolumeId()) == 0 {
45-
return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
114+
volumeID := req.GetVolumeId()
115+
if volumeID == "" {
116+
return nil, status.Error(codes.InvalidArgument, "volume id is empty")
117+
}
118+
smbVol, err := d.getSmbVolFromID(volumeID)
119+
if err != nil {
120+
// An invalid ID should be treated as doesn't exist
121+
klog.Warningf("failed to get smb volume for volume id %v deletion: %v", volumeID, err)
122+
return &csi.DeleteVolumeResponse{}, nil
123+
}
124+
125+
secrets := req.GetSecrets()
126+
if len(secrets) > 0 {
127+
// Mount smb base share so we can delete the subdirectory
128+
if err = d.internalMount(ctx, smbVol, nil, secrets); err != nil {
129+
return nil, status.Errorf(codes.Internal, "failed to mount smb server: %v", err.Error())
130+
}
131+
defer func() {
132+
if err = d.internalUnmount(ctx, smbVol); err != nil {
133+
klog.Warningf("failed to unmount smb server: %v", err.Error())
134+
}
135+
}()
136+
137+
// Delete subdirectory under base-dir
138+
internalVolumePath := d.getInternalVolumePath(smbVol)
139+
klog.V(2).Infof("Removing subdirectory at %v", internalVolumePath)
140+
if err = os.RemoveAll(internalVolumePath); err != nil {
141+
return nil, status.Errorf(codes.Internal, "failed to delete subdirectory: %v", err.Error())
142+
}
143+
} else {
144+
klog.Warningf("DeleteVolume: Volume secrets should be provided")
46145
}
146+
47147
return &csi.DeleteVolumeResponse{}, nil
48148
}
49149

@@ -105,3 +205,119 @@ func (d *Driver) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequ
105205
func (d *Driver) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
106206
return nil, status.Error(codes.Unimplemented, "")
107207
}
208+
209+
// Given a smbVolume, return a CSI volume id
210+
func (d *Driver) getVolumeIDFromSmbVol(vol *smbVolume) string {
211+
idElements := make([]string, totalIDElements)
212+
idElements[idsourceField] = strings.Trim(vol.sourceField, "/")
213+
idElements[idSubDir] = strings.Trim(vol.subDir, "/")
214+
return strings.Join(idElements, "/")
215+
}
216+
217+
// Get working directory for CreateVolume and DeleteVolume
218+
func (d *Driver) getInternalMountPath(vol *smbVolume) string {
219+
// use default if empty
220+
if d.workingMountDir == "" {
221+
d.workingMountDir = "/tmp"
222+
}
223+
return filepath.Join(d.workingMountDir, vol.subDir)
224+
}
225+
226+
// Mount smb server at base-dir
227+
func (d *Driver) internalMount(ctx context.Context, vol *smbVolume, volCap *csi.VolumeCapability, secrets map[string]string) error {
228+
stagingPath := d.getInternalMountPath(vol)
229+
230+
if volCap == nil {
231+
volCap = &csi.VolumeCapability{
232+
AccessType: &csi.VolumeCapability_Mount{
233+
Mount: &csi.VolumeCapability_MountVolume{},
234+
},
235+
}
236+
}
237+
238+
klog.V(4).Infof("internally mounting %v at %v", sourceField, stagingPath)
239+
_, err := d.NodeStageVolume(ctx, &csi.NodeStageVolumeRequest{
240+
StagingTargetPath: stagingPath,
241+
VolumeContext: map[string]string{
242+
sourceField: vol.sourceField,
243+
},
244+
VolumeCapability: volCap,
245+
VolumeId: vol.id,
246+
Secrets: secrets,
247+
})
248+
return err
249+
}
250+
251+
// Unmount smb server at base-dir
252+
func (d *Driver) internalUnmount(ctx context.Context, vol *smbVolume) error {
253+
targetPath := d.getInternalMountPath(vol)
254+
255+
// Unmount smb server at base-dir
256+
klog.V(4).Infof("internally unmounting %v", targetPath)
257+
_, err := d.NodeUnstageVolume(ctx, &csi.NodeUnstageVolumeRequest{
258+
VolumeId: vol.id,
259+
StagingTargetPath: d.getInternalMountPath(vol),
260+
})
261+
return err
262+
}
263+
264+
// Convert VolumeCreate parameters to an smbVolume
265+
func (d *Driver) newSMBVolume(name string, size int64, params map[string]string) (*smbVolume, error) {
266+
var sourceField string
267+
268+
// Validate parameters (case-insensitive).
269+
for k, v := range params {
270+
switch strings.ToLower(k) {
271+
case paramSource:
272+
sourceField = v
273+
}
274+
}
275+
276+
// Validate required parameters
277+
if sourceField == "" {
278+
return nil, fmt.Errorf("%v is a required parameter", paramSource)
279+
}
280+
281+
vol := &smbVolume{
282+
sourceField: sourceField,
283+
subDir: name,
284+
size: size,
285+
}
286+
vol.id = d.getVolumeIDFromSmbVol(vol)
287+
288+
return vol, nil
289+
}
290+
291+
// Get internal path where the volume is created
292+
// The reason why the internal path is "workingDir/subDir/subDir" is because:
293+
// * the semantic is actually "workingDir/volId/subDir" and volId == subDir.
294+
// * we need a mount directory per volId because you can have multiple
295+
// CreateVolume calls in parallel and they may use the same underlying share.
296+
// Instead of refcounting how many CreateVolume calls are using the same
297+
// share, it's simpler to just do a mount per request.
298+
func (d *Driver) getInternalVolumePath(vol *smbVolume) string {
299+
return filepath.Join(d.getInternalMountPath(vol), vol.subDir)
300+
}
301+
302+
// Convert into smbVolume into a csi.Volume
303+
func (d *Driver) smbVolToCSI(vol *smbVolume, parameters map[string]string) *csi.Volume {
304+
return &csi.Volume{
305+
CapacityBytes: 0, // by setting it to zero, Provisioner will use PVC requested size as PV size
306+
VolumeId: vol.id,
307+
VolumeContext: parameters,
308+
}
309+
}
310+
311+
// Given a CSI volume id, return a smbVolume
312+
func (d *Driver) getSmbVolFromID(id string) (*smbVolume, error) {
313+
volRegex := regexp.MustCompile("^([^/]+)/([^/]+)$")
314+
tokens := volRegex.FindStringSubmatch(id)
315+
if tokens == nil {
316+
return nil, fmt.Errorf("Could not split %q into server, baseDir and subDir", id)
317+
}
318+
return &smbVolume{
319+
id: id,
320+
sourceField: tokens[1],
321+
subDir: tokens[2],
322+
}, nil
323+
}

0 commit comments

Comments
 (0)