@@ -18,15 +18,20 @@ package plugins
1818
1919import (
2020 "context"
21+ "errors"
2122 "os"
23+ "os/exec"
2224 "path/filepath"
25+ "reflect"
2326 "time"
2427
28+ "github.com/agiledragon/gomonkey/v2"
2529 "github.com/container-storage-interface/spec/lib/go/csi"
2630 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
2731 "github.com/fluid-cloudnative/fluid/pkg/common"
2832 "github.com/fluid-cloudnative/fluid/pkg/ddc/base"
2933 "github.com/fluid-cloudnative/fluid/pkg/utils"
34+ "github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
3035 csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common"
3136 . "github.com/onsi/ginkgo/v2"
3237 . "github.com/onsi/gomega"
@@ -289,6 +294,51 @@ var _ = Describe("NodeServer", func() {
289294 })
290295 })
291296
297+ Context ("when bind mounting succeeds" , func () {
298+ It ("should return success after creating the target path" , func () {
299+ tempDir , err := os .MkdirTemp ("" , "node-publish-success-*" )
300+ Expect (err ).NotTo (HaveOccurred ())
301+ DeferCleanup (func () {
302+ Expect (os .RemoveAll (tempDir )).To (Succeed ())
303+ })
304+
305+ fluidPath := filepath .Join (tempDir , "runtime" , testName )
306+ targetPath := filepath .Join (tempDir , "target" )
307+ fakeMountPath := filepath .Join (tempDir , "mount" )
308+ originalPath := os .Getenv ("PATH" )
309+
310+ isMountedPatch := gomonkey .ApplyFunc (utils .IsMounted , func (absPath string ) (bool , error ) {
311+ return false , os .ErrNotExist
312+ })
313+ defer isMountedPatch .Reset ()
314+
315+ mountReadyPatch := gomonkey .ApplyFunc (utils .CheckMountReadyAndSubPathExist , func (fluidPath string , mountType string , subPath string ) error {
316+ return nil
317+ })
318+ defer mountReadyPatch .Reset ()
319+
320+ Expect (os .WriteFile (fakeMountPath , []byte ("#!/bin/sh\n exit 0\n " ), 0755 )).To (Succeed ())
321+ Expect (os .Setenv ("PATH" , tempDir + string (os .PathListSeparator )+ originalPath )).To (Succeed ())
322+ DeferCleanup (func () {
323+ Expect (os .Setenv ("PATH" , originalPath )).To (Succeed ())
324+ })
325+
326+ req := & csi.NodePublishVolumeRequest {
327+ VolumeId : testVolumeID ,
328+ TargetPath : targetPath ,
329+ VolumeContext : map [string ]string {
330+ common .VolumeAttrFluidPath : fluidPath ,
331+ },
332+ }
333+
334+ resp , err := ns .NodePublishVolume (context .Background (), req )
335+
336+ Expect (err ).NotTo (HaveOccurred ())
337+ Expect (resp ).NotTo (BeNil ())
338+ Expect (targetPath ).To (BeADirectory ())
339+ })
340+ })
341+
292342 Context ("when skip check mount ready is set" , func () {
293343 It ("should skip mount ready check for mountPod mode" , func () {
294344 // Use /tmp for test to avoid permission issues
@@ -392,6 +442,67 @@ var _ = Describe("NodeServer", func() {
392442 Expect (os .IsNotExist (err )).To (BeTrue ())
393443 })
394444 })
445+
446+ Context ("when bind mount cleanup succeeds" , func () {
447+ It ("should unmount until clean and remove the mount point" , func () {
448+ tempDir , err := os .MkdirTemp ("" , "node-unpublish-success-*" )
449+ Expect (err ).NotTo (HaveOccurred ())
450+ DeferCleanup (func () {
451+ Expect (os .RemoveAll (tempDir )).To (Succeed ())
452+ })
453+
454+ targetPath := filepath .Join (tempDir , "mounted-target" )
455+ Expect (os .MkdirAll (targetPath , 0o750 )).To (Succeed ())
456+
457+ pathExistsPatch := gomonkey .ApplyFunc (utils .MountPathExists , func (path string ) (bool , error ) {
458+ return true , nil
459+ })
460+ defer pathExistsPatch .Reset ()
461+
462+ removeSymlinkPatch := gomonkey .ApplyFunc (utils .RemoveSymlink , func (path string ) (bool , error ) {
463+ return false , nil
464+ })
465+ defer removeSymlinkPatch .Reset ()
466+
467+ mounterType := reflect .TypeOf (& mount.Mounter {})
468+ isLikelyNotMountPointCalls := 0
469+ isLikelyNotMountPointPatch := gomonkey .ApplyMethod (mounterType , "IsLikelyNotMountPoint" , func (_ * mount.Mounter , file string ) (bool , error ) {
470+ isLikelyNotMountPointCalls ++
471+ if isLikelyNotMountPointCalls == 1 {
472+ return false , nil
473+ }
474+ return true , nil
475+ })
476+ defer isLikelyNotMountPointPatch .Reset ()
477+
478+ unmountCalls := 0
479+ unmountPatch := gomonkey .ApplyMethod (mounterType , "Unmount" , func (_ * mount.Mounter , target string ) error {
480+ unmountCalls ++
481+ return nil
482+ })
483+ defer unmountPatch .Reset ()
484+
485+ cleanupPatch := gomonkey .ApplyFunc (mount .CleanupMountPoint , func (path string , mounter mount.Interface , extensiveMountPointCheck bool ) error {
486+ return os .RemoveAll (path )
487+ })
488+ defer cleanupPatch .Reset ()
489+
490+ req := & csi.NodeUnpublishVolumeRequest {
491+ VolumeId : testVolumeID ,
492+ TargetPath : targetPath ,
493+ }
494+
495+ resp , err := ns .NodeUnpublishVolume (context .Background (), req )
496+
497+ Expect (err ).NotTo (HaveOccurred ())
498+ Expect (resp ).NotTo (BeNil ())
499+ Expect (unmountCalls ).To (Equal (1 ))
500+ Expect (isLikelyNotMountPointCalls ).To (Equal (2 ))
501+
502+ _ , statErr := os .Stat (targetPath )
503+ Expect (os .IsNotExist (statErr )).To (BeTrue ())
504+ })
505+ })
395506 })
396507
397508 Describe ("NodeStageVolume" , func () {
@@ -487,6 +598,46 @@ var _ = Describe("NodeServer", func() {
487598 })
488599 })
489600
601+ Context ("when fuse label key is not provided" , func () {
602+ It ("should fall back to runtime info to label the node" , func () {
603+ tempDir , err := os .MkdirTemp ("" , "node-stage-fallback-*" )
604+ Expect (err ).NotTo (HaveOccurred ())
605+ DeferCleanup (func () {
606+ Expect (os .RemoveAll (tempDir )).To (Succeed ())
607+ })
608+
609+ runtimeInfo , err := base .BuildRuntimeInfo (testName , testNamespace , common .AlluxioRuntime )
610+ Expect (err ).NotTo (HaveOccurred ())
611+
612+ runtimeInfoPatch := gomonkey .ApplyFunc (base .GetRuntimeInfo , func (client.Reader , string , string ) (base.RuntimeInfoInterface , error ) {
613+ return runtimeInfo , nil
614+ })
615+ defer runtimeInfoPatch .Reset ()
616+
617+ fluidPath := filepath .Join (tempDir , "runtime-fallback" )
618+ Expect (os .MkdirAll (fluidPath , 0o750 )).To (Succeed ())
619+
620+ req := & csi.NodeStageVolumeRequest {
621+ VolumeId : testVolumeID ,
622+ VolumeContext : map [string ]string {
623+ common .VolumeAttrName : testName ,
624+ common .VolumeAttrNamespace : testNamespace ,
625+ common .VolumeAttrFluidPath : fluidPath ,
626+ },
627+ }
628+
629+ resp , err := ns .NodeStageVolume (context .Background (), req )
630+
631+ Expect (err ).NotTo (HaveOccurred ())
632+ Expect (resp ).NotTo (BeNil ())
633+
634+ updatedNode := & corev1.Node {}
635+ err = mockClient .Get (context .Background (), types.NamespacedName {Name : testNode .Name }, updatedNode )
636+ Expect (err ).NotTo (HaveOccurred ())
637+ Expect (updatedNode .Labels ).To (HaveKeyWithValue (runtimeInfo .GetFuseLabelName (), "true" ))
638+ })
639+ })
640+
490641 Context ("when SessMgr is required" , func () {
491642 It ("should prepare SessMgr when work directory is specified" , func () {
492643 workDir := "/tmp/sessmgr-work"
@@ -873,20 +1024,58 @@ var _ = Describe("NodeServer", func() {
8731024 })
8741025
8751026 Describe ("isLikelyNeedUnmount" , func () {
876- var mounter mount.Interface
1027+ var fakeMounter mount.Interface
8771028
8781029 BeforeEach (func () {
879- mounter = mount .NewFakeMounter ([]mount. MountPoint {} )
1030+ fakeMounter = mount .New ( "" )
8801031 })
8811032
8821033 Context ("when path does not exist" , func () {
8831034 It ("should return false without error" , func () {
884- needUnmount , err := isLikelyNeedUnmount (mounter , "/non/existent/path" )
1035+ patch := gomonkey .ApplyMethod (reflect .TypeOf (& mount.Mounter {}), "IsLikelyNotMountPoint" , func (_ * mount.Mounter , file string ) (bool , error ) {
1036+ return true , os .ErrNotExist
1037+ })
1038+ defer patch .Reset ()
1039+
1040+ needUnmount , err := isLikelyNeedUnmount (fakeMounter , "/non/existent/path" )
8851041
8861042 Expect (err ).NotTo (HaveOccurred ())
8871043 Expect (needUnmount ).To (BeFalse ())
8881044 })
8891045 })
1046+
1047+ Context ("when mounter reports a mount point" , func () {
1048+ It ("should require unmount" , func () {
1049+ patch := gomonkey .ApplyMethod (reflect .TypeOf (& mount.Mounter {}), "IsLikelyNotMountPoint" , func (_ * mount.Mounter , file string ) (bool , error ) {
1050+ return false , nil
1051+ })
1052+ defer patch .Reset ()
1053+
1054+ needUnmount , err := isLikelyNeedUnmount (fakeMounter , "/mounted/path" )
1055+
1056+ Expect (err ).NotTo (HaveOccurred ())
1057+ Expect (needUnmount ).To (BeTrue ())
1058+ })
1059+ })
1060+
1061+ Context ("when mounter returns an unexpected error" , func () {
1062+ It ("should return the error" , func () {
1063+ patch := gomonkey .ApplyMethod (reflect .TypeOf (& mount.Mounter {}), "IsLikelyNotMountPoint" , func (_ * mount.Mounter , file string ) (bool , error ) {
1064+ return false , errors .New ("unexpected" )
1065+ })
1066+ defer patch .Reset ()
1067+
1068+ corruptedPatch := gomonkey .ApplyFunc (mount .IsCorruptedMnt , func (err error ) bool {
1069+ return false
1070+ })
1071+ defer corruptedPatch .Reset ()
1072+
1073+ needUnmount , err := isLikelyNeedUnmount (fakeMounter , "/error/path" )
1074+
1075+ Expect (err ).To (MatchError ("unexpected" ))
1076+ Expect (needUnmount ).To (BeFalse ())
1077+ })
1078+ })
8901079 })
8911080
8921081 Describe ("checkMountPathExists" , func () {
@@ -938,6 +1127,20 @@ var _ = Describe("NodeServer", func() {
9381127 Expect (err ).To (BeNil ())
9391128 })
9401129 })
1130+
1131+ Context ("when stat returns a non-not-exist error" , func () {
1132+ It ("should wrap the stat error" , func () {
1133+ statPatch := gomonkey .ApplyFunc (os .Stat , func (name string ) (os.FileInfo , error ) {
1134+ return nil , errors .New ("stat failed" )
1135+ })
1136+ defer statPatch .Reset ()
1137+
1138+ err := cleanUpBrokenMountPoint ("/broken/path" )
1139+
1140+ Expect (err ).To (HaveOccurred ())
1141+ Expect (err .Error ()).To (ContainSubstring ("failed to os.Stat(/broken/path)" ))
1142+ })
1143+ })
9411144 })
9421145
9431146 Describe ("prepareSessMgr" , func () {
@@ -1029,6 +1232,75 @@ var _ = Describe("NodeServer", func() {
10291232 })
10301233 })
10311234
1235+ Context ("when metadata file cannot be read" , func () {
1236+ It ("should return false" , func () {
1237+ runtimeInfo , err := base .BuildRuntimeInfo (testName , testNamespace , common .AlluxioRuntime )
1238+ Expect (err ).NotTo (HaveOccurred ())
1239+
1240+ needUpdate := checkIfFuseNeedUpdate (runtimeInfo , "v2" )
1241+
1242+ Expect (needUpdate ).To (BeFalse ())
1243+ })
1244+ })
1245+
1246+ Context ("when metadata generation differs from latest" , func () {
1247+ It ("should return true" , func () {
1248+ tempDir , err := os .MkdirTemp ("" , "fuse-generation-*" )
1249+ Expect (err ).NotTo (HaveOccurred ())
1250+ DeferCleanup (func () {
1251+ Expect (os .RemoveAll (tempDir )).To (Succeed ())
1252+ })
1253+
1254+ mountRoot := filepath .Join (tempDir , "mount-root" )
1255+ Expect (os .Setenv (utils .MountRoot , mountRoot )).To (Succeed ())
1256+ DeferCleanup (func () {
1257+ Expect (os .Unsetenv (utils .MountRoot )).To (Succeed ())
1258+ })
1259+
1260+ runtimeInfo , err := base .BuildRuntimeInfo (testName , testNamespace , common .AlluxioRuntime )
1261+ Expect (err ).NotTo (HaveOccurred ())
1262+
1263+ fuseMetaDir := filepath .Join (mountRoot , common .AlluxioRuntime , testNamespace , testName , ".meta" , "fuse" )
1264+ Expect (os .MkdirAll (fuseMetaDir , 0o755 )).To (Succeed ())
1265+ labelsFile := filepath .Join (fuseMetaDir , utils .MetaDataFuseLabelFileName )
1266+ labelsContent := []byte (common .LabelRuntimeFuseGeneration + "=\" v1\" \n " )
1267+ Expect (os .WriteFile (labelsFile , labelsContent , 0o644 )).To (Succeed ())
1268+
1269+ needUpdate := checkIfFuseNeedUpdate (runtimeInfo , "v2" )
1270+
1271+ Expect (needUpdate ).To (BeTrue ())
1272+ })
1273+ })
1274+
1275+ Context ("when metadata generation matches latest" , func () {
1276+ It ("should return false" , func () {
1277+ tempDir , err := os .MkdirTemp ("" , "matching-fuse-generation-*" )
1278+ Expect (err ).NotTo (HaveOccurred ())
1279+ DeferCleanup (func () {
1280+ Expect (os .RemoveAll (tempDir )).To (Succeed ())
1281+ })
1282+
1283+ mountRoot := filepath .Join (tempDir , "matching-mount-root" )
1284+ Expect (os .Setenv (utils .MountRoot , mountRoot )).To (Succeed ())
1285+ DeferCleanup (func () {
1286+ Expect (os .Unsetenv (utils .MountRoot )).To (Succeed ())
1287+ })
1288+
1289+ runtimeInfo , err := base .BuildRuntimeInfo (testName , testNamespace , common .AlluxioRuntime )
1290+ Expect (err ).NotTo (HaveOccurred ())
1291+
1292+ fuseMetaDir := filepath .Join (mountRoot , common .AlluxioRuntime , testNamespace , testName , ".meta" , "fuse" )
1293+ Expect (os .MkdirAll (fuseMetaDir , 0o755 )).To (Succeed ())
1294+ labelsFile := filepath .Join (fuseMetaDir , utils .MetaDataFuseLabelFileName )
1295+ labelsContent := []byte (common .LabelRuntimeFuseGeneration + "=\" v2\" \n " )
1296+ Expect (os .WriteFile (labelsFile , labelsContent , 0o644 )).To (Succeed ())
1297+
1298+ needUpdate := checkIfFuseNeedUpdate (runtimeInfo , "v2" )
1299+
1300+ Expect (needUpdate ).To (BeFalse ())
1301+ })
1302+ })
1303+
10321304 Context ("when versions match" , func () {
10331305 It ("should return false or handle appropriately" , func () {
10341306 // Create a simple mock runtime info
@@ -1182,6 +1454,28 @@ var _ = Describe("NodeServer", func() {
11821454 Expect (err ).NotTo (HaveOccurred ())
11831455 Expect (cleanFunc ).NotTo (BeNil ())
11841456 })
1457+
1458+ It ("should remove the fuse label when the volume is no longer in use" , func () {
1459+ updatedNode := testNode .DeepCopy ()
1460+ updatedNode .Labels ["test-fuse-label" ] = "true"
1461+ Expect (mockClient .Update (context .Background (), updatedNode )).To (Succeed ())
1462+
1463+ commandPatch := gomonkey .ApplyFunc (cmdguard .Command , func (name string , arg ... string ) (* exec.Cmd , error ) {
1464+ return exec .Command ("sh" , "-c" , "exit 1" ), nil
1465+ })
1466+ defer commandPatch .Reset ()
1467+
1468+ cleanFunc , err := ns .getCleanFuseFunc (testVolumeID )
1469+ Expect (err ).NotTo (HaveOccurred ())
1470+ Expect (cleanFunc ).NotTo (BeNil ())
1471+
1472+ Expect (cleanFunc ()).To (Succeed ())
1473+
1474+ nodeAfterCleanup := & corev1.Node {}
1475+ err = mockClient .Get (context .Background (), types.NamespacedName {Name : testNode .Name }, nodeAfterCleanup )
1476+ Expect (err ).NotTo (HaveOccurred ())
1477+ Expect (nodeAfterCleanup .Labels ).NotTo (HaveKey ("test-fuse-label" ))
1478+ })
11851479 })
11861480
11871481 Context ("when clean policy is OnRuntimeDeleted" , func () {
@@ -1248,5 +1542,19 @@ var _ = Describe("NodeServer", func() {
12481542 _ = err
12491543 })
12501544 })
1545+
1546+ Context ("when the command exits with status 1" , func () {
1547+ It ("should return not in use without error" , func () {
1548+ patch := gomonkey .ApplyFunc (cmdguard .Command , func (name string , arg ... string ) (* exec.Cmd , error ) {
1549+ return exec .Command ("sh" , "-c" , "exit 1" ), nil
1550+ })
1551+ defer patch .Reset ()
1552+
1553+ inUse , err := checkMountInUse ("test-volume" )
1554+
1555+ Expect (err ).NotTo (HaveOccurred ())
1556+ Expect (inUse ).To (BeFalse ())
1557+ })
1558+ })
12511559 })
12521560})
0 commit comments