diff --git a/Makefile b/Makefile index f9d0a18f..14449f1a 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ build-sanity: $(MAKE) -C cmd/csi-sanity all -TEST_HOSTPATH_VERSION=v1.7.3 +TEST_HOSTPATH_VERSION=v1.11.0 TEST_HOSTPATH_SOURCE=bin/hostpath-source TEST_HOSTPATH_REPO=https://github.com/kubernetes-csi/csi-driver-host-path.git bin/hostpathplugin: diff --git a/hack/_apitest2/api_test.go b/hack/_apitest2/api_test.go index b4b07638..950dda8f 100644 --- a/hack/_apitest2/api_test.go +++ b/hack/_apitest2/api_test.go @@ -31,10 +31,10 @@ func TestMyDriverWithCustomTargetPaths(t *testing.T) { var createTargetDirCalls, createStagingDirCalls, removeTargetDirCalls, removeStagingDirCalls int - wantCreateTargetCalls := 3 - wantCreateStagingCalls := 3 - wantRemoveTargetCalls := 3 - wantRemoveStagingCalls := 3 + wantCreateTargetCalls := 4 + wantCreateStagingCalls := 4 + wantRemoveTargetCalls := 4 + wantRemoveStagingCalls := 4 // 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 diff --git a/pkg/sanity/node.go b/pkg/sanity/node.go index 183bb361..868319f8 100644 --- a/pkg/sanity/node.go +++ b/pkg/sanity/node.go @@ -183,23 +183,22 @@ var _ = DescribeSanity("Node Service", func(sc *TestContext) { var ( r *Resources - providesControllerService bool - controllerPublishSupported bool - nodeStageSupported bool - nodeVolumeStatsSupported bool - nodeExpansionSupported bool - controllerExpansionSupported bool + providesControllerService bool + controllerPublishSupported bool + nodeStageSupported bool + nodeVolumeStatsSupported bool + nodeExpansionSupported bool + controllerExpansionSupported bool + singleNodeMultiWriterSupported bool ) - createVolume := func(volumeName string) *csi.CreateVolumeResponse { + createVolumeWithCapability := func(volumeName string, cap *csi.VolumeCapability) *csi.CreateVolumeResponse { By("creating a single node writer volume for expansion") return r.MustCreateVolume( context.Background(), &csi.CreateVolumeRequest{ - Name: volumeName, - VolumeCapabilities: []*csi.VolumeCapability{ - TestVolumeCapabilityWithAccessType(sc, csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER), - }, + Name: volumeName, + VolumeCapabilities: []*csi.VolumeCapability{cap}, CapacityRange: &csi.CapacityRange{ RequiredBytes: TestVolumeSize(sc), }, @@ -208,8 +207,11 @@ var _ = DescribeSanity("Node Service", func(sc *TestContext) { }, ) } + createVolume := func(volumeName string) *csi.CreateVolumeResponse { + return createVolumeWithCapability(volumeName, TestVolumeCapabilityWithAccessType(sc, csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER)) + } - controllerPublishVolume := func(volumeName string, vol *csi.CreateVolumeResponse, nid *csi.NodeGetInfoResponse) *csi.ControllerPublishVolumeResponse { + controllerPublishVolumeWithCapability := func(volumeName string, vol *csi.CreateVolumeResponse, nid *csi.NodeGetInfoResponse, cap *csi.VolumeCapability) *csi.ControllerPublishVolumeResponse { var conpubvol *csi.ControllerPublishVolumeResponse if controllerPublishSupported { By("controller publishing volume") @@ -219,7 +221,7 @@ var _ = DescribeSanity("Node Service", func(sc *TestContext) { &csi.ControllerPublishVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), NodeId: nid.GetNodeId(), - VolumeCapability: TestVolumeCapabilityWithAccessType(sc, csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER), + VolumeCapability: cap, VolumeContext: vol.GetVolume().GetVolumeContext(), Readonly: false, Secrets: sc.Secrets.ControllerPublishVolumeSecret, @@ -228,14 +230,17 @@ var _ = DescribeSanity("Node Service", func(sc *TestContext) { } return conpubvol } + controllerPublishVolume := func(volumeName string, vol *csi.CreateVolumeResponse, nid *csi.NodeGetInfoResponse) *csi.ControllerPublishVolumeResponse { + return controllerPublishVolumeWithCapability(volumeName, vol, nid, TestVolumeCapabilityWithAccessType(sc, csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER)) + } - nodeStageVolume := func(volumeName string, vol *csi.CreateVolumeResponse, conpubvol *csi.ControllerPublishVolumeResponse) *csi.NodeStageVolumeResponse { + nodeStageVolumeWithCapability := func(volumeName string, vol *csi.CreateVolumeResponse, conpubvol *csi.ControllerPublishVolumeResponse, cap *csi.VolumeCapability) *csi.NodeStageVolumeResponse { // NodeStageVolume if nodeStageSupported { By("node staging volume") nodeStageRequest := &csi.NodeStageVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - VolumeCapability: TestVolumeCapabilityWithAccessType(sc, csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER), + VolumeCapability: cap, StagingTargetPath: sc.StagingPath, VolumeContext: vol.GetVolume().GetVolumeContext(), Secrets: sc.Secrets.NodeStageVolumeSecret, @@ -253,8 +258,11 @@ var _ = DescribeSanity("Node Service", func(sc *TestContext) { } return nil } + nodeStageVolume := func(volumeName string, vol *csi.CreateVolumeResponse, conpubvol *csi.ControllerPublishVolumeResponse) *csi.NodeStageVolumeResponse { + return nodeStageVolumeWithCapability(volumeName, vol, conpubvol, TestVolumeCapabilityWithAccessType(sc, csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER)) + } - nodePublishVolume := func(volumeName string, vol *csi.CreateVolumeResponse, conpubvol *csi.ControllerPublishVolumeResponse) *csi.NodePublishVolumeResponse { + nodePublishVolumeWithCapability := func(volumeName string, vol *csi.CreateVolumeResponse, conpubvol *csi.ControllerPublishVolumeResponse, cap *csi.VolumeCapability) *csi.NodePublishVolumeResponse { By("publishing the volume on a node") var stagingPath string if nodeStageSupported { @@ -264,7 +272,7 @@ var _ = DescribeSanity("Node Service", func(sc *TestContext) { VolumeId: vol.GetVolume().GetVolumeId(), TargetPath: sc.TargetPath + "/target", StagingTargetPath: stagingPath, - VolumeCapability: TestVolumeCapabilityWithAccessType(sc, csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER), + VolumeCapability: cap, VolumeContext: vol.GetVolume().GetVolumeContext(), Secrets: sc.Secrets.NodePublishVolumeSecret, } @@ -281,6 +289,9 @@ var _ = DescribeSanity("Node Service", func(sc *TestContext) { Expect(nodepubvol).NotTo(BeNil()) return nodepubvol } + nodePublishVolume := func(volumeName string, vol *csi.CreateVolumeResponse, conpubvol *csi.ControllerPublishVolumeResponse) *csi.NodePublishVolumeResponse { + return nodePublishVolumeWithCapability(volumeName, vol, conpubvol, TestVolumeCapabilityWithAccessType(sc, csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER)) + } BeforeEach(func() { cl := csi.NewControllerClient(sc.ControllerConn) @@ -309,6 +320,8 @@ var _ = DescribeSanity("Node Service", func(sc *TestContext) { nodeVolumeStatsSupported = isNodeCapabilitySupported(n, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS) nodeExpansionSupported = isNodeCapabilitySupported(n, csi.NodeServiceCapability_RPC_EXPAND_VOLUME) controllerExpansionSupported = isControllerCapabilitySupported(cl, csi.ControllerServiceCapability_RPC_EXPAND_VOLUME) + singleNodeMultiWriterSupported = isNodeCapabilitySupported(n, csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER) + r = &Resources{ Context: sc, ControllerClient: cl, @@ -421,6 +434,57 @@ var _ = DescribeSanity("Node Service", func(sc *TestContext) { Expect(ok).To(BeTrue()) Expect(serverError.Code()).To(Equal(codes.InvalidArgument), "unexpected error: %s", serverError.Message()) }) + + Describe("with single node multi writer capability", func() { + BeforeEach(func() { + if !singleNodeMultiWriterSupported { + Skip("Service does not have single node multi writer capability") + } + }) + + It("should fail when volume with single node single writer access mode is already mounted at a different target path", func() { + By("creating a single node single writer volume") + name := UniqueString("sanity-node-publish-single-node-single-writer") + cap := TestVolumeCapabilityWithAccessType(sc, csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER) + vol := createVolumeWithCapability(name, cap) + + By("Getting a node id") + nid, err := r.NodeGetInfo( + context.Background(), + &csi.NodeGetInfoRequest{}) + Expect(err).NotTo(HaveOccurred()) + Expect(nid).NotTo(BeNil()) + Expect(nid.GetNodeId()).NotTo(BeEmpty()) + + By("Staging and publishing a volume") + conpubvol := controllerPublishVolumeWithCapability(name, vol, nid, cap) + _ = nodeStageVolumeWithCapability(name, vol, conpubvol, cap) + _ = nodePublishVolumeWithCapability(name, vol, conpubvol, cap) + + nodePublishRequest := &csi.NodePublishVolumeRequest{ + VolumeId: vol.GetVolume().GetVolumeId(), + TargetPath: sc.TargetPath + "/other_target", + VolumeCapability: cap, + VolumeContext: vol.GetVolume().GetVolumeContext(), + Secrets: sc.Secrets.NodePublishVolumeSecret, + } + if conpubvol != nil { + nodePublishRequest.PublishContext = conpubvol.GetPublishContext() + } + if nodeStageSupported { + nodePublishRequest.StagingTargetPath = sc.StagingPath + } + + _, err = r.NodePublishVolume( + context.Background(), + nodePublishRequest) + Expect(err).To(HaveOccurred()) + + serverError, ok := status.FromError(err) + Expect(ok).To(BeTrue()) + Expect(serverError.Code()).To(Equal(codes.FailedPrecondition), "unexpected error: %s", serverError.Message()) + }) + }) }) Describe("NodeUnpublishVolume", func() {