1
1
package nfs
2
2
3
3
import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+
4
9
"github.com/container-storage-interface/spec/lib/go/csi"
5
10
"github.com/golang/glog"
6
11
"golang.org/x/net/context"
@@ -10,10 +15,93 @@ import (
10
15
11
16
type ControllerServer struct {
12
17
Driver * nfsDriver
18
+ // Working directory for the provisioner to temporarily mount nfs shares at
19
+ workingMountDir string
20
+ }
21
+
22
+ // nfsVolume is an internal representation of a volume
23
+ // created by the provisioner.
24
+ type nfsVolume struct {
25
+ // Volume id
26
+ id string
27
+ // Address of the NFS server.
28
+ // Matches paramServer.
29
+ server string
30
+ // Base directory of the NFS server to create volumes under
31
+ // Matches paramBaseDir.
32
+ baseDir string
33
+ // Subdirectory of the NFS server to create volumes under
34
+ subDir string
13
35
}
14
36
37
+ // CSI CreateVolume parameters
38
+ const (
39
+ // Address of the NFS server
40
+ paramServer = "server"
41
+ // Base directory of the NFS server to create volumes under.
42
+ // The base directory must be a direct child of the root directory.
43
+ // The root directory is ommitted from the string, for example:
44
+ // "base" instead of "/base"
45
+ paramBaseDir = "base-dir"
46
+ )
47
+
48
+ // CSI Volume attributes
49
+ const (
50
+ // Address of the NFS server
51
+ attrServer = "server"
52
+ // Path to the NFS share on the server
53
+ attrShare = "share"
54
+ )
55
+
56
+ // Ordering of elements in the CSI volume id.
57
+ // ID is of the form {server}/{baseDir}/{subDir}.
58
+ // TODO: This volume id format limits baseDir and
59
+ // subDir to only be one directory deep.
60
+ // Adding a new element should always go at the end
61
+ // before totalIDElements
62
+ const (
63
+ idServer = iota
64
+ idBaseDir
65
+ idSubDir
66
+ totalIDElements // Always last
67
+ )
68
+
15
69
func (cs * ControllerServer ) CreateVolume (ctx context.Context , req * csi.CreateVolumeRequest ) (* csi.CreateVolumeResponse , error ) {
16
- return nil , status .Error (codes .Unimplemented , "" )
70
+
71
+ // Validate arguments
72
+ name := req .GetName ()
73
+ if len (name ) == 0 {
74
+ return nil , status .Error (codes .InvalidArgument , "CreateVolume name must be provided" )
75
+ }
76
+
77
+ if err := cs .validateVolumeCapabilities (req .GetVolumeCapabilities ()); err != nil {
78
+ return nil , status .Error (codes .InvalidArgument , err .Error ())
79
+ }
80
+
81
+ nfsVol , err := cs .newNFSVolume (name , req .GetParameters ())
82
+ if err != nil {
83
+ return nil , status .Error (codes .InvalidArgument , err .Error ())
84
+ }
85
+
86
+ // Mount nfs base share so we can create a subdirectory
87
+ if err = cs .internalMount (ctx , nfsVol ); err != nil {
88
+ return nil , status .Errorf (codes .Internal , "failed to mount nfs server: %v" , err .Error ())
89
+ }
90
+ defer func () {
91
+ if err = cs .internalUnmount (ctx , nfsVol ); err != nil {
92
+ glog .Warningf ("failed to unmount nfs server: %v" , err .Error ())
93
+ }
94
+ }()
95
+
96
+ // Create subdirectory under base-dir
97
+ // TODO: revisit permissions
98
+ internalVolumePath := cs .getInternalVolumePath (nfsVol )
99
+ if err = os .Mkdir (internalVolumePath , 0777 ); err != nil {
100
+ return nil , status .Errorf (codes .Internal , "failed to make subdirectory: %v" , err .Error ())
101
+ }
102
+ // Remove capacity setting when provisioner 1.4.0 is available with fix for
103
+ // https://github.com/kubernetes-csi/external-provisioner/pull/271
104
+ return & csi.CreateVolumeResponse {Volume : cs .nfsVolToCSI (nfsVol , req .GetCapacityRange ().GetRequiredBytes ())}, nil
17
105
}
18
106
19
107
func (cs * ControllerServer ) DeleteVolume (ctx context.Context , req * csi.DeleteVolumeRequest ) (* csi.DeleteVolumeResponse , error ) {
@@ -73,3 +161,177 @@ func (cs *ControllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnap
73
161
func (cs * ControllerServer ) ControllerExpandVolume (ctx context.Context , req * csi.ControllerExpandVolumeRequest ) (* csi.ControllerExpandVolumeResponse , error ) {
74
162
return nil , status .Error (codes .Unimplemented , "" )
75
163
}
164
+
165
+ func (cs * ControllerServer ) validateVolumeCapabilities (caps []* csi.VolumeCapability ) error {
166
+ if len (caps ) == 0 {
167
+ return fmt .Errorf ("volume capabilities must be provided" )
168
+ }
169
+
170
+ for _ , c := range caps {
171
+ if err := cs .validateVolumeCapability (c ); err != nil {
172
+ return err
173
+ }
174
+ }
175
+ return nil
176
+ }
177
+
178
+ func (cs * ControllerServer ) validateVolumeCapability (c * csi.VolumeCapability ) error {
179
+ if c == nil {
180
+ return fmt .Errorf ("volume capability must be provided" )
181
+ }
182
+
183
+ // Validate access mode
184
+ accessMode := c .GetAccessMode ()
185
+ if accessMode == nil {
186
+ return fmt .Errorf ("volume capability access mode not set" )
187
+ }
188
+ if ! cs .Driver .cap [accessMode .Mode ] {
189
+ return fmt .Errorf ("driver does not support access mode: %v" , accessMode .Mode .String ())
190
+ }
191
+
192
+ // Validate access type
193
+ accessType := c .GetAccessType ()
194
+ if accessType == nil {
195
+ return fmt .Errorf ("volume capability access type not set" )
196
+ }
197
+ mountType := c .GetMount ()
198
+ if mountType == nil {
199
+ return fmt .Errorf ("driver only supports mount access type volume capability" )
200
+ }
201
+
202
+ if mountType .FsType != "" {
203
+ return fmt .Errorf ("driver does not support fstype %v" , mountType .FsType )
204
+ }
205
+ // TODO: check if we want to whitelist/blacklist certain mount options
206
+ return nil
207
+ }
208
+
209
+ // Mount nfs server at base-dir
210
+ func (cs * ControllerServer ) internalMount (ctx context.Context , vol * nfsVolume ) error {
211
+ sharePath := filepath .Join ("/" + vol .baseDir )
212
+ targetPath := cs .getInternalMountPath (vol )
213
+ stdVolCap := csi.VolumeCapability {
214
+ AccessType : & csi.VolumeCapability_Mount {
215
+ Mount : & csi.VolumeCapability_MountVolume {},
216
+ },
217
+ }
218
+
219
+ glog .V (4 ).Infof ("internally mounting %v:%v at %v" , vol .server , sharePath , targetPath )
220
+ _ , err := cs .Driver .ns .NodePublishVolume (ctx , & csi.NodePublishVolumeRequest {
221
+ TargetPath : targetPath ,
222
+ VolumeContext : map [string ]string {
223
+ "server" : vol .server ,
224
+ "share" : sharePath ,
225
+ },
226
+ VolumeCapability : & stdVolCap ,
227
+ VolumeId : vol .id ,
228
+ })
229
+ return err
230
+ }
231
+
232
+ // Unmount nfs server at base-dir
233
+ func (cs * ControllerServer ) internalUnmount (ctx context.Context , vol * nfsVolume ) error {
234
+ targetPath := cs .getInternalMountPath (vol )
235
+
236
+ // Unmount nfs server at base-dir
237
+ glog .V (4 ).Infof ("internally unmounting %v" , targetPath )
238
+ _ , err := cs .Driver .ns .NodeUnpublishVolume (ctx , & csi.NodeUnpublishVolumeRequest {
239
+ TargetPath : cs .getInternalMountPath (vol ),
240
+ })
241
+ return err
242
+ }
243
+
244
+ // Convert VolumeCreate parameters to an nfsVolume
245
+ func (cs * ControllerServer ) newNFSVolume (name string , params map [string ]string ) (* nfsVolume , error ) {
246
+ var (
247
+ server string
248
+ baseDir string
249
+ )
250
+
251
+ // Validate parameters (case-insensitive).
252
+ // TODO do more strict validation.
253
+ for k , v := range params {
254
+ switch strings .ToLower (k ) {
255
+ case paramServer :
256
+ server = v
257
+ case paramBaseDir :
258
+ baseDir = v
259
+ default :
260
+ return nil , fmt .Errorf ("invalid parameter %q" , k )
261
+ }
262
+ }
263
+
264
+ // Validate required parameters
265
+ if server == "" {
266
+ return nil , fmt .Errorf ("%v is a required parameter" , paramServer )
267
+ }
268
+ if baseDir == "" {
269
+ return nil , fmt .Errorf ("%v is a required parameter" , paramBaseDir )
270
+ }
271
+
272
+ vol := & nfsVolume {
273
+ server : server ,
274
+ baseDir : baseDir ,
275
+ subDir : name ,
276
+ }
277
+ vol .id = cs .getVolumeIdFromNfsVol (vol )
278
+
279
+ return vol , nil
280
+ }
281
+
282
+ // Get working directory for CreateVolume and DeleteVolume
283
+ func (cs * ControllerServer ) getInternalMountPath (vol * nfsVolume ) string {
284
+ return filepath .Join (cs .workingMountDir , vol .subDir )
285
+ }
286
+
287
+ // Get internal path where the volume is created
288
+ // The reason why the internal path is "workingDir/subDir/subDir" is because:
289
+ // * the semantic is actually "workingDir/volId/subDir" and volId == subDir.
290
+ // * we need a mount directory per volId because you can have multiple
291
+ // CreateVolume calls in parallel and they may use the same underlying share.
292
+ // Instead of refcounting how many CreateVolume calls are using the same
293
+ // share, it's simpler to just do a mount per request.
294
+ func (cs * ControllerServer ) getInternalVolumePath (vol * nfsVolume ) string {
295
+ return filepath .Join (cs .getInternalMountPath (vol ), vol .subDir )
296
+ }
297
+
298
+ // Get user-visible share path for the volume
299
+ func (cs * ControllerServer ) getVolumeSharePath (vol * nfsVolume ) string {
300
+ return filepath .Join ("/" , vol .baseDir , vol .subDir )
301
+ }
302
+
303
+ // Convert into nfsVolume into a csi.Volume
304
+ func (cs * ControllerServer ) nfsVolToCSI (vol * nfsVolume , reqBytes int64 ) * csi.Volume {
305
+ return & csi.Volume {
306
+ CapacityBytes : reqBytes ,
307
+ VolumeId : vol .id ,
308
+ VolumeContext : map [string ]string {
309
+ attrServer : vol .server ,
310
+ attrShare : cs .getVolumeSharePath (vol ),
311
+ },
312
+ }
313
+ }
314
+
315
+ // Given a CSI volume id, return a nfsVolume
316
+ func (cs * ControllerServer ) getNfsVolFromId (id string ) (* nfsVolume , error ) {
317
+ tokens := strings .Split (id , "/" )
318
+ if len (tokens ) != totalIDElements {
319
+ return nil , fmt .Errorf ("volume id %q unexpected format: got %v tokens" , id , len (tokens ))
320
+ }
321
+
322
+ return & nfsVolume {
323
+ id : id ,
324
+ server : tokens [idServer ],
325
+ baseDir : tokens [idBaseDir ],
326
+ subDir : tokens [idSubDir ],
327
+ }, nil
328
+ }
329
+
330
+ // Given a nfsVolume, return a CSI volume id
331
+ func (cs * ControllerServer ) getVolumeIdFromNfsVol (vol * nfsVolume ) string {
332
+ idElements := make ([]string , totalIDElements )
333
+ idElements [idServer ] = vol .server
334
+ idElements [idBaseDir ] = vol .baseDir
335
+ idElements [idSubDir ] = vol .subDir
336
+ return strings .Join (idElements , "/" )
337
+ }
0 commit comments