Merge pull request #432 from woehrl01/ondelete_subdir

feat: provide option to retain subdir on delete
This commit is contained in:
Kubernetes Prow Robot 2023-04-03 00:45:51 -07:00 committed by GitHub
commit fafa015708
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 294 additions and 101 deletions

View File

@ -66,6 +66,7 @@ The following table lists the configurable parameters of the latest NFS CSI Driv
| `controller.runOnMaster` | run controller on master node(deprecated on k8s 1.25+) |`false` | | `controller.runOnMaster` | run controller on master node(deprecated on k8s 1.25+) |`false` |
| `controller.runOnControlPlane` | run controller on control plane node |`false` | | `controller.runOnControlPlane` | run controller on control plane node |`false` |
| `controller.dnsPolicy` | dnsPolicy of controller driver, available values: `Default`, `ClusterFirstWithHostNet`, `ClusterFirst` | `Default` | | `controller.dnsPolicy` | dnsPolicy of controller driver, available values: `Default`, `ClusterFirstWithHostNet`, `ClusterFirst` | `Default` |
| `controller.defaultOnDeletePolicy` | default policy for deleting subdirectory when deleting a volume, available values: `delete`, `retain` | `delete` |
| `controller.logLevel` | controller driver log level |`5` | | `controller.logLevel` | controller driver log level |`5` |
| `controller.workingMountDir` | working directory for provisioner to mount nfs shares temporarily | `/tmp` | | `controller.workingMountDir` | working directory for provisioner to mount nfs shares temporarily | `/tmp` |
| `controller.affinity` | controller pod affinity | `{}` | | `controller.affinity` | controller pod affinity | `{}` |

View File

@ -93,6 +93,7 @@ spec:
- "--drivername={{ .Values.driver.name }}" - "--drivername={{ .Values.driver.name }}"
- "--mount-permissions={{ .Values.driver.mountPermissions }}" - "--mount-permissions={{ .Values.driver.mountPermissions }}"
- "--working-mount-dir={{ .Values.controller.workingMountDir }}" - "--working-mount-dir={{ .Values.controller.workingMountDir }}"
- "--default-ondelete-policy={{ .Values.controller.defaultOnDeletePolicy }}"
env: env:
- name: NODE_ID - name: NODE_ID
valueFrom: valueFrom:

View File

@ -44,8 +44,9 @@ controller:
livenessProbe: livenessProbe:
healthPort: 29652 healthPort: 29652
logLevel: 5 logLevel: 5
workingMountDir: "/tmp" workingMountDir: /tmp
dnsPolicy: Default # available values: Default, ClusterFirstWithHostNet, ClusterFirst dnsPolicy: Default # available values: Default, ClusterFirstWithHostNet, ClusterFirst
defaultOnDeletePolicy: delete # available values: delete, retain
affinity: {} affinity: {}
nodeSelector: {} nodeSelector: {}
tolerations: tolerations:

View File

