Skip to content

Commit 657b933

Browse files
authored
test(csi/plugins): add Ginkgo coverage for register paths (#5826)
* test(csi/plugins): add ginkgo coverage for register paths Signed-off-by: Harsh <harshmastic@gmail.com> * test(csi/plugins): harden review-driven coverage specs Signed-off-by: Harsh <harshmastic@gmail.com> * test(csi/plugins): avoid fragile bind mount command patch Signed-off-by: Harsh <harshmastic@gmail.com> --------- Signed-off-by: Harsh <harshmastic@gmail.com>
1 parent dbb42c7 commit 657b933

3 files changed

Lines changed: 527 additions & 4 deletions

File tree

pkg/csi/plugins/nodeserver_test.go

Lines changed: 311 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,20 @@ package plugins
1818

1919
import (
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\nexit 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

Comments
 (0)