Repository: kubernetes-csi/csi-driver-nfs (go)

Issue:
Clean up empty parents on deletion when using subDir
**What happened**:
The subDir parameter provides great flexibility allowing the user to set the subDir to something one considers helpful.
The problem is that when subDir is set to some nested directory tree like for example:

`subDir=${pvc.metadata.namespace}/${pvc.metadata.name}/` when all the volumes from namespace are deleted, you will still see in the storage the empty $namespace directory.

The problem is even bigger if the depth of the subdir is bigger, for example:
`subDir=${pvc.metadata.namespace}/${pvc.metadata.name}/${pv.metadata.name}`
If I delete all the volumes of that namespace I will have at least 2 empty directories inside the $namespace directory on the nfs share.

**What you expected to happen**:
When deleting a volume, the driver should check if its parent is empty ( and it is inside a level of subDir ) and if so delete it too. 

**How to reproduce it**:
Use subDir=`${pvc.metadata.namespace}/${pvc.metadata.name}/${pv.metadata.name}`
Use this values.yaml
```
replicaCount: 1
persistence:
  enabled: true
  accessModes:
    - ReadWriteMany
  size: 1Gi
mariadb:
  primary:
    persistence:
      size: 2Gi
```
and install wordpress to test it. 
`helm install wp oci://registry-1.docker.io/bitnamicharts/wordpress --namespace wp --create-namespace -f values.yml`

Delete the wp namespace and check the remove NFS server, there will be several emptydirs under the wp folder.


**Anything else we need to know?**:

**Environment**:
- CSI Driver version: nfsplugin:v4.8.0
- Kubernetes version (use `kubectl version`): v1.30.3
- OS (e.g. from /etc/os-release):  Flatcar Container Linux by Kinvolk 3815.2.2
- Kernel (e.g. `uname -a`):6.1.85-flatcar


Pull request description:
fix: remove parent dir in DeleteVolume
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/guide/release-notes.md
3. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

**What type of PR is this?**
/kind bug

**What this PR does / why we need it**:
fix: remove parent dir in DeleteVolume

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #763

**Special notes for your reviewer**:

<details>

```
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.363893       1 utils.go:110] GRPC call: /csi.v1.Controller/DeleteVolume
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.364289       1 utils.go:111] GRPC request: {"secrets":"***stripped***","volume_id":"nfs-server.default.svc.cluster.local##nfs-7869/pvc-m74ps#pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998#"}
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.364360       1 controllerserver.go:225] DeleteVolume: found mountOptions(nfsvers=4.1) for volume(nfs-server.default.svc.cluster.local##nfs-7869/pvc-m74ps#pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998#)
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.364550       1 controllerserver.go:502] internally mounting nfs-server.default.svc.cluster.local:/ at /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.364894       1 nodeserver.go:132] NodePublishVolume: volumeID(nfs-server.default.svc.cluster.local##nfs-7869/pvc-m74ps#pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998#) source(nfs-server.default.svc.cluster.local:/) targetPath(/tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998) mountflags([nfsvers=4.1])
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.364925       1 mount_linux.go:218] Mounting cmd (mount) with arguments (-t nfs -o nfsvers=4.1 nfs-server.default.svc.cluster.local:/ /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998)
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.394775       1 nodeserver.go:149] skip chmod on targetPath(/tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998) since mountPermissions is set as 0
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.394816       1 nodeserver.go:151] volume(nfs-server.default.svc.cluster.local##nfs-7869/pvc-m74ps#pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998#) mount nfs-server.default.svc.cluster.local:/ on /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998 succeeded
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.394905       1 controllerserver.go:301] removing subdirectory at /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998/nfs-7869 on internalVolumePath /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998/nfs-7869/pvc-m74ps
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.396139       1 controllerserver.go:517] internally unmounting /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.396163       1 nodeserver.go:172] NodeUnpublishVolume: unmounting volume nfs-server.default.svc.cluster.local##nfs-7869/pvc-m74ps#pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998# on /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.396361       1 nodeserver.go:177] force unmount nfs-server.default.svc.cluster.local##nfs-7869/pvc-m74ps#pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998# on /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.396408       1 mount_helper_common.go:56] unmounting "/tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998" (corruptedMount: false, mounterCanSkipMountPointChecks: true)
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.396422       1 mount_linux.go:789] Unmounting /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.422736       1 mount_helper_common.go:150] Deleting path "/tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998"
[pod/csi-nfs-controller-67c56ff89f-j244d/nfs] I0915 05:01:30.423299       1 nodeserver.go:185] NodeUnpublishVolume: unmount volume nfs-server.default.svc.cluster.local##nfs-7869/pvc-m74ps#pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998# on /tmp/pvc-f2772f7e-a91c-4b0e-83d5-9cb0b3da9998 successfully
```

