add csi-test to vendor

This commit is contained in:
wackxu
2018-08-21 23:03:36 +08:00
parent 62551068b1
commit a2987675cf
110 changed files with 11973 additions and 24 deletions

View File

@@ -0,0 +1,62 @@
# CSI Driver Sanity Tester
This library provides a simple way to ensure that a CSI driver conforms to
the CSI specification. There are two ways to leverage this testing framework.
For CSI drivers written in Golang, the framework provides a simple API function
to call to test the driver. Another way to run the test suite is to use the
command line program [csi-sanity](https://github.com/kubernetes-csi/csi-test/tree/master/cmd/csi-sanity).
## For Golang CSI Drivers
This framework leverages the Ginkgo BDD testing framework to deliver a descriptive
test suite for your driver. To test your driver, simply call the API in one of your
Golang `TestXXX` functions. For example:
```go
func TestMyDriver(t *testing.T) {
// Setup the full driver and its environment
... setup driver ...
config := &sanity.Config{
TargetPath: ...
StagingPath: ...
Address: endpoint,
}
// Now call the test suite
sanity.Test(t, config)
}
```
Only one such test function is supported because under the hood a
Ginkgo test suite gets constructed and executed by the call.
Alternatively, the tests can also be embedded inside a Ginkgo test
suite. In that case it is possible to define multiple tests with
different configurations:
```go
var _ = Describe("MyCSIDriver", func () {
Context("Config A", func () {
var config &sanity.Config
BeforeEach() {
... setup driver and config...
}
AfterEach() {
...tear down driver...
}
Describe("CSI sanity", func() {
sanity.GinkgoTest(config)
})
})
Context("Config B", func () {
...
})
})
```
## Command line program
Please see [csi-sanity](https://github.com/kubernetes-csi/csi-test/tree/master/cmd/csi-sanity)

View File

@@ -0,0 +1,134 @@
/*
Copyright 2018 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sanity
import (
"context"
"log"
"github.com/container-storage-interface/spec/lib/go/csi/v0"
. "github.com/onsi/ginkgo"
)
// VolumeInfo keeps track of the information needed to delete a volume.
type VolumeInfo struct {
// Node on which the volume was published, empty if none
// or publishing is not supported.
NodeID string
// Volume ID assigned by CreateVolume.
VolumeID string
}
// Cleanup keeps track of resources, in particular volumes, which need
// to be freed when testing is done.
type Cleanup struct {
Context *SanityContext
ControllerClient csi.ControllerClient
NodeClient csi.NodeClient
ControllerPublishSupported bool
NodeStageSupported bool
// Maps from volume name to the node ID for which the volume
// is published and the volume ID.
volumes map[string]VolumeInfo
}
// RegisterVolume adds or updates an entry for the volume with the
// given name.
func (cl *Cleanup) RegisterVolume(name string, info VolumeInfo) {
if cl.volumes == nil {
cl.volumes = make(map[string]VolumeInfo)
}
cl.volumes[name] = info
}
// MaybeRegisterVolume adds or updates an entry for the volume with
// the given name if CreateVolume was successful.
func (cl *Cleanup) MaybeRegisterVolume(name string, vol *csi.CreateVolumeResponse, err error) {
if err == nil && vol.GetVolume().GetId() != "" {
cl.RegisterVolume(name, VolumeInfo{VolumeID: vol.GetVolume().GetId()})
}
}
// UnregisterVolume removes the entry for the volume with the
// given name, thus preventing all cleanup operations for it.
func (cl *Cleanup) UnregisterVolume(name string) {
if cl.volumes != nil {
delete(cl.volumes, name)
}
}
// DeleteVolumes stops using the registered volumes and tries to delete all of them.
func (cl *Cleanup) DeleteVolumes() {
if cl.volumes == nil {
return
}
logger := log.New(GinkgoWriter, "cleanup: ", 0)
ctx := context.Background()
for name, info := range cl.volumes {
logger.Printf("deleting %s = %s", name, info.VolumeID)
if _, err := cl.NodeClient.NodeUnpublishVolume(
ctx,
&csi.NodeUnpublishVolumeRequest{
VolumeId: info.VolumeID,
TargetPath: cl.Context.Config.TargetPath,
},
); err != nil {
logger.Printf("warning: NodeUnpublishVolume: %s", err)
}
if cl.NodeStageSupported {
if _, err := cl.NodeClient.NodeUnstageVolume(
ctx,
&csi.NodeUnstageVolumeRequest{
VolumeId: info.VolumeID,
StagingTargetPath: cl.Context.Config.StagingPath,
},
); err != nil {
logger.Printf("warning: NodeUnstageVolume: %s", err)
}
}
if cl.ControllerPublishSupported && info.NodeID != "" {
if _, err := cl.ControllerClient.ControllerUnpublishVolume(
ctx,
&csi.ControllerUnpublishVolumeRequest{
VolumeId: info.VolumeID,
NodeId: info.NodeID,
ControllerUnpublishSecrets: cl.Context.Secrets.ControllerUnpublishVolumeSecret,
},
); err != nil {
logger.Printf("warning: ControllerUnpublishVolume: %s", err)
}
}
if _, err := cl.ControllerClient.DeleteVolume(
ctx,
&csi.DeleteVolumeRequest{
VolumeId: info.VolumeID,
ControllerDeleteSecrets: cl.Context.Secrets.DeleteVolumeSecret,
},
); err != nil {
logger.Printf("error: DeleteVolume: %s", err)
}
cl.UnregisterVolume(name)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/*
Copyright 2017 Luis Pabón luis@portworx.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sanity
import (
"context"
"fmt"
"regexp"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/container-storage-interface/spec/lib/go/csi/v0"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = DescribeSanity("Identity Service", func(sc *SanityContext) {
var (
c csi.IdentityClient
)
BeforeEach(func() {
c = csi.NewIdentityClient(sc.Conn)
})
Describe("GetPluginCapabilities", func() {
It("should return appropriate capabilities", func() {
req := &csi.GetPluginCapabilitiesRequest{}
res, err := c.GetPluginCapabilities(context.Background(), req)
Expect(err).NotTo(HaveOccurred())
Expect(res).NotTo(BeNil())
By("checking successful response")
Expect(res.GetCapabilities()).NotTo(BeNil())
for _, cap := range res.GetCapabilities() {
switch cap.GetService().GetType() {
case csi.PluginCapability_Service_CONTROLLER_SERVICE:
case csi.PluginCapability_Service_ACCESSIBILITY_CONSTRAINTS:
default:
Fail(fmt.Sprintf("Unknown capability: %v\n", cap.GetService().GetType()))
}
}
})
})
Describe("Probe", func() {
It("should return appropriate information", func() {
req := &csi.ProbeRequest{}
res, err := c.Probe(context.Background(), req)
Expect(err).NotTo(HaveOccurred())
Expect(res).NotTo(BeNil())
By("verifying return status")
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code() == codes.FailedPrecondition ||
serverError.Code() == codes.OK).To(BeTrue())
if res.GetReady() != nil {
Expect(res.GetReady().GetValue() == true ||
res.GetReady().GetValue() == false).To(BeTrue())
}
})
})
Describe("GetPluginInfo", func() {
It("should return appropriate information", func() {
req := &csi.GetPluginInfoRequest{}
res, err := c.GetPluginInfo(context.Background(), req)
Expect(err).NotTo(HaveOccurred())
Expect(res).NotTo(BeNil())
By("verifying name size and characters")
Expect(res.GetName()).ToNot(HaveLen(0))
Expect(len(res.GetName())).To(BeNumerically("<=", 63))
Expect(regexp.
MustCompile("^[a-zA-Z][A-Za-z0-9-\\.\\_]{0,61}[a-zA-Z]$").
MatchString(res.GetName())).To(BeTrue())
})
})
})

View File

@@ -0,0 +1,528 @@
/*
Copyright 2017 Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sanity
import (
"context"
"fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/container-storage-interface/spec/lib/go/csi/v0"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func isNodeCapabilitySupported(c csi.NodeClient,
capType csi.NodeServiceCapability_RPC_Type,
) bool {
caps, err := c.NodeGetCapabilities(
context.Background(),
&csi.NodeGetCapabilitiesRequest{})
Expect(err).NotTo(HaveOccurred())
Expect(caps).NotTo(BeNil())
for _, cap := range caps.GetCapabilities() {
Expect(cap.GetRpc()).NotTo(BeNil())
if cap.GetRpc().GetType() == capType {
return true
}
}
return false
}
func isPluginCapabilitySupported(c csi.IdentityClient,
capType csi.PluginCapability_Service_Type,
) bool {
caps, err := c.GetPluginCapabilities(
context.Background(),
&csi.GetPluginCapabilitiesRequest{})
Expect(err).NotTo(HaveOccurred())
Expect(caps).NotTo(BeNil())
Expect(caps.GetCapabilities()).NotTo(BeNil())
for _, cap := range caps.GetCapabilities() {
Expect(cap.GetService()).NotTo(BeNil())
if cap.GetService().GetType() == capType {
return true
}
}
return false
}
var _ = DescribeSanity("Node Service", func(sc *SanityContext) {
var (
cl *Cleanup
c csi.NodeClient
s csi.ControllerClient
controllerPublishSupported bool
nodeStageSupported bool
)
BeforeEach(func() {
c = csi.NewNodeClient(sc.Conn)
s = csi.NewControllerClient(sc.Conn)
controllerPublishSupported = isControllerCapabilitySupported(
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())
}
cl = &Cleanup{
Context: sc,
NodeClient: c,
ControllerClient: s,
ControllerPublishSupported: controllerPublishSupported,
NodeStageSupported: nodeStageSupported,
}
})
AfterEach(func() {
cl.DeleteVolumes()
})
Describe("NodeGetCapabilities", func() {
It("should return appropriate capabilities", func() {
caps, err := c.NodeGetCapabilities(
context.Background(),
&csi.NodeGetCapabilitiesRequest{})
By("checking successful response")
Expect(err).NotTo(HaveOccurred())
Expect(caps).NotTo(BeNil())
for _, cap := range caps.GetCapabilities() {
Expect(cap.GetRpc()).NotTo(BeNil())
switch cap.GetRpc().GetType() {
case csi.NodeServiceCapability_RPC_UNKNOWN:
case csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME:
default:
Fail(fmt.Sprintf("Unknown capability: %v\n", cap.GetRpc().GetType()))
}
}
})
})
Describe("NodeGetId", func() {
It("should return appropriate values", func() {
nid, err := c.NodeGetId(
context.Background(),
&csi.NodeGetIdRequest{})
Expect(err).NotTo(HaveOccurred())
Expect(nid).NotTo(BeNil())
Expect(nid.GetNodeId()).NotTo(BeEmpty())
})
})
Describe("NodeGetInfo", func() {
var (
i csi.IdentityClient
accessibilityConstraintSupported bool
)
BeforeEach(func() {
i = csi.NewIdentityClient(sc.Conn)
accessibilityConstraintSupported = isPluginCapabilitySupported(i, csi.PluginCapability_Service_ACCESSIBILITY_CONSTRAINTS)
})
It("should return approproate values", func() {
ninfo, err := c.NodeGetInfo(
context.Background(),
&csi.NodeGetInfoRequest{})
Expect(err).NotTo(HaveOccurred())
Expect(ninfo).NotTo(BeNil())
Expect(ninfo.GetNodeId()).NotTo(BeEmpty())
Expect(ninfo.GetMaxVolumesPerNode()).NotTo(BeNumerically("<", 0))
if accessibilityConstraintSupported {
Expect(ninfo.GetAccessibleTopology()).NotTo(BeNil())
}
})
})
Describe("NodePublishVolume", func() {
It("should fail when no volume id is provided", func() {
_, err := c.NodePublishVolume(
context.Background(),
&csi.NodePublishVolumeRequest{
NodePublishSecrets: sc.Secrets.NodePublishVolumeSecret,
},
)
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
It("should fail when no target path is provided", func() {
_, err := c.NodePublishVolume(
context.Background(),
&csi.NodePublishVolumeRequest{
VolumeId: "id",
NodePublishSecrets: sc.Secrets.NodePublishVolumeSecret,
},
)
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
It("should fail when no volume capability is provided", func() {
_, err := c.NodePublishVolume(
context.Background(),
&csi.NodePublishVolumeRequest{
VolumeId: "id",
TargetPath: sc.Config.TargetPath,
NodePublishSecrets: sc.Secrets.NodePublishVolumeSecret,
},
)
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
})
Describe("NodeUnpublishVolume", func() {
It("should fail when no volume id is provided", func() {
_, err := c.NodeUnpublishVolume(
context.Background(),
&csi.NodeUnpublishVolumeRequest{})
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
It("should fail when no target path is provided", func() {
_, err := c.NodeUnpublishVolume(
context.Background(),
&csi.NodeUnpublishVolumeRequest{
VolumeId: "id",
})
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
})
Describe("NodeStageVolume", func() {
var (
device string
)
BeforeEach(func() {
if !nodeStageSupported {
Skip("NodeStageVolume not supported")
}
device = "/dev/mock"
})
It("should fail when no volume id is provided", func() {
_, err := c.NodeStageVolume(
context.Background(),
&csi.NodeStageVolumeRequest{
StagingTargetPath: sc.Config.StagingPath,
VolumeCapability: &csi.VolumeCapability{
AccessType: &csi.VolumeCapability_Mount{
Mount: &csi.VolumeCapability_MountVolume{},
},
AccessMode: &csi.VolumeCapability_AccessMode{
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
},
},
PublishInfo: map[string]string{
"device": device,
},
NodeStageSecrets: sc.Secrets.NodeStageVolumeSecret,
},
)
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
It("should fail when no staging target path is provided", func() {
_, err := c.NodeStageVolume(
context.Background(),
&csi.NodeStageVolumeRequest{
VolumeId: "id",
VolumeCapability: &csi.VolumeCapability{
AccessType: &csi.VolumeCapability_Mount{
Mount: &csi.VolumeCapability_MountVolume{},
},
AccessMode: &csi.VolumeCapability_AccessMode{
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
},
},
PublishInfo: map[string]string{
"device": device,
},
NodeStageSecrets: sc.Secrets.NodeStageVolumeSecret,
},
)
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
It("should fail when no volume capability is provided", func() {
_, err := c.NodeStageVolume(
context.Background(),
&csi.NodeStageVolumeRequest{
VolumeId: "id",
StagingTargetPath: sc.Config.StagingPath,
PublishInfo: map[string]string{
"device": device,
},
NodeStageSecrets: sc.Secrets.NodeStageVolumeSecret,
},
)
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
})
Describe("NodeUnstageVolume", func() {
BeforeEach(func() {
if !nodeStageSupported {
Skip("NodeUnstageVolume not supported")
}
})
It("should fail when no volume id is provided", func() {
_, err := c.NodeUnstageVolume(
context.Background(),
&csi.NodeUnstageVolumeRequest{
StagingTargetPath: sc.Config.StagingPath,
})
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
It("should fail when no staging target path is provided", func() {
_, err := c.NodeUnstageVolume(
context.Background(),
&csi.NodeUnstageVolumeRequest{
VolumeId: "id",
})
Expect(err).To(HaveOccurred())
serverError, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
})
})
It("should work", func() {
name := uniqueString("sanity-node-full")
// 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,
},
},
},
ControllerCreateSecrets: sc.Secrets.CreateVolumeSecret,
},
)
Expect(err).NotTo(HaveOccurred())
Expect(vol).NotTo(BeNil())
Expect(vol.GetVolume()).NotTo(BeNil())
Expect(vol.GetVolume().GetId()).NotTo(BeEmpty())
cl.RegisterVolume(name, VolumeInfo{VolumeID: vol.GetVolume().GetId()})
By("getting a node id")
nid, err := c.NodeGetId(
context.Background(),
&csi.NodeGetIdRequest{})
Expect(err).NotTo(HaveOccurred())
Expect(nid).NotTo(BeNil())
Expect(nid.GetNodeId()).NotTo(BeEmpty())
var conpubvol *csi.ControllerPublishVolumeResponse
if controllerPublishSupported {
By("controller publishing volume")
conpubvol, err = s.ControllerPublishVolume(
context.Background(),
&csi.ControllerPublishVolumeRequest{
VolumeId: vol.GetVolume().GetId(),
NodeId: nid.GetNodeId(),
VolumeCapability: &csi.VolumeCapability{
AccessType: &csi.VolumeCapability_Mount{
Mount: &csi.VolumeCapability_MountVolume{},
},
AccessMode: &csi.VolumeCapability_AccessMode{
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
},
},
VolumeAttributes: vol.GetVolume().GetAttributes(),
Readonly: false,
ControllerPublishSecrets: sc.Secrets.ControllerPublishVolumeSecret,
},
)
Expect(err).NotTo(HaveOccurred())
cl.RegisterVolume(name, VolumeInfo{VolumeID: vol.GetVolume().GetId(), NodeID: nid.GetNodeId()})
Expect(conpubvol).NotTo(BeNil())
}
// NodeStageVolume
if nodeStageSupported {
By("node staging volume")
nodestagevol, err := c.NodeStageVolume(
context.Background(),
&csi.NodeStageVolumeRequest{
VolumeId: vol.GetVolume().GetId(),
VolumeCapability: &csi.VolumeCapability{
AccessType: &csi.VolumeCapability_Mount{
Mount: &csi.VolumeCapability_MountVolume{},
},
AccessMode: &csi.VolumeCapability_AccessMode{
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
},
},
StagingTargetPath: sc.Config.StagingPath,
VolumeAttributes: vol.GetVolume().GetAttributes(),
PublishInfo: conpubvol.GetPublishInfo(),
NodeStageSecrets: 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.Config.StagingPath
}
nodepubvol, err := c.NodePublishVolume(
context.Background(),
&csi.NodePublishVolumeRequest{
VolumeId: vol.GetVolume().GetId(),
TargetPath: sc.Config.TargetPath,
StagingTargetPath: stagingPath,
VolumeCapability: &csi.VolumeCapability{
AccessType: &csi.VolumeCapability_Mount{
Mount: &csi.VolumeCapability_MountVolume{},
},
AccessMode: &csi.VolumeCapability_AccessMode{
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
},
},
VolumeAttributes: vol.GetVolume().GetAttributes(),
PublishInfo: conpubvol.GetPublishInfo(),
NodePublishSecrets: sc.Secrets.NodePublishVolumeSecret,
},
)
Expect(err).NotTo(HaveOccurred())
Expect(nodepubvol).NotTo(BeNil())
// NodeUnpublishVolume
By("cleaning up calling nodeunpublish")
nodeunpubvol, err := c.NodeUnpublishVolume(
context.Background(),
&csi.NodeUnpublishVolumeRequest{
VolumeId: vol.GetVolume().GetId(),
TargetPath: sc.Config.TargetPath,
})
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().GetId(),
StagingTargetPath: sc.Config.StagingPath,
},
)
Expect(err).NotTo(HaveOccurred())
Expect(nodeunstagevol).NotTo(BeNil())
}
if controllerPublishSupported {
By("cleaning up calling controllerunpublishing")
controllerunpubvol, err := s.ControllerUnpublishVolume(
context.Background(),
&csi.ControllerUnpublishVolumeRequest{
VolumeId: vol.GetVolume().GetId(),
NodeId: nid.GetNodeId(),
ControllerUnpublishSecrets: sc.Secrets.ControllerUnpublishVolumeSecret,
},
)
Expect(err).NotTo(HaveOccurred())
Expect(controllerunpubvol).NotTo(BeNil())
}
By("cleaning up deleting the volume")
_, err = s.DeleteVolume(
context.Background(),
&csi.DeleteVolumeRequest{
VolumeId: vol.GetVolume().GetId(),
ControllerDeleteSecrets: sc.Secrets.DeleteVolumeSecret,
},
)
Expect(err).NotTo(HaveOccurred())
})
})

View File

@@ -0,0 +1,163 @@
/*
Copyright 2017 Luis Pabón luis@portworx.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sanity
import (
"crypto/rand"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/kubernetes-csi/csi-test/utils"
yaml "gopkg.in/yaml.v2"
"google.golang.org/grpc"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
// CSISecrets consists of secrets used in CSI credentials.
type CSISecrets struct {
CreateVolumeSecret map[string]string `yaml:"CreateVolumeSecret"`
DeleteVolumeSecret map[string]string `yaml:"DeleteVolumeSecret"`
ControllerPublishVolumeSecret map[string]string `yaml:"ControllerPublishVolumeSecret"`
ControllerUnpublishVolumeSecret map[string]string `yaml:"ControllerUnpublishVolumeSecret"`
NodeStageVolumeSecret map[string]string `yaml:"NodeStageVolumeSecret"`
NodePublishVolumeSecret map[string]string `yaml:"NodePublishVolumeSecret"`
CreateSnapshotSecret map[string]string `yaml:"CreateSnapshotSecret"`
DeleteSnapshotSecret map[string]string `yaml:"DeleteSnapshotSecret"`
}
// Config provides the configuration for the sanity tests. It
// needs to be initialized by the user of the sanity package.
type Config struct {
TargetPath string
StagingPath string
Address string
SecretsFile string
TestVolumeSize int64
}
// SanityContext holds the variables that each test can depend on. It
// gets initialized before each test block runs.
type SanityContext struct {
Config *Config
Conn *grpc.ClientConn
Secrets *CSISecrets
}
// Test will test the CSI driver at the specified address by
// setting up a Ginkgo suite and running it.
func Test(t *testing.T, reqConfig *Config) {
sc := &SanityContext{
Config: reqConfig,
}
registerTestsInGinkgo(sc)
RegisterFailHandler(Fail)
RunSpecs(t, "CSI Driver Test Suite")
}
func GinkgoTest(reqConfig *Config) {
sc := &SanityContext{
Config: reqConfig,
}
registerTestsInGinkgo(sc)
}
func (sc *SanityContext) setup() {
var err error
if len(sc.Config.SecretsFile) > 0 {
sc.Secrets, err = loadSecrets(sc.Config.SecretsFile)
Expect(err).NotTo(HaveOccurred())
} else {
sc.Secrets = &CSISecrets{}
}
By("connecting to CSI driver")
sc.Conn, err = utils.Connect(sc.Config.Address)
Expect(err).NotTo(HaveOccurred())
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())
}
}
func (sc *SanityContext) teardown() {
if sc.Conn != nil {
sc.Conn.Close()
sc.Conn = nil
}
}
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
}
if !fileInfo.IsDir() {
return fmt.Errorf("Target location %s is not a directory", targetPath)
}
return nil
}
func loadSecrets(path string) (*CSISecrets, error) {
var creds CSISecrets
yamlFile, err := ioutil.ReadFile(path)
if err != nil {
return &creds, fmt.Errorf("failed to read file %q: #%v", path, err)
}
err = yaml.Unmarshal(yamlFile, &creds)
if err != nil {
return &creds, fmt.Errorf("error unmarshaling yaml: #%v", err)
}
return &creds, nil
}
var uniqueSuffix = "-" + pseudoUUID()
// pseudoUUID returns a unique string generated from random
// bytes, empty string in case of error.
func pseudoUUID() string {
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
// Shouldn't happen?!
return ""
}
return fmt.Sprintf("%08X-%08X", b[0:4], b[4:8])
}
// uniqueString returns a unique string by appending a random
// number. In case of an error, just the prefix is returned, so it
// alone should already be fairly unique.
func uniqueString(prefix string) string {
return prefix + uniqueSuffix
}

View File

@@ -0,0 +1,56 @@
/*
Copyright 2018 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sanity
import (
. "github.com/onsi/ginkgo"
)
type test struct {
text string
body func(*SanityContext)
}
var tests []test
// DescribeSanity must be used instead of the usual Ginkgo Describe to
// register a test block. The difference is that the body function
// will be called multiple times with the right context (when
// setting up a Ginkgo suite or a testing.T test, with the right
// configuration).
func DescribeSanity(text string, body func(*SanityContext)) bool {
tests = append(tests, test{text, body})
return true
}
// registerTestsInGinkgo invokes the actual Gingko Describe
// for the tests registered earlier with DescribeSanity.
func registerTestsInGinkgo(sc *SanityContext) {
for _, test := range tests {
Describe(test.text, func() {
BeforeEach(func() {
sc.setup()
})
test.body(sc)
AfterEach(func() {
sc.teardown()
})
})
}
}