diff --git a/hack/_apitest2/api_test.go b/hack/_apitest2/api_test.go new file mode 100644 index 00000000..40acc3f4 --- /dev/null +++ b/hack/_apitest2/api_test.go @@ -0,0 +1,63 @@ +package apitest2 + +import ( + "fmt" + "os" + "path" + "testing" + + "github.com/kubernetes-csi/csi-test/pkg/sanity" +) + +// TestMyDriverWithCustomTargetPaths verifies that CreateTargetDir and +// CreateStagingDir are called a specific number of times. +func TestMyDriverWithCustomTargetPaths(t *testing.T) { + var createTargetDirCalls, createStagingDirCalls int + + wantCreateTargetCalls := 3 + wantCreateStagingCalls := 3 + + // tmpPath could be a CO specific directory under which all the target dirs + // are created. For k8s, it could be /var/lib/kubelet/pods under which the + // mount directories could be created. + tmpPath := path.Join(os.TempDir(), "csi") + config := &sanity.Config{ + TargetPath: "foo/target/mount", + StagingPath: "foo/staging/mount", + Address: "/tmp/e2e-csi-sanity.sock", + CreateTargetDir: func(targetPath string) (string, error) { + createTargetDirCalls++ + targetPath = path.Join(tmpPath, targetPath) + return targetPath, createTargetDir(targetPath) + }, + CreateStagingDir: func(targetPath string) (string, error) { + createStagingDirCalls++ + targetPath = path.Join(tmpPath, targetPath) + return targetPath, createTargetDir(targetPath) + }, + } + + sanity.Test(t, config) + + if createTargetDirCalls != wantCreateTargetCalls { + t.Errorf("unexpected number of CreateTargetDir calls:\n(WNT) %d\n(GOT) %d", wantCreateTargetCalls, createTargetDirCalls) + } + + if createStagingDirCalls != wantCreateStagingCalls { + t.Errorf("unexpected number of CreateStagingDir calls:\n(WNT) %d\n(GOT) %d", wantCreateStagingCalls, createStagingDirCalls) + } +} + +func createTargetDir(targetPath string) error { + fileInfo, err := os.Stat(targetPath) + if err != nil && os.IsNotExist(err) { + return os.MkdirAll(targetPath, 0755) + } else if err != nil { + return err + } + if !fileInfo.IsDir() { + return fmt.Errorf("Target location %s is not a directory", targetPath) + } + + return nil +} diff --git a/hack/e2e.sh b/hack/e2e.sh index 419405c4..83f4770d 100755 --- a/hack/e2e.sh +++ b/hack/e2e.sh @@ -69,6 +69,20 @@ runTestAPI() fi } +runTestAPIWithCustomTargetPaths() +{ + CSI_ENDPOINT=$1 ./bin/mock-driver & + local pid=$! + + # Running a specific test to verify that the custom target paths are called + # a deterministic number of times. + GOCACHE=off go test -v ./hack/_apitest2/api_test.go -ginkgo.focus="NodePublishVolume"; ret=$? + + if [ $ret -ne 0 ] ; then + exit $ret + fi +} + make cd cmd/csi-sanity @@ -88,4 +102,7 @@ runTestWithDifferentAddresses "${UDS_NODE}" "${UDS_CONTROLLER}" rm -f $UDS_NODE rm -f $UDS_CONTROLLER +runTestAPIWithCustomTargetPaths "${UDS}" +rm -rf $UDS + exit 0 diff --git a/pkg/sanity/node.go b/pkg/sanity/node.go index bd706c51..62339525 100644 --- a/pkg/sanity/node.go +++ b/pkg/sanity/node.go @@ -86,10 +86,6 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { s, csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME) nodeStageSupported = isNodeCapabilitySupported(c, csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME) - if nodeStageSupported { - err := createMountTargetLocation(sc.Config.StagingPath) - Expect(err).NotTo(HaveOccurred()) - } nodeVolumeStatsSupported = isNodeCapabilitySupported(c, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS) cl = &Cleanup{ Context: sc, @@ -190,7 +186,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { context.Background(), &csi.NodePublishVolumeRequest{ VolumeId: "id", - TargetPath: sc.Config.TargetPath, + TargetPath: sc.targetPath, Secrets: sc.Secrets.NodePublishVolumeSecret, }, ) @@ -247,7 +243,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { _, err := c.NodeStageVolume( context.Background(), &csi.NodeStageVolumeRequest{ - StagingTargetPath: sc.Config.StagingPath, + StagingTargetPath: sc.stagingPath, VolumeCapability: &csi.VolumeCapability{ AccessType: &csi.VolumeCapability_Mount{ Mount: &csi.VolumeCapability_MountVolume{}, @@ -329,7 +325,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { context.Background(), &csi.NodeStageVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - StagingTargetPath: sc.Config.StagingPath, + StagingTargetPath: sc.stagingPath, PublishContext: map[string]string{ "device": device, }, @@ -368,7 +364,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { _, err := c.NodeUnstageVolume( context.Background(), &csi.NodeUnstageVolumeRequest{ - StagingTargetPath: sc.Config.StagingPath, + StagingTargetPath: sc.stagingPath, }) Expect(err).To(HaveOccurred()) @@ -519,7 +515,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, }, }, - StagingTargetPath: sc.Config.StagingPath, + StagingTargetPath: sc.stagingPath, VolumeContext: vol.GetVolume().GetVolumeContext(), PublishContext: conpubvol.GetPublishContext(), Secrets: sc.Secrets.NodeStageVolumeSecret, @@ -532,13 +528,13 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { By("publishing the volume on a node") var stagingPath string if nodeStageSupported { - stagingPath = sc.Config.StagingPath + stagingPath = sc.stagingPath } nodepubvol, err := c.NodePublishVolume( context.Background(), &csi.NodePublishVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath, + TargetPath: sc.targetPath, StagingTargetPath: stagingPath, VolumeCapability: &csi.VolumeCapability{ AccessType: &csi.VolumeCapability_Mount{ @@ -577,7 +573,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { context.Background(), &csi.NodeUnpublishVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath, + TargetPath: sc.targetPath, }) Expect(err).NotTo(HaveOccurred()) Expect(nodeunpubvol).NotTo(BeNil()) @@ -588,7 +584,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { context.Background(), &csi.NodeUnstageVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - StagingTargetPath: sc.Config.StagingPath, + StagingTargetPath: sc.stagingPath, }, ) Expect(err).NotTo(HaveOccurred()) @@ -703,7 +699,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, }, }, - StagingTargetPath: sc.Config.StagingPath, + StagingTargetPath: sc.stagingPath, VolumeContext: vol.GetVolume().GetVolumeContext(), PublishContext: conpubvol.GetPublishContext(), Secrets: sc.Secrets.NodeStageVolumeSecret, @@ -716,13 +712,13 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { By("publishing the volume on a node") var stagingPath string if nodeStageSupported { - stagingPath = sc.Config.StagingPath + stagingPath = sc.stagingPath } nodepubvol, err := c.NodePublishVolume( context.Background(), &csi.NodePublishVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath, + TargetPath: sc.targetPath, StagingTargetPath: stagingPath, VolumeCapability: &csi.VolumeCapability{ AccessType: &csi.VolumeCapability_Mount{ @@ -747,7 +743,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { context.Background(), &csi.NodeGetVolumeStatsRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - VolumePath: sc.Config.TargetPath, + VolumePath: sc.targetPath, }, ) Expect(err).ToNot(HaveOccurred()) @@ -760,7 +756,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { context.Background(), &csi.NodeUnpublishVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath, + TargetPath: sc.targetPath, }) Expect(err).NotTo(HaveOccurred()) Expect(nodeunpubvol).NotTo(BeNil()) @@ -771,7 +767,7 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { context.Background(), &csi.NodeUnstageVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - StagingTargetPath: sc.Config.StagingPath, + StagingTargetPath: sc.stagingPath, }, ) Expect(err).NotTo(HaveOccurred()) diff --git a/pkg/sanity/sanity.go b/pkg/sanity/sanity.go index f6ff707d..ad075356 100644 --- a/pkg/sanity/sanity.go +++ b/pkg/sanity/sanity.go @@ -60,6 +60,13 @@ type Config struct { TestNodeVolumeAttachLimit bool JUnitFile string + + // Callback functions to customize the creation of target and staging + // directories. Returns the new paths for mount and staging. + // If not defined, directories are created in the default way at TargetPath + // and StagingPath. + CreateTargetDir func(path string) (string, error) + CreateStagingDir func(path string) (string, error) } // SanityContext holds the variables that each test can depend on. It @@ -72,6 +79,10 @@ type SanityContext struct { connAddress string controllerConnAddress string + + // Target and staging paths derived from the sanity config. + targetPath string + stagingPath string } // Test will test the CSI driver at the specified address by @@ -153,12 +164,15 @@ func (sc *SanityContext) setup() { } By("creating mount and staging directories") - err = createMountTargetLocation(sc.Config.TargetPath) - Expect(err).NotTo(HaveOccurred()) - if len(sc.Config.StagingPath) > 0 { - err = createMountTargetLocation(sc.Config.StagingPath) - Expect(err).NotTo(HaveOccurred()) - } + // If callback function for creating target dir is specified, use it. + targetPath, err := createMountTargetLocation(sc.Config.TargetPath, sc.Config.CreateTargetDir) + Expect(err).NotTo(HaveOccurred(), "failed to create target directory %s", targetPath) + sc.targetPath = targetPath + + // If callback function for creating staging dir is specified, use it. + stagingPath, err := createMountTargetLocation(sc.Config.StagingPath, sc.Config.CreateStagingDir) + Expect(err).NotTo(HaveOccurred(), "failed to create staging directory %s", stagingPath) + sc.stagingPath = stagingPath } func (sc *SanityContext) teardown() { @@ -174,18 +188,35 @@ func (sc *SanityContext) teardown() { // (https://github.com/kubernetes-csi/csi-test/pull/98). } -func createMountTargetLocation(targetPath string) error { - fileInfo, err := os.Stat(targetPath) - if err != nil && os.IsNotExist(err) { - return os.MkdirAll(targetPath, 0755) - } else if err != nil { - return err +func createMountTargetLocation(targetPath string, customCreateDir func(string) (string, error)) (string, error) { + + // Return the target path if empty. + if len(targetPath) < 0 { + return targetPath, nil } - if !fileInfo.IsDir() { - return fmt.Errorf("Target location %s is not a directory", targetPath) + + var newTargetPath string + + if customCreateDir != nil { + newpath, err := customCreateDir(targetPath) + if err != nil { + return "", err + } + newTargetPath = newpath + } else { + fileInfo, err := os.Stat(targetPath) + if err != nil && os.IsNotExist(err) { + return "", os.MkdirAll(targetPath, 0755) + } else if err != nil { + return "", err + } + if !fileInfo.IsDir() { + return "", fmt.Errorf("Target location %s is not a directory", targetPath) + } + newTargetPath = targetPath } - return nil + return newTargetPath, nil } func loadSecrets(path string) (*CSISecrets, error) {