@ -26,11 +26,12 @@ import (
) )
var ( var (
endpoint = flag.String("endpoint", "unix://tmp/csi.sock", "CSI endpoint") endpoint = flag.String("endpoint", "unix://tmp/csi.sock", "CSI endpoint")
nodeID = flag.String("nodeid", "", "node id") nodeID = flag.String("nodeid", "", "node id")
mountPermissions = flag.Uint64("mount-permissions", 0, "mounted folder permissions") mountPermissions = flag.Uint64("mount-permissions", 0, "mounted folder permissions")
driverName = flag.String("drivername", nfs.DefaultDriverName, "name of the driver") driverName = flag.String("drivername", nfs.DefaultDriverName, "name of the driver")
workingMountDir = flag.String("working-mount-dir", "/tmp", "working directory for provisioner to mount nfs shares temporarily") workingMountDir = flag.String("working-mount-dir", "/tmp", "working directory for provisioner to mount nfs shares temporarily")
defaultOnDeletePolicy = flag.String("default-ondelete-policy", "", "default policy for deleting subdirectory when deleting a volume")
) )
func init() { func init() {
@ -50,11 +51,12 @@ func main() {
func handle() { func handle() {
driverOptions := nfs.DriverOptions{ driverOptions := nfs.DriverOptions{
NodeID: *nodeID, NodeID: *nodeID,
DriverName: *driverName, DriverName: *driverName,
Endpoint: *endpoint, Endpoint: *endpoint,
MountPermissions: *mountPermissions, MountPermissions: *mountPermissions,
WorkingMountDir: *workingMountDir, WorkingMountDir: *workingMountDir,
DefaultOnDeletePolicy: *defaultOnDeletePolicy,
} }
d := nfs.NewDriver(&driverOptions) d := nfs.NewDriver(&driverOptions)
d.Run(false) d.Run(false)

View File

@ -10,6 +10,7 @@ server | NFS Server address | domain name `nfs-server.default.svc.cluster.local`
share | NFS share path | `/` | Yes | share | NFS share path | `/` | Yes |
subDir | sub directory under nfs share | | No | if sub directory does not exist, this driver would create a new one subDir | sub directory under nfs share | | No | if sub directory does not exist, this driver would create a new one
mountPermissions | mounted folder permissions. The default is `0`, if set as non-zero, driver will perform `chmod` after mount | | No | mountPermissions | mounted folder permissions. The default is `0`, if set as non-zero, driver will perform `chmod` after mount | | No |
onDelete | when volume is deleted, keep the directory if it's `retain` | `delete`(default), `retain` | No | `delete`
- VolumeID(`volumeHandle`) is the identifier of the volume handled by the driver, format of VolumeID: - VolumeID(`volumeHandle`) is the identifier of the volume handled by the driver, format of VolumeID:
``` ```

View File

@ -55,6 +55,8 @@ type nfsVolume struct {
size int64 size int64
// pv name when subDir is not empty // pv name when subDir is not empty
uuid string uuid string
// on delete action
onDelete string
} }
// Ordering of elements in the CSI volume id. // Ordering of elements in the CSI volume id.
@ -68,11 +70,10 @@ const (
idBaseDir idBaseDir
idSubDir idSubDir
idUUID idUUID
idOnDelete
totalIDElements // Always last totalIDElements // Always last
) )
const separator = "#"
// CreateVolume create a volume // CreateVolume create a volume
func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
name := req.GetName() name := req.GetName()
@ -96,6 +97,7 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
case paramServer: case paramServer:
case paramShare: case paramShare:
case paramSubDir: case paramSubDir:
case paramOnDelete:
case pvcNamespaceKey: case pvcNamespaceKey:
case pvcNameKey: case pvcNameKey:
case pvNameKey: case pvNameKey:
@ -112,7 +114,7 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
} }
} }
nfsVol, err := newNFSVolume(name, reqCapacity, parameters) nfsVol, err := newNFSVolume(name, reqCapacity, parameters, cs.Driver.defaultOnDeletePolicy)
if err != nil { if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error()) return nil, status.Error(codes.InvalidArgument, err.Error())
} }
@ -187,22 +189,30 @@ func (cs *ControllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol
} }
} }
// mount nfs base share so we can delete the subdirectory if nfsVol.onDelete == "" {
if err = cs.internalMount(ctx, nfsVol, nil, volCap); err != nil { nfsVol.onDelete = cs.Driver.defaultOnDeletePolicy
return nil, status.Errorf(codes.Internal, "failed to mount nfs server: %v", err.Error())
} }
defer func() {
if err = cs.internalUnmount(ctx, nfsVol); err != nil { if !strings.EqualFold(nfsVol.onDelete, retain) {
klog.Warningf("failed to unmount nfs server: %v", err.Error()) // mount nfs base share so we can delete the subdirectory
if err = cs.internalMount(ctx, nfsVol, nil, volCap); err != nil {
return nil, status.Errorf(codes.Internal, "failed to mount nfs server: %v", err.Error())
} }
}() defer func() {
if err = cs.internalUnmount(ctx, nfsVol); err != nil {
klog.Warningf("failed to unmount nfs server: %v", err.Error())
}
}()
// delete subdirectory under base-dir // delete subdirectory under base-dir
internalVolumePath := getInternalVolumePath(cs.Driver.workingMountDir, nfsVol) internalVolumePath := getInternalVolumePath(cs.Driver.workingMountDir, nfsVol)
klog.V(2).Infof("Removing subdirectory at %v", internalVolumePath) klog.V(2).Infof("Removing subdirectory at %v", internalVolumePath)
if err = os.RemoveAll(internalVolumePath); err != nil { if err = os.RemoveAll(internalVolumePath); err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete subdirectory: %v", err.Error()) return nil, status.Errorf(codes.Internal, "failed to delete subdirectory: %v", err.Error())
}
} else {
klog.V(2).Infof("DeleteVolume: volume(%s) is set to retain, not deleting subdirectory", volumeID)
} }
return &csi.DeleteVolumeResponse{}, nil return &csi.DeleteVolumeResponse{}, nil
@ -368,8 +378,8 @@ func (cs *ControllerServer) copyVolume(ctx context.Context, req *csi.CreateVolum
} }
// newNFSVolume Convert VolumeCreate parameters to an nfsVolume // newNFSVolume Convert VolumeCreate parameters to an nfsVolume
func newNFSVolume(name string, size int64, params map[string]string) (*nfsVolume, error) { func newNFSVolume(name string, size int64, params map[string]string, defaultOnDeletePolicy string) (*nfsVolume, error) {
var server, baseDir, subDir string var server, baseDir, subDir, onDelete string
subDirReplaceMap := map[string]string{} subDirReplaceMap := map[string]string{}
// validate parameters (case-insensitive) // validate parameters (case-insensitive)
@ -381,6 +391,8 @@ func newNFSVolume(name string, size int64, params map[string]string) (*nfsVolume
baseDir = v baseDir = v
case paramSubDir: case paramSubDir:
subDir = v subDir = v
case paramOnDelete:
onDelete = v
case pvcNamespaceKey: case pvcNamespaceKey:
subDirReplaceMap[pvcNamespaceMetadata] = v subDirReplaceMap[pvcNamespaceMetadata] = v
case pvcNameKey: case pvcNameKey:
@ -408,6 +420,16 @@ func newNFSVolume(name string, size int64, params map[string]string) (*nfsVolume
// make volume id unique if subDir is provided // make volume id unique if subDir is provided
vol.uuid = name vol.uuid = name
} }
if err := validateOnDeleteValue(onDelete); err != nil {
return nil, err
}
vol.onDelete = defaultOnDeletePolicy
if onDelete != "" {
vol.onDelete = onDelete
}
vol.id = getVolumeIDFromNfsVol(vol) vol.id = getVolumeIDFromNfsVol(vol)
return vol, nil return vol, nil
} }
@ -442,6 +464,9 @@ func getVolumeIDFromNfsVol(vol *nfsVolume) string {
idElements[idBaseDir] = strings.Trim(vol.baseDir, "/") idElements[idBaseDir] = strings.Trim(vol.baseDir, "/")
idElements[idSubDir] = strings.Trim(vol.subDir, "/") idElements[idSubDir] = strings.Trim(vol.subDir, "/")
idElements[idUUID] = vol.uuid idElements[idUUID] = vol.uuid
if strings.EqualFold(vol.onDelete, retain) {
idElements[idOnDelete] = vol.onDelete
}
return strings.Join(idElements, separator) return strings.Join(idElements, separator)
} }
@ -450,10 +475,10 @@ func getVolumeIDFromNfsVol(vol *nfsVolume) string {
// //
// new volumeID: // new volumeID:
// nfs-server.default.svc.cluster.local#share#pvc-4bcbf944-b6f7-4bd0-b50f-3c3dd00efc64 // nfs-server.default.svc.cluster.local#share#pvc-4bcbf944-b6f7-4bd0-b50f-3c3dd00efc64
// nfs-server.default.svc.cluster.local#share#subdir#pvc-4bcbf944-b6f7-4bd0-b50f-3c3dd00efc64 // nfs-server.default.svc.cluster.local#share#subdir#pvc-4bcbf944-b6f7-4bd0-b50f-3c3dd00efc64#retain
// old volumeID: nfs-server.default.svc.cluster.local/share/pvc-4bcbf944-b6f7-4bd0-b50f-3c3dd00efc64 // old volumeID: nfs-server.default.svc.cluster.local/share/pvc-4bcbf944-b6f7-4bd0-b50f-3c3dd00efc64
func getNfsVolFromID(id string) (*nfsVolume, error) { func getNfsVolFromID(id string) (*nfsVolume, error) {
var server, baseDir, subDir, uuid string var server, baseDir, subDir, uuid, onDelete string
segments := strings.Split(id, separator) segments := strings.Split(id, separator)
if len(segments) < 3 { if len(segments) < 3 {
klog.V(2).Infof("could not split %s into server, baseDir and subDir with separator(%s)", id, separator) klog.V(2).Infof("could not split %s into server, baseDir and subDir with separator(%s)", id, separator)
@ -473,14 +498,18 @@ func getNfsVolFromID(id string) (*nfsVolume, error) {
if len(segments) >= 4 { if len(segments) >= 4 {
uuid = segments[3] uuid = segments[3]
} }
if len(segments) >= 5 {
onDelete = segments[4]
}
} }
return &nfsVolume{ return &nfsVolume{
id: id, id: id,
server: server, server: server,
baseDir: baseDir, baseDir: baseDir,
subDir: subDir, subDir: subDir,
uuid: uuid, uuid: uuid,
onDelete: onDelete,
}, nil }, nil
} }

View File

@ -35,15 +35,18 @@ import (
) )
const ( const (
testServer = "test-server" testServer = "test-server"
testBaseDir = "test-base-dir" testBaseDir = "test-base-dir"
testBaseDirNested = "test/base/dir" testBaseDirNested = "test/base/dir"
testCSIVolume = "volume-name" testCSIVolume = "volume-name"
testVolumeID = "test-server/test-base-dir/volume-name" testVolumeID = "test-server/test-base-dir/volume-name"
newTestVolumeID = "test-server#test-base-dir#volume-name#" newTestVolumeID = "test-server#test-base-dir#volume-name##"
testVolumeIDNested = "test-server/test/base/dir/volume-name" newTestVolumeWithVolumeID = "test-server#test-base-dir#volume-name#volume-name#"
newTestVolumeIDNested = "test-server#test/base/dir#volume-name#" testVolumeIDNested = "test-server/test/base/dir/volume-name"
newTestVolumeIDUUID = "test-server#test-base-dir#volume-name#uuid" newTestVolumeIDNested = "test-server#test/base/dir#volume-name#"
newTestVolumeIDUUID = "test-server#test-base-dir#volume-name#uuid"
newTestVolumeOnDeleteRetain = "test-server#test-base-dir#volume-name#uuid#retain"
newTestVolumeOnDeleteDelete = "test-server#test-base-dir#volume-name#uuid#delete"
) )
func initTestController(t *testing.T) *ControllerServer { func initTestController(t *testing.T) *ControllerServer {
@ -136,7 +139,7 @@ func TestCreateVolume(t *testing.T) {
}, },
resp: &csi.CreateVolumeResponse{ resp: &csi.CreateVolumeResponse{
Volume: &csi.Volume{ Volume: &csi.Volume{
VolumeId: newTestVolumeID + testCSIVolume, VolumeId: newTestVolumeWithVolumeID,
VolumeContext: map[string]string{ VolumeContext: map[string]string{
paramServer: testServer, paramServer: testServer,
paramShare: testBaseDir, paramShare: testBaseDir,
@ -242,32 +245,44 @@ func TestCreateVolume(t *testing.T) {
func TestDeleteVolume(t *testing.T) { func TestDeleteVolume(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string
testOnWindows bool testOnWindows bool
req *csi.DeleteVolumeRequest req *csi.DeleteVolumeRequest
resp *csi.DeleteVolumeResponse resp *csi.DeleteVolumeResponse
expectedErr error expectedDeleteSubDir bool
expectedErr error
}{ }{
{ {
desc: "Volume ID missing", desc: "Volume ID missing",
testOnWindows: true, testOnWindows: true,
req: &csi.DeleteVolumeRequest{}, req: &csi.DeleteVolumeRequest{},
resp: nil, resp: nil,
expectedErr: status.Error(codes.InvalidArgument, "Volume ID missing in request"), expectedErr: status.Error(codes.InvalidArgument, "Volume ID missing in request"),
expectedDeleteSubDir: false,
}, },
{ {
desc: "Valid request", desc: "Valid request",
testOnWindows: false, testOnWindows: false,
req: &csi.DeleteVolumeRequest{VolumeId: testVolumeID}, req: &csi.DeleteVolumeRequest{VolumeId: testVolumeID},
resp: &csi.DeleteVolumeResponse{}, resp: &csi.DeleteVolumeResponse{},
expectedErr: nil, expectedErr: nil,
expectedDeleteSubDir: true,
}, },
{ {
desc: "Valid request with newTestVolumeID", desc: "Valid request with newTestVolumeID",
testOnWindows: true, testOnWindows: true,
req: &csi.DeleteVolumeRequest{VolumeId: newTestVolumeID}, req: &csi.DeleteVolumeRequest{VolumeId: newTestVolumeID},
resp: &csi.DeleteVolumeResponse{}, resp: &csi.DeleteVolumeResponse{},
expectedErr: nil, expectedErr: nil,
expectedDeleteSubDir: true,
},
{
desc: "Valid request with onDelete:retain",
testOnWindows: true,
req: &csi.DeleteVolumeRequest{VolumeId: newTestVolumeOnDeleteRetain},
resp: &csi.DeleteVolumeResponse{},
expectedErr: nil,
expectedDeleteSubDir: false,
}, },
} }
@ -292,8 +307,13 @@ func TestDeleteVolume(t *testing.T) {
if !reflect.DeepEqual(resp, test.resp) { if !reflect.DeepEqual(resp, test.resp) {
t.Errorf("test %q failed: got resp %+v, expected %+v", test.desc, resp, test.resp) t.Errorf("test %q failed: got resp %+v, expected %+v", test.desc, resp, test.resp)
} }
if _, err := os.Stat(filepath.Join(cs.Driver.workingMountDir, testCSIVolume, testCSIVolume)); test.expectedErr == nil && !os.IsNotExist(err) {
t.Errorf("test %q failed: expected volume subdirectory deleted, it still exists", test.desc) if _, err := os.Stat(filepath.Join(cs.Driver.workingMountDir, testCSIVolume, testCSIVolume)); test.expectedErr == nil {
if !os.IsNotExist(err) && test.expectedDeleteSubDir {
t.Errorf("test %q failed: expected volume subdirectory deleted, it still exists", test.desc)
} else if os.IsNotExist(err) && !test.expectedDeleteSubDir {
t.Errorf("test %q failed: expected volume subdirectory not deleted, it was deleted", test.desc)
}
} }
}) })
} }
@ -395,10 +415,11 @@ func TestNfsVolFromId(t *testing.T) {
name: "valid request single baseDir with newTestVolumeID", name: "valid request single baseDir with newTestVolumeID",
volumeID: newTestVolumeID, volumeID: newTestVolumeID,
resp: &nfsVolume{ resp: &nfsVolume{
id: newTestVolumeID, id: newTestVolumeID,
server: testServer, server: testServer,
baseDir: testBaseDir, baseDir: testBaseDir,
subDir: testCSIVolume, subDir: testCSIVolume,
onDelete: "",
}, },
expectErr: false, expectErr: false,
}, },
@ -436,6 +457,32 @@ func TestNfsVolFromId(t *testing.T) {
}, },
expectErr: false, expectErr: false,
}, },
{
name: "valid request nested ondelete retain",
volumeID: newTestVolumeOnDeleteRetain,
resp: &nfsVolume{
id: newTestVolumeOnDeleteRetain,
server: testServer,
baseDir: testBaseDir,
subDir: testCSIVolume,
uuid: "uuid",
onDelete: "retain",
},
expectErr: false,
},
{
name: "valid request nested ondelete delete",
volumeID: newTestVolumeOnDeleteDelete,
resp: &nfsVolume{
id: newTestVolumeOnDeleteDelete,
server: testServer,
baseDir: testBaseDir,
subDir: testCSIVolume,
uuid: "uuid",
onDelete: "delete",
},
expectErr: false,
},
} }
for _, test := range cases { for _, test := range cases {
@ -556,12 +603,13 @@ func TestNewNFSVolume(t *testing.T) {
paramSubDir: "subdir", paramSubDir: "subdir",
}, },
expectVol: &nfsVolume{ expectVol: &nfsVolume{
id: "nfs-server.default.svc.cluster.local#share#subdir#pv-name", id: "nfs-server.default.svc.cluster.local#share#subdir#pv-name#",
server: "//nfs-server.default.svc.cluster.local", server: "//nfs-server.default.svc.cluster.local",
baseDir: "share", baseDir: "share",
subDir: "subdir", subDir: "subdir",
size: 100, size: 100,
uuid: "pv-name", uuid: "pv-name",
onDelete: "delete",
}, },
}, },
{ {
@ -577,12 +625,13 @@ func TestNewNFSVolume(t *testing.T) {
pvNameKey: "pvname", pvNameKey: "pvname",
}, },
expectVol: &nfsVolume{ expectVol: &nfsVolume{
id: "nfs-server.default.svc.cluster.local#share#subdir-pvcname-pvcnamespace-pvname#pv-name", id: "nfs-server.default.svc.cluster.local#share#subdir-pvcname-pvcnamespace-pvname#pv-name#",
server: "//nfs-server.default.svc.cluster.local", server: "//nfs-server.default.svc.cluster.local",
baseDir: "share", baseDir: "share",
subDir: "subdir-pvcname-pvcnamespace-pvname", subDir: "subdir-pvcname-pvcnamespace-pvname",
size: 100, size: 100,
uuid: "pv-name", uuid: "pv-name",
onDelete: "delete",
}, },
}, },
{ {
@ -594,12 +643,13 @@ func TestNewNFSVolume(t *testing.T) {
paramShare: "share", paramShare: "share",
}, },
expectVol: &nfsVolume{ expectVol: &nfsVolume{
id: "nfs-server.default.svc.cluster.local#share#pv-name#", id: "nfs-server.default.svc.cluster.local#share#pv-name##",
server: "//nfs-server.default.svc.cluster.local", server: "//nfs-server.default.svc.cluster.local",
baseDir: "share", baseDir: "share",
subDir: "pv-name", subDir: "pv-name",
size: 200, size: 200,
uuid: "", uuid: "",
onDelete: "delete",
}, },
}, },
{ {
@ -608,10 +658,20 @@ func TestNewNFSVolume(t *testing.T) {
expectVol: nil, expectVol: nil,
expectErr: fmt.Errorf("%s is a required parameter", paramServer), expectErr: fmt.Errorf("%s is a required parameter", paramServer),
}, },
{
desc: "invalid onDelete value",
params: map[string]string{
paramServer: "//nfs-server.default.svc.cluster.local",
paramShare: "share",
paramOnDelete: "invalid",
},
expectVol: nil,
expectErr: fmt.Errorf("invalid value %s for OnDelete, supported values are %v", "invalid", supportedOnDeleteValues),
},
} }
for _, test := range cases { for _, test := range cases {
vol, err := newNFSVolume(test.name, test.size, test.params) vol, err := newNFSVolume(test.name, test.size, test.params, "delete")
if !reflect.DeepEqual(err, test.expectErr) { if !reflect.DeepEqual(err, test.expectErr) {
t.Errorf("[test: %s] Unexpected error: %v, expected error: %v", test.desc, err, test.expectErr) t.Errorf("[test: %s] Unexpected error: %v, expected error: %v", test.desc, err, test.expectErr)
} }

View File

@ -27,20 +27,22 @@ import (
// DriverOptions defines driver parameters specified in driver deployment // DriverOptions defines driver parameters specified in driver deployment
type DriverOptions struct { type DriverOptions struct {
NodeID string NodeID string
DriverName string DriverName string
Endpoint string Endpoint string
MountPermissions uint64 MountPermissions uint64
WorkingMountDir string WorkingMountDir string
DefaultOnDeletePolicy string
} }
type Driver struct { type Driver struct {
name string name string
nodeID string nodeID string
version string version string
endpoint string endpoint string
mountPermissions uint64 mountPermissions uint64
workingMountDir string workingMountDir string
defaultOnDeletePolicy string
//ids *identityServer //ids *identityServer
ns *NodeServer ns *NodeServer
@ -59,6 +61,7 @@ const (
// "base" instead of "/base" // "base" instead of "/base"
paramShare = "share" paramShare = "share"
paramSubDir = "subdir" paramSubDir = "subdir"
paramOnDelete = "ondelete"
mountOptionsField = "mountoptions" mountOptionsField = "mountoptions"
mountPermissionsField = "mountpermissions" mountPermissionsField = "mountpermissions"
pvcNameKey = "csi.storage.k8s.io/pvc/name" pvcNameKey = "csi.storage.k8s.io/pvc/name"

View File

@ -32,6 +32,24 @@ import (
netutil "k8s.io/utils/net" netutil "k8s.io/utils/net"
) )
const (
separator = "#"
delete = "delete"
retain = "retain"
)
var supportedOnDeleteValues = []string{"", delete, retain}
func validateOnDeleteValue(onDelete string) error {
for _, v := range supportedOnDeleteValues {
if strings.EqualFold(v, onDelete) {
return nil
}
}
return fmt.Errorf("invalid value %s for OnDelete, supported values are %v", onDelete, supportedOnDeleteValues)
}
func NewDefaultIdentityServer(d *Driver) *IdentityServer { func NewDefaultIdentityServer(d *Driver) *IdentityServer {
return &IdentityServer{ return &IdentityServer{
Driver: d, Driver: d,

View File

@ -301,3 +301,49 @@ func TestSetKeyValueInMap(t *testing.T) {
} }
} }
} }
func TestValidateOnDeleteValue(t *testing.T) {
tests := []struct {
desc string
onDelete string
expected error
}{
{
desc: "empty value",
onDelete: "",
expected: nil,
},
{
desc: "delete value",
onDelete: "delete",
expected: nil,
},
{
desc: "retain value",
onDelete: "retain",
expected: nil,
},
{
desc: "Retain value",
onDelete: "Retain",
expected: nil,
},
{
desc: "Delete value",
onDelete: "Delete",
expected: nil,
},
{
desc: "invalid value",
onDelete: "invalid",
expected: fmt.Errorf("invalid value %s for OnDelete, supported values are %v", "invalid", supportedOnDeleteValues),
},
}
for _, test := range tests {
result := validateOnDeleteValue(test.onDelete)
if !reflect.DeepEqual(result, test.expected) {
t.Errorf("test[%s]: unexpected output: %v, expected result: %v", test.desc, result, test.expected)
}
}
}

View File

@ -325,4 +325,27 @@ var _ = ginkgo.Describe("Dynamic Provisioning", func() {
} }
test.Run(cs, ns) test.Run(cs, ns)
}) })
ginkgo.It("should create a volume on demand with retaining subdir on delete [nfs.csi.k8s.io]", func() {
pods := []testsuites.PodDetails{
{
Cmd: "echo 'hello world' > /mnt/test-1/data && grep 'hello world' /mnt/test-1/data",
Volumes: []testsuites.VolumeDetails{
{
ClaimSize: "10Gi",
VolumeMount: testsuites.VolumeMountDetails{
NameGenerate: "test-volume-",
MountPathGenerate: "/mnt/test-",
},
},
},
},
}
test := testsuites.DynamicallyProvisionedCmdVolumeTest{
CSIDriver: testDriver,
Pods: pods,
StorageClassParameters: retainStorageClassParameters,
}
test.Run(cs, ns)
})
}) })

View File

@ -67,6 +67,14 @@ var (
"csi.storage.k8s.io/provisioner-secret-namespace": "default", "csi.storage.k8s.io/provisioner-secret-namespace": "default",
"mountPermissions": "0755", "mountPermissions": "0755",
} }
retainStorageClassParameters = map[string]string{
"server": nfsServerAddress,
"share": nfsShare,
"csi.storage.k8s.io/provisioner-secret-name": "mount-options",
"csi.storage.k8s.io/provisioner-secret-namespace": "default",
"mountPermissions": "0755",
"onDelete": "retain",
}
controllerServer *nfs.ControllerServer controllerServer *nfs.ControllerServer
) )