</details>

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note
fix: remove parent dir in DeleteVolume
```


Patch:
diff --git a/pkg/nfs/controllerserver.go b/pkg/nfs/controllerserver.go
index 95ac0448..39a38e67 100644
--- a/pkg/nfs/controllerserver.go
+++ b/pkg/nfs/controllerserver.go
@@ -291,9 +291,15 @@ func (cs *ControllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol
 			}
 			klog.V(2).Infof("archived subdirectory %s --> %s", internalVolumePath, archivedInternalVolumePath)
 		} else {
+			rootDir := getRootDir(nfsVol.subDir)
+			if rootDir != "" {
+				rootDir = filepath.Join(getInternalMountPath(cs.Driver.workingMountDir, nfsVol), rootDir)
+			} else {
+				rootDir = internalVolumePath
+			}
 			// delete subdirectory under base-dir
-			klog.V(2).Infof("removing subdirectory at %v", internalVolumePath)
-			if err = os.RemoveAll(internalVolumePath); err != nil {
+			klog.V(2).Infof("removing subdirectory at %v on internalVolumePath %s", rootDir, internalVolumePath)
+			if err = os.RemoveAll(rootDir); err != nil {
 				return nil, status.Errorf(codes.Internal, "delete subdirectory(%s) failed with %v", internalVolumePath, err)
 			}
 		}
diff --git a/pkg/nfs/utils.go b/pkg/nfs/utils.go
index d0c202e6..3e54923e 100644
--- a/pkg/nfs/utils.go
+++ b/pkg/nfs/utils.go
@@ -215,3 +215,9 @@ func waitForPathNotExistWithTimeout(path string, timeout time.Duration) error {
 		time.Sleep(500 * time.Microsecond)
 	}
 }
+
+// getRootDir returns the root directory of the given directory
+func getRootDir(path string) string {
+	parts := strings.Split(path, "/")
+	return parts[0]
+}


Test patch:
diff --git a/pkg/nfs/utils_test.go b/pkg/nfs/utils_test.go
index 34f3b3e2..1d843085 100644
--- a/pkg/nfs/utils_test.go
+++ b/pkg/nfs/utils_test.go
@@ -387,3 +387,44 @@ func TestWaitForPathNotExistWithTimeout(t *testing.T) {
 		}
 	}
 }
+
+func TestGetRootPath(t *testing.T) {
+	tests := []struct {
+		desc     string
+		dir      string
+		expected string
+	}{
+		{
+			desc:     "empty path",
+			dir:      "",
+			expected: "",
+		},
+		{
+			desc:     "root path",
+			dir:      "/",
+			expected: "",
+		},
+		{
+			desc:     "subdir path",
+			dir:      "/subdir",
+			expected: "",
+		},
+		{
+			desc:     "subdir path without leading slash",
+			dir:      "subdir",
+			expected: "subdir",
+		},
+		{
+			desc:     "multiple subdir path without leading slash",
+			dir:      "subdir/subdir2",
+			expected: "subdir",
+		},
+	}
+
+	for _, test := range tests {
+		result := getRootDir(test.dir)
+		if result != test.expected {
+			t.Errorf("Unexpected result: %s, expected: %s", result, test.expected)
+		}
+	}
+}
diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go
index 8142cea9..fddf1f89 100644
--- a/test/e2e/e2e_suite_test.go
+++ b/test/e2e/e2e_suite_test.go
@@ -62,7 +62,7 @@ var (
 	subDirStorageClassParameters = map[string]string{
 		"server": nfsServerAddress,
 		"share":  nfsShare,
-		"subDir": "subDirectory-${pvc.metadata.namespace}",
+		"subDir": "${pvc.metadata.namespace}/${pvc.metadata.name}",
 		"csi.storage.k8s.io/provisioner-secret-name":      "mount-options",
 		"csi.storage.k8s.io/provisioner-secret-namespace": "default",
 		"mountPermissions": "0755",