diff --git a/pkg/sanity/node.go b/pkg/sanity/node.go index 52f35d3c..9e73307f 100644 --- a/pkg/sanity/node.go +++ b/pkg/sanity/node.go @@ -66,6 +66,208 @@ func isPluginCapabilitySupported(c csi.IdentityClient, return false } +func runControllerTest(sc *SanityContext, c csi.NodeClient, s csi.ControllerClient, cl *Cleanup, controllerPublishSupported bool, nodeStageSupported bool, nodeVolumeStatsSupported bool, count int) { + + name := UniqueString("sanity-node-full") + + By("getting node information") + ni, err := c.NodeGetInfo( + context.Background(), + &csi.NodeGetInfoRequest{}) + Expect(err).NotTo(HaveOccurred()) + Expect(ni).NotTo(BeNil()) + Expect(ni.GetNodeId()).NotTo(BeEmpty()) + + var accReqs *csi.TopologyRequirement + if ni.AccessibleTopology != nil { + // Topology requirements are honored if provided by the driver + accReqs = &csi.TopologyRequirement{ + Requisite: []*csi.Topology{ni.AccessibleTopology}, + } + } + + // Create Volume First + By("creating a single node writer volume") + vol, err := s.CreateVolume( + context.Background(), + &csi.CreateVolumeRequest{ + Name: name, + VolumeCapabilities: []*csi.VolumeCapability{ + { + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{}, + }, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + }, + Secrets: sc.Secrets.CreateVolumeSecret, + Parameters: sc.Config.TestVolumeParameters, + AccessibilityRequirements: accReqs, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(vol).NotTo(BeNil()) + Expect(vol.GetVolume()).NotTo(BeNil()) + Expect(vol.GetVolume().GetVolumeId()).NotTo(BeEmpty()) + cl.RegisterVolume(name, VolumeInfo{VolumeID: vol.GetVolume().GetVolumeId()}) + + var conpubvol *csi.ControllerPublishVolumeResponse + if controllerPublishSupported { + By("controller publishing volume") + + conpubvol, err = s.ControllerPublishVolume( + context.Background(), + &csi.ControllerPublishVolumeRequest{ + VolumeId: vol.GetVolume().GetVolumeId(), + NodeId: ni.GetNodeId(), + VolumeCapability: &csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{}, + }, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + VolumeContext: vol.GetVolume().GetVolumeContext(), + Readonly: false, + Secrets: sc.Secrets.ControllerPublishVolumeSecret, + }, + ) + Expect(err).NotTo(HaveOccurred()) + cl.RegisterVolume(name, VolumeInfo{VolumeID: vol.GetVolume().GetVolumeId(), NodeID: ni.GetNodeId()}) + Expect(conpubvol).NotTo(BeNil()) + } + // NodeStageVolume + if nodeStageSupported { + By("node staging volume") + for i := 0; i < count; i++ { + nodestagevol, err := c.NodeStageVolume( + context.Background(), + &csi.NodeStageVolumeRequest{ + VolumeId: vol.GetVolume().GetVolumeId(), + VolumeCapability: &csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{}, + }, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + StagingTargetPath: sc.StagingPath, + VolumeContext: vol.GetVolume().GetVolumeContext(), + PublishContext: conpubvol.GetPublishContext(), + Secrets: sc.Secrets.NodeStageVolumeSecret, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(nodestagevol).NotTo(BeNil()) + } + } + // NodePublishVolume + By("publishing the volume on a node") + var stagingPath string + if nodeStageSupported { + stagingPath = sc.StagingPath + } + for i := 0; i < count; i++ { + nodepubvol, err := c.NodePublishVolume( + context.Background(), + &csi.NodePublishVolumeRequest{ + VolumeId: vol.GetVolume().GetVolumeId(), + TargetPath: sc.TargetPath + "/target", + StagingTargetPath: stagingPath, + VolumeCapability: &csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{}, + }, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + VolumeContext: vol.GetVolume().GetVolumeContext(), + PublishContext: conpubvol.GetPublishContext(), + Secrets: sc.Secrets.NodePublishVolumeSecret, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(nodepubvol).NotTo(BeNil()) + } + // NodeGetVolumeStats + if nodeVolumeStatsSupported { + By("Get node volume stats") + statsResp, err := c.NodeGetVolumeStats( + context.Background(), + &csi.NodeGetVolumeStatsRequest{ + VolumeId: vol.GetVolume().GetVolumeId(), + VolumePath: sc.TargetPath + "/target", + }, + ) + Expect(err).ToNot(HaveOccurred()) + Expect(statsResp.GetUsage()).ToNot(BeNil()) + } + + for i := 0; i < count; i++ { + // NodeUnpublishVolume + By("cleaning up calling nodeunpublish") + nodeunpubvol, err := c.NodeUnpublishVolume( + context.Background(), + &csi.NodeUnpublishVolumeRequest{ + VolumeId: vol.GetVolume().GetVolumeId(), + TargetPath: sc.TargetPath + "/target", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(nodeunpubvol).NotTo(BeNil()) + } + + if nodeStageSupported { + for i := 0; i < count; i++ { + By("cleaning up calling nodeunstage") + nodeunstagevol, err := c.NodeUnstageVolume( + context.Background(), + &csi.NodeUnstageVolumeRequest{ + VolumeId: vol.GetVolume().GetVolumeId(), + StagingTargetPath: sc.StagingPath, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(nodeunstagevol).NotTo(BeNil()) + } + } + + if controllerPublishSupported { + for i := 0; i < count; i++ { + By("cleaning up calling controllerunpublishing") + + controllerunpubvol, err := s.ControllerUnpublishVolume( + context.Background(), + &csi.ControllerUnpublishVolumeRequest{ + VolumeId: vol.GetVolume().GetVolumeId(), + NodeId: ni.GetNodeId(), + Secrets: sc.Secrets.ControllerUnpublishVolumeSecret, + }, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(controllerunpubvol).NotTo(BeNil()) + } + } + + By("cleaning up deleting the volume") + + for i := 0; i < count; i++ { + _, err = s.DeleteVolume( + context.Background(), + &csi.DeleteVolumeRequest{ + VolumeId: vol.GetVolume().GetVolumeId(), + Secrets: sc.Secrets.DeleteVolumeSecret, + }, + ) + Expect(err).NotTo(HaveOccurred()) + } +} + + var _ = DescribeSanity("Node Service", func(sc *SanityContext) { var ( cl *Cleanup @@ -645,191 +847,21 @@ var _ = DescribeSanity("Node Service", func(sc *SanityContext) { Skip("Controller Service not provided: CreateVolume not supported") } - name := UniqueString("sanity-node-full") - - By("getting node information") - ni, err := c.NodeGetInfo( - context.Background(), - &csi.NodeGetInfoRequest{}) - Expect(err).NotTo(HaveOccurred()) - Expect(ni).NotTo(BeNil()) - Expect(ni.GetNodeId()).NotTo(BeEmpty()) - - var accReqs *csi.TopologyRequirement - if ni.AccessibleTopology != nil { - // Topology requirements are honored if provided by the driver - accReqs = &csi.TopologyRequirement{ - Requisite: []*csi.Topology{ni.AccessibleTopology}, - } - } - - // Create Volume First - By("creating a single node writer volume") - vol, err := s.CreateVolume( - context.Background(), - &csi.CreateVolumeRequest{ - Name: name, - VolumeCapabilities: []*csi.VolumeCapability{ - { - AccessType: &csi.VolumeCapability_Mount{ - Mount: &csi.VolumeCapability_MountVolume{}, - }, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, - }, - }, - Secrets: sc.Secrets.CreateVolumeSecret, - Parameters: sc.Config.TestVolumeParameters, - AccessibilityRequirements: accReqs, - }, - ) - Expect(err).NotTo(HaveOccurred()) - Expect(vol).NotTo(BeNil()) - Expect(vol.GetVolume()).NotTo(BeNil()) - Expect(vol.GetVolume().GetVolumeId()).NotTo(BeEmpty()) - cl.RegisterVolume(name, VolumeInfo{VolumeID: vol.GetVolume().GetVolumeId()}) - - var conpubvol *csi.ControllerPublishVolumeResponse - if controllerPublishSupported { - By("controller publishing volume") - - conpubvol, err = s.ControllerPublishVolume( - context.Background(), - &csi.ControllerPublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - NodeId: ni.GetNodeId(), - VolumeCapability: &csi.VolumeCapability{ - AccessType: &csi.VolumeCapability_Mount{ - Mount: &csi.VolumeCapability_MountVolume{}, - }, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, - }, - VolumeContext: vol.GetVolume().GetVolumeContext(), - Readonly: false, - Secrets: sc.Secrets.ControllerPublishVolumeSecret, - }, - ) - Expect(err).NotTo(HaveOccurred()) - cl.RegisterVolume(name, VolumeInfo{VolumeID: vol.GetVolume().GetVolumeId(), NodeID: ni.GetNodeId()}) - Expect(conpubvol).NotTo(BeNil()) - } - // NodeStageVolume - if nodeStageSupported { - By("node staging volume") - nodestagevol, err := c.NodeStageVolume( - context.Background(), - &csi.NodeStageVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - VolumeCapability: &csi.VolumeCapability{ - AccessType: &csi.VolumeCapability_Mount{ - Mount: &csi.VolumeCapability_MountVolume{}, - }, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, - }, - StagingTargetPath: sc.StagingPath, - VolumeContext: vol.GetVolume().GetVolumeContext(), - PublishContext: conpubvol.GetPublishContext(), - Secrets: sc.Secrets.NodeStageVolumeSecret, - }, - ) - Expect(err).NotTo(HaveOccurred()) - Expect(nodestagevol).NotTo(BeNil()) - } - // NodePublishVolume - By("publishing the volume on a node") - var stagingPath string - if nodeStageSupported { - stagingPath = sc.StagingPath - } - nodepubvol, err := c.NodePublishVolume( - context.Background(), - &csi.NodePublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.TargetPath + "/target", - StagingTargetPath: stagingPath, - VolumeCapability: &csi.VolumeCapability{ - AccessType: &csi.VolumeCapability_Mount{ - Mount: &csi.VolumeCapability_MountVolume{}, - }, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, - }, - VolumeContext: vol.GetVolume().GetVolumeContext(), - PublishContext: conpubvol.GetPublishContext(), - Secrets: sc.Secrets.NodePublishVolumeSecret, - }, - ) - Expect(err).NotTo(HaveOccurred()) - Expect(nodepubvol).NotTo(BeNil()) - - // NodeGetVolumeStats - if nodeVolumeStatsSupported { - By("Get node volume stats") - statsResp, err := c.NodeGetVolumeStats( - context.Background(), - &csi.NodeGetVolumeStatsRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - VolumePath: sc.TargetPath + "/target", - }, - ) - Expect(err).ToNot(HaveOccurred()) - Expect(statsResp.GetUsage()).ToNot(BeNil()) + By("runControllerTest") + runControllerTest(sc, c, s, cl, controllerPublishSupported, nodeStageSupported, nodeVolumeStatsSupported, 1) + }) + It("should be idempotent", func() { + if !providesControllerService { + Skip("Controller Service not provided: CreateVolume not supported") } - - // NodeUnpublishVolume - By("cleaning up calling nodeunpublish") - nodeunpubvol, err := c.NodeUnpublishVolume( - context.Background(), - &csi.NodeUnpublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.TargetPath + "/target", - }) - Expect(err).NotTo(HaveOccurred()) - Expect(nodeunpubvol).NotTo(BeNil()) - - if nodeStageSupported { - By("cleaning up calling nodeunstage") - nodeunstagevol, err := c.NodeUnstageVolume( - context.Background(), - &csi.NodeUnstageVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - StagingTargetPath: sc.StagingPath, - }, - ) - Expect(err).NotTo(HaveOccurred()) - Expect(nodeunstagevol).NotTo(BeNil()) + if sc.Config.IdempotentCount < 0 { + Skip("Config.IdempotentCount is negative, skip tests") } - - if controllerPublishSupported { - By("cleaning up calling controllerunpublishing") - - controllerunpubvol, err := s.ControllerUnpublishVolume( - context.Background(), - &csi.ControllerUnpublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - NodeId: ni.GetNodeId(), - Secrets: sc.Secrets.ControllerUnpublishVolumeSecret, - }, - ) - Expect(err).NotTo(HaveOccurred()) - Expect(controllerunpubvol).NotTo(BeNil()) + count := sc.Config.IdempotentCount + if count == 0 { + count = 10 } - - By("cleaning up deleting the volume") - - _, err = s.DeleteVolume( - context.Background(), - &csi.DeleteVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - Secrets: sc.Secrets.DeleteVolumeSecret, - }, - ) - Expect(err).NotTo(HaveOccurred()) + By("runControllerTest with Idempotent count") + runControllerTest(sc, c, s, cl, controllerPublishSupported, nodeStageSupported, nodeVolumeStatsSupported, count) }) }) diff --git a/pkg/sanity/sanity.go b/pkg/sanity/sanity.go index 49daa7f7..05cc6059 100644 --- a/pkg/sanity/sanity.go +++ b/pkg/sanity/sanity.go @@ -127,6 +127,16 @@ type Config struct { // it will be set to a DefaultIDGenerator instance when // passing the config to Test or GinkgoTest. IDGen IDGenerator + + // Repeat count for Volume operations to test idempotency requirements. + // csi-test/pkg/sanity/node.go:runControllerTest() optionally runs + // added variant set of tests which repeats those Volume operations + // that are required to be idempotent, based on this count value. + // -1 : Skip idempotency test + // 0 : Use default value, 10 repetitions + // n : repeat n times + // Note that missing explicit value leads to default repetition count. + IdempotentCount int } // SanityContext holds the variables that each test can depend on. It