@@ -18,32 +18,132 @@ package smb
1818
1919import (
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
2855func (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
43113func (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
105205func (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