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,95 @@ 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
+ glog .V (4 ).Infof ("CreateVolume called with request %+v" , req )
71
+
72
+ // Validate arguments
73
+ name := req .GetName ()
74
+ if len (name ) == 0 {
75
+ return nil , status .Error (codes .InvalidArgument , "CreateVolume name must be provided" )
76
+ }
77
+
78
+ if err := cs .validateVolumeCapabilities (req .GetVolumeCapabilities ()); err != nil {
79
+ return nil , status .Error (codes .InvalidArgument , err .Error ())
80
+ }
81
+
82
+ nfsVol , err := cs .newNFSVolume (name , req .GetParameters ())
83
+ if err != nil {
84
+ return nil , status .Error (codes .InvalidArgument , err .Error ())
85
+ }
86
+
87
+ // Mount nfs base share so we can create a subdirectory
88
+ if err = cs .internalMount (ctx , nfsVol ); err != nil {
89
+ return nil , status .Errorf (codes .Internal , "failed to mount nfs server: %v" , err .Error ())
90
+ }
91
+ defer func () {
92
+ if err = cs .internalUnmount (ctx , nfsVol ); err != nil {
93
+ glog .Warningf ("failed to unmount nfs server: %v" , err .Error ())
94
+ }
95
+ }()
96
+
97
+ // Create subdirectory under base-dir
98
+ // TODO: revisit permissions
99
+ internalVolumePath := cs .getInternalVolumePath (nfsVol )
100
+ glog .V (4 ).Infof ("Creating subdirectory at %v" , internalVolumePath )
101
+ if err = os .Mkdir (internalVolumePath , 0755 ); err != nil {
102
+ return nil , status .Errorf (codes .Internal , "failed to make subdirectory: %v" , err .Error ())
103
+ }
104
+ // Remove capacity setting when provisioner 1.4.0 is available with fix for
105
+ // https://github.com/kubernetes-csi/external-provisioner/pull/271
106
+ return & csi.CreateVolumeResponse {Volume : cs .nfsVolToCSI (nfsVol , req .GetCapacityRange ().GetRequiredBytes ())}, nil
17
107
}
18
108
19
109
func (cs * ControllerServer ) DeleteVolume (ctx context.Context , req * csi.DeleteVolumeRequest ) (* csi.DeleteVolumeResponse , error ) {
@@ -73,3 +163,177 @@ func (cs *ControllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnap
73
163
func (cs * ControllerServer ) ControllerExpandVolume (ctx context.Context , req * csi.ControllerExpandVolumeRequest ) (* csi.ControllerExpandVolumeResponse , error ) {
74
164
return nil , status .Error (codes .Unimplemented , "" )
75
165
}
166
+
167
+ func (cs * ControllerServer ) validateVolumeCapabilities (caps []* csi.VolumeCapability ) error {
168
+ if len (caps ) == 0 {
169
+ return fmt .Errorf ("volume capabilities must be provided" )
170
+ }
171
+
172
+ for _ , c := range caps {
173
+ if err := cs .validateVolumeCapability (c ); err != nil {
174
+ return err
175
+ }
176
+ }
177
+ return nil
178
+ }
179
+
180
+ func (cs * ControllerServer ) validateVolumeCapability (c * csi.VolumeCapability ) error {
181
+ if c == nil {
182
+ return fmt .Errorf ("volume capability must be provided" )
183
+ }
184
+
185
+ // Validate access mode
186
+ accessMode := c .GetAccessMode ()
187
+ if accessMode == nil {
188
+ return fmt .Errorf ("volume capability access mode not set" )
189
+ }
190
+ if ! cs .Driver .cap [accessMode .Mode ] {
191
+ return fmt .Errorf ("driver does not support access mode: %v" , accessMode .Mode .String ())
192
+ }
193
+
194
+ // Validate access type
195
+ accessType := c .GetAccessType ()
196
+ if accessType == nil {
197
+ return fmt .Errorf ("volume capability access type not set" )
198
+ }
199
+ mountType := c .GetMount ()
200
+ if mountType == nil {
201
+ return fmt .Errorf ("driver only supports mount access type volume capability" )
202
+ }
203
+
204
+ if mountType .FsType != "" {
205
+ return fmt .Errorf ("driver does not support fstype %v" , mountType .FsType )
206
+ }
207
+ // TODO: check if we want to whitelist/blacklist certain mount options
208
+ return nil
209
+ }
210
+
211
+ // Mount nfs server at base-dir
212
+ func (cs * ControllerServer ) internalMount (ctx context.Context , vol * nfsVolume ) error {
213
+ sharePath := filepath .Join ("/" + vol .baseDir )
214
+ targetPath := cs .getInternalMountPath (vol )
215
+ stdVolCap := csi.VolumeCapability {
216
+ AccessType : & csi.VolumeCapability_Mount {
217
+ Mount : & csi.VolumeCapability_MountVolume {},
218
+ },
219
+ }
220
+
221
+ glog .V (4 ).Infof ("internally mounting %v:%v at %v" , vol .server , sharePath , targetPath )
222
+ _ , err := cs .Driver .ns .NodePublishVolume (ctx , & csi.NodePublishVolumeRequest {
223
+ TargetPath : targetPath ,
224
+ VolumeContext : map [string ]string {
225
+ "server" : vol .server ,
226
+ "share" : sharePath ,
227
+ },
228
+ VolumeCapability : & stdVolCap ,
229
+ VolumeId : vol .id ,
230
+ })
231
+ return err
232
+ }
233
+
234
+ // Unmount nfs server at base-dir
235
+ func (cs * ControllerServer ) internalUnmount (ctx context.Context , vol * nfsVolume ) error {
236
+ targetPath := cs .getInternalMountPath (vol )
237
+
238
+ // Unmount nfs server at base-dir
239
+ glog .V (4 ).Infof ("internally unmounting %v" , targetPath )
240
+ _ , err := cs .Driver .ns .NodeUnpublishVolume (ctx , & csi.NodeUnpublishVolumeRequest {
241
+ TargetPath : cs .getInternalMountPath (vol ),
242
+ })
243
+ return err
244
+ }
245
+
246
+ // Convert VolumeCreate parameters to an nfsVolume
247
+ func (cs * ControllerServer ) newNFSVolume (name string , params map [string ]string ) (* nfsVolume , error ) {
248
+ var (
249
+ server string
250
+ baseDir string
251
+ )
252
+
253
+ // Validate parameters (case-insensitive).
254
+ // TODO do more strict validation.
255
+ for k , v := range params {
256
+ switch strings .ToLower (k ) {
257
+ case paramServer :
258
+ server = v
259
+ case paramBaseDir :
260
+ baseDir = v
261
+ default :
262
+ return nil , fmt .Errorf ("invalid parameter %q" , k )
263
+ }
264
+ }
265
+
266
+ // Validate required parameters
267
+ if server == "" {
268
+ return nil , fmt .Errorf ("%v is a required parameter" , paramServer )
269
+ }
270
+ if baseDir == "" {
271
+ return nil , fmt .Errorf ("%v is a required parameter" , paramBaseDir )
272
+ }
273
+
274
+ vol := & nfsVolume {
275
+ server : server ,
276
+ baseDir : baseDir ,
277
+ subDir : name ,
278
+ }
279
+ vol .id = cs .getVolumeIdFromNfsVol (vol )
280
+
281
+ return vol , nil
282
+ }
283
+
284
+ // Get working directory for CreateVolume and DeleteVolume
285
+ func (cs * ControllerServer ) getInternalMountPath (vol * nfsVolume ) string {
286
+ return filepath .Join (cs .workingMountDir , vol .subDir )
287
+ }
288
+
289
+ // Get internal path where the volume is created
290
+ // The reason why the internal path is "workingDir/subDir/subDir" is because:
291
+ // * the semantic is actually "workingDir/volId/subDir" and volId == subDir.
292
+ // * we need a mount directory per volId because you can have multiple
293
+ // CreateVolume calls in parallel and they may use the same underlying share.
294
+ // Instead of refcounting how many CreateVolume calls are using the same
295
+ // share, it's simpler to just do a mount per request.
296
+ func (cs * ControllerServer ) getInternalVolumePath (vol * nfsVolume ) string {
297
+ return filepath .Join (cs .getInternalMountPath (vol ), vol .subDir )
298
+ }
299
+
300
+ // Get user-visible share path for the volume
301
+ func (cs * ControllerServer ) getVolumeSharePath (vol * nfsVolume ) string {
302
+ return filepath .Join ("/" , vol .baseDir , vol .subDir )
303
+ }
304
+
305
+ // Convert into nfsVolume into a csi.Volume
306
+ func (cs * ControllerServer ) nfsVolToCSI (vol * nfsVolume , reqBytes int64 ) * csi.Volume {
307
+ return & csi.Volume {
308
+ CapacityBytes : reqBytes ,
309
+ VolumeId : vol .id ,
310
+ VolumeContext : map [string ]string {
311
+ attrServer : vol .server ,
312
+ attrShare : cs .getVolumeSharePath (vol ),
313
+ },
314
+ }
315
+ }
316
+
317
+ // Given a CSI volume id, return a nfsVolume
318
+ func (cs * ControllerServer ) getNfsVolFromId (id string ) (* nfsVolume , error ) {
319
+ tokens := strings .Split (id , "/" )
320
+ if len (tokens ) != totalIDElements {
321
+ return nil , fmt .Errorf ("volume id %q unexpected format: got %v tokens" , id , len (tokens ))
322
+ }
323
+
324
+ return & nfsVolume {
325
+ id : id ,
326
+ server : tokens [idServer ],
327
+ baseDir : tokens [idBaseDir ],
328
+ subDir : tokens [idSubDir ],
329
+ }, nil
330
+ }
331
+
332
+ // Given a nfsVolume, return a CSI volume id
333
+ func (cs * ControllerServer ) getVolumeIdFromNfsVol (vol * nfsVolume ) string {
334
+ idElements := make ([]string , totalIDElements )
335
+ idElements [idServer ] = vol .server
336
+ idElements [idBaseDir ] = vol .baseDir
337
+ idElements [idSubDir ] = vol .subDir
338
+ return strings .Join (idElements , "/" )
339
+ }
0 commit